Add manual testing setup for pkg6depotd

- Introduced scripts and configurations for manual testing using `anyvm` with OpenIndiana and OmniOS.
- Implemented repository fetching (`fetch_repo.sh`) and server startup (`run_depotd.sh`) scripts.
- Enhanced `pkg6depotd` to support default publisher routes and trailing slashes.
- Updated integration tests to verify new publisher route behavior.
This commit is contained in:
Till Wegmueller 2026-01-20 17:44:36 +01:00
parent d16e5339ce
commit 22178cffd7
No known key found for this signature in database
9 changed files with 317 additions and 22 deletions

View file

@ -991,7 +991,10 @@ impl ReadableRepository for FileBackend {
} }
// Create and return a RepositoryInfo struct // Create and return a RepositoryInfo struct
Ok(RepositoryInfo { publishers }) Ok(RepositoryInfo {
publishers,
default_publisher: self.config.default_publisher.clone(),
})
} }
/// List packages in the repository /// List packages in the repository

View file

@ -274,6 +274,8 @@ pub struct PublisherInfo {
pub struct RepositoryInfo { pub struct RepositoryInfo {
/// Information about publishers in the repository /// Information about publishers in the repository
pub publishers: Vec<PublisherInfo>, pub publishers: Vec<PublisherInfo>,
/// Name of the default publisher, if any
pub default_publisher: Option<String>,
} }
/// Information about a package in a repository /// Information about a package in a repository

View file

@ -407,7 +407,10 @@ impl ReadableRepository for RestBackend {
} }
// Create and return a RepositoryInfo struct // Create and return a RepositoryInfo struct
Ok(RepositoryInfo { publishers }) Ok(RepositoryInfo {
publishers,
default_publisher: self.config.default_publisher.clone(),
})
} }
/// List packages in the repository /// List packages in the repository

View file

@ -25,28 +25,38 @@ struct P5iFile {
pub async fn get_publisher_v0( pub async fn get_publisher_v0(
state: State<Arc<DepotRepo>>, state: State<Arc<DepotRepo>>,
path: Path<String>, Path(publisher): Path<String>,
) -> Result<Response, DepotError> { ) -> Result<Response, DepotError> {
get_publisher_impl(state, path).await get_publisher_response(state, Some(publisher)).await
} }
pub async fn get_publisher_v1( pub async fn get_publisher_v1(
state: State<Arc<DepotRepo>>, state: State<Arc<DepotRepo>>,
path: Path<String>, Path(publisher): Path<String>,
) -> Result<Response, DepotError> { ) -> Result<Response, DepotError> {
get_publisher_impl(state, path).await get_publisher_response(state, Some(publisher)).await
} }
async fn get_publisher_impl( pub async fn get_default_publisher_v0(
state: State<Arc<DepotRepo>>,
) -> Result<Response, DepotError> {
get_publisher_response(state, None).await
}
pub async fn get_default_publisher_v1(
state: State<Arc<DepotRepo>>,
) -> Result<Response, DepotError> {
get_publisher_response(state, None).await
}
async fn get_publisher_response(
State(repo): State<Arc<DepotRepo>>, State(repo): State<Arc<DepotRepo>>,
Path(publisher): Path<String>, publisher: Option<String>,
) -> Result<Response, DepotError> { ) -> Result<Response, DepotError> {
let repo_info = repo.get_info()?; let repo_info = repo.get_info()?;
let pub_info = repo_info if let Some(name) = publisher {
.publishers let pub_info = repo_info.publishers.into_iter().find(|p| p.name == name);
.into_iter()
.find(|p| p.name == publisher);
if let Some(p) = pub_info { if let Some(p) = pub_info {
let p5i = P5iFile { let p5i = P5iFile {
@ -64,7 +74,29 @@ async fn get_publisher_impl(
Ok(([(header::CONTENT_TYPE, "application/vnd.pkg5.info")], json).into_response()) Ok(([(header::CONTENT_TYPE, "application/vnd.pkg5.info")], json).into_response())
} else { } else {
Err(DepotError::Repo( Err(DepotError::Repo(
libips::repository::RepositoryError::PublisherNotFound(publisher), libips::repository::RepositoryError::PublisherNotFound(name),
)) ))
} }
} else {
// Return all publishers
let publishers = repo_info
.publishers
.into_iter()
.map(|p| P5iPublisherInfo {
alias: None,
name: p.name,
packages: Vec::new(),
repositories: Vec::new(),
})
.collect();
let p5i = P5iFile {
packages: Vec::new(),
publishers,
version: 1,
};
let json =
serde_json::to_string_pretty(&p5i).map_err(|e| DepotError::Server(e.to_string()))?;
Ok(([(header::CONTENT_TYPE, "application/vnd.pkg5.info")], json).into_response())
}
} }

View file

@ -10,6 +10,7 @@ use tower_http::trace::TraceLayer;
pub fn app_router(state: Arc<DepotRepo>) -> Router { pub fn app_router(state: Arc<DepotRepo>) -> Router {
Router::new() Router::new()
.route("/versions/0", get(versions::get_versions))
.route("/versions/0/", get(versions::get_versions)) .route("/versions/0/", get(versions::get_versions))
.route( .route(
"/{publisher}/catalog/1/{filename}", "/{publisher}/catalog/1/{filename}",
@ -37,7 +38,13 @@ pub fn app_router(state: Arc<DepotRepo>) -> Router {
) )
.route("/{publisher}/info/0/{fmri}", get(info::get_info)) .route("/{publisher}/info/0/{fmri}", get(info::get_info))
.route("/{publisher}/publisher/0", get(publisher::get_publisher_v0)) .route("/{publisher}/publisher/0", get(publisher::get_publisher_v0))
.route("/{publisher}/publisher/0/", get(publisher::get_publisher_v0))
.route("/{publisher}/publisher/1", get(publisher::get_publisher_v1)) .route("/{publisher}/publisher/1", get(publisher::get_publisher_v1))
.route("/{publisher}/publisher/1/", get(publisher::get_publisher_v1))
.route("/publisher/0", get(publisher::get_default_publisher_v0))
.route("/publisher/0/", get(publisher::get_default_publisher_v0))
.route("/publisher/1", get(publisher::get_default_publisher_v1))
.route("/publisher/1/", get(publisher::get_default_publisher_v1))
.route("/{publisher}/search/0/{token}", get(search::get_search_v0)) .route("/{publisher}/search/0/{token}", get(search::get_search_v0))
.route("/{publisher}/search/1/{token}", get(search::get_search_v1)) .route("/{publisher}/search/1/{token}", get(search::get_search_v1))
// Admin API over HTTP // Admin API over HTTP

View file

@ -105,7 +105,7 @@ async fn test_depot_server() {
.unwrap(); .unwrap();
assert!(resp.status().is_success()); assert!(resp.status().is_success());
let text = resp.text().await.unwrap(); let text = resp.text().await.unwrap();
assert!(text.contains("pkg-server pkg6depotd-0.5.1")); assert!(text.contains("pkg-server pkg6depotd-"));
assert!(text.contains("catalog 1")); assert!(text.contains("catalog 1"));
assert!(text.contains("manifest 0 1")); assert!(text.contains("manifest 0 1"));
@ -158,6 +158,12 @@ async fn test_depot_server() {
let pub_url = format!("{}/test/publisher/1", base_url); let pub_url = format!("{}/test/publisher/1", base_url);
let resp = client.get(&pub_url).send().await.unwrap(); let resp = client.get(&pub_url).send().await.unwrap();
assert!(resp.status().is_success()); assert!(resp.status().is_success());
// Test Publisher v1 with trailing slash
let pub_url_slash = format!("{}/test/publisher/1/", base_url);
let resp = client.get(&pub_url_slash).send().await.unwrap();
assert!(resp.status().is_success());
assert!( assert!(
resp.headers() resp.headers()
.get("content-type") .get("content-type")
@ -170,6 +176,36 @@ async fn test_depot_server() {
assert_eq!(pub_json["version"], 1); assert_eq!(pub_json["version"], 1);
assert_eq!(pub_json["publishers"][0]["name"], "test"); assert_eq!(pub_json["publishers"][0]["name"], "test");
// Test Default Publisher Route v1
let def_pub_url = format!("{}/publisher/1", base_url);
let resp = client.get(&def_pub_url).send().await.unwrap();
assert!(resp.status().is_success());
// Test Default Publisher Route v1 with trailing slash
let def_pub_url_slash = format!("{}/publisher/1/", base_url);
let resp = client.get(&def_pub_url_slash).send().await.unwrap();
assert!(resp.status().is_success());
let pub_json: serde_json::Value = resp.json().await.unwrap();
// In current implementation it returns one publisher.
// We want it to return all publishers.
assert_eq!(pub_json["publishers"].as_array().unwrap().len(), 1);
assert_eq!(pub_json["publishers"][0]["name"], "test");
// Test Default Publisher Route v0
let def_pub_url_v0 = format!("{}/publisher/0", base_url);
let resp = client.get(&def_pub_url_v0).send().await.unwrap();
assert!(resp.status().is_success());
// Test Default Publisher Route v0 with trailing slash
let def_pub_url_v0_slash = format!("{}/publisher/0/", base_url);
let resp = client.get(&def_pub_url_v0_slash).send().await.unwrap();
assert!(resp.status().is_success());
let pub_json: serde_json::Value = resp.json().await.unwrap();
assert_eq!(pub_json["publishers"].as_array().unwrap().len(), 1);
assert_eq!(pub_json["publishers"][0]["name"], "test");
// 6. Test File // 6. Test File
// We assume file exists if manifest works. // We assume file exists if manifest works.
} }
@ -385,3 +421,62 @@ async fn test_file_url_without_algo() {
); );
let _content = resp.text().await.unwrap(); let _content = resp.text().await.unwrap();
} }
#[tokio::test]
async fn test_multiple_publishers_default_route() {
let temp_dir = TempDir::new().unwrap();
let repo_path = temp_dir.path().join("repo_multi");
let mut backend = FileBackend::create(&repo_path, RepositoryVersion::V4).unwrap();
backend.add_publisher("pub1").unwrap();
backend.add_publisher("pub2").unwrap();
let config = Config {
server: ServerConfig {
bind: vec!["127.0.0.1:0".to_string()],
workers: None,
max_connections: None,
reuseport: None,
cache_max_age: Some(3600),
tls_cert: None,
tls_key: None,
},
repository: RepositoryConfig {
root: repo_path.clone(),
mode: Some("readonly".to_string()),
},
telemetry: None,
publishers: None,
admin: None,
oauth2: None,
};
let repo = DepotRepo::new(&config).unwrap();
let state = Arc::new(repo);
let router = http::routes::app_router(state);
let listener = TcpListener::bind("127.0.0.1:0").await.unwrap();
let addr = listener.local_addr().unwrap();
tokio::spawn(async move {
http::server::run(router, listener).await.unwrap();
});
let client = reqwest::Client::new();
let base_url = format!("http://{}", addr);
let def_pub_url = format!("{}/publisher/0", base_url);
let resp = client.get(&def_pub_url).send().await.unwrap();
assert!(resp.status().is_success());
let pub_json: serde_json::Value = resp.json().await.unwrap();
let pubs = pub_json["publishers"].as_array().unwrap();
// CURRENT BEHAVIOR: returns 1
// DESIRED BEHAVIOR: returns 2
assert_eq!(pubs.len(), 2, "Should return all publishers");
let names: Vec<String> = pubs.iter().map(|p| p["name"].as_str().unwrap().to_string()).collect();
assert!(names.contains(&"pub1".to_string()));
assert!(names.contains(&"pub2".to_string()));
}

79
testing/manual/README.md Normal file
View file

@ -0,0 +1,79 @@
# Manual Testing Setup for pkg6depotd
This directory contains scripts and configurations for manual testing of `pkg6depotd` using `anyvm` with OpenIndiana and OmniOS.
## Overview
The goal is to test `pkg6depotd` as a server for the standard Python `pkg` client running inside an illumos VM.
1. **Host**: Runs `pkg6depotd` serving a local repository.
2. **VM**: Runs OpenIndiana or OmniOS and uses `pkg` to communicate with the host.
## Prerequisites
- `~/bin/anyvm.py` script (automated QEMU VM launcher).
- Rust toolchain installed on the host.
## Step-by-Step Instructions
### 1. Start the VM
Choose either OpenIndiana or OmniOS. Use the `anyvm.py` script located in `~/bin/`.
```bash
# For OpenIndiana
python3 ~/bin/anyvm.py --os openindiana --release 202510 -v $(pwd):/root/ips
# For OmniOS
python3 ~/bin/anyvm.py --os omnios --release r151056 -v $(pwd):/root/ips
```
You can add `--ssh-port 2222` if you want a fixed SSH port. `anyvm.py` will display the SSH command to use.
### 2. Fetch a sample repository inside the VM
Once the VM is running, SSH into it and run the `fetch_repo.sh` script to create a small local repository.
Since we mounted the project root to `/root/ips`, you can fetch the repository directly into that mount to make it immediately available on the host.
```bash
# From the host (replace <port> with the one assigned by anyvm)
ssh -p <port> root@localhost
# Inside the VM
cd /root/ips
./testing/manual/fetch_repo.sh https://pkg.openindiana.org/hipster ./test_repo
```
This will create a repository at `./test_repo` inside the VM (which is also visible on the host) containing a few packages.
### 3. Run pkg6depotd on the host
Now that the repository is available on the host, you can run `pkg6depotd`.
```bash
./testing/manual/run_depotd.sh ./test_repo
```
The server will start on `0.0.0.0:8080`.
### 4. Test with the pkg client inside the VM
Back inside the VM, point the `pkg` client to the host's `pkg6depotd`.
In QEMU's default user networking, the host is reachable at `10.0.2.2`.
```bash
# Inside the VM
# 1. Add the publisher
pkg set-publisher -g http://10.0.2.2:8080 test
# 2. List packages from the new publisher
pkg list -v -p test
# 3. Try to install a package (if available in the fetched subset)
pkg install library/zlib
```
## Troubleshooting
- **Connection issues**: Ensure `pkg6depotd` is binding to an address reachable from the VM (e.g., `0.0.0.0` or the host's bridge IP).
- **Missing packages**: Ensure the packages you are trying to install were included in the `fetch_repo.sh` call.

36
testing/manual/fetch_repo.sh Executable file
View file

@ -0,0 +1,36 @@
#!/usr/bin/env bash
# Script to fetch a package repository copy using pkgrecv.
# This script is intended to be run inside an OpenIndiana or OmniOS VM.
set -e
SOURCE_URL="${1:-https://pkg.openindiana.org/hipster}"
DEST_DIR="${2:-/var/share/pkg_repo}"
# Default to a small set of packages for testing if none specified
# 'entire' or '*' can be used to fetch more/all packages, but be aware of size.
shift 2 || true
PACKAGES=("$@")
if [ ${#PACKAGES[@]} -eq 0 ]; then
echo "No packages specified, fetching a small set for testing..."
PACKAGES=("library/zlib" "system/library")
fi
echo "Source: $SOURCE_URL"
echo "Destination: $DEST_DIR"
echo "Packages: ${PACKAGES[*]}"
if [ ! -d "$DEST_DIR" ]; then
echo "Creating repository at $DEST_DIR..."
mkdir -p "$DEST_DIR"
pkgrepo create "$DEST_DIR"
# We'll set a generic prefix, or use the one from source if we wanted to be more fancy
pkgrepo set -s "$DEST_DIR" publisher/prefix=openindiana.org
fi
pkgrecv -s "$SOURCE_URL" -d "$DEST_DIR" "${PACKAGES[@]}" --newest
echo "Repository fetch complete."
echo "You can now sync $DEST_DIR to your host to use with pkg6depotd."

38
testing/manual/run_depotd.sh Executable file
View file

@ -0,0 +1,38 @@
#!/usr/bin/env bash
# Script to build and run pkg6depotd for manual testing.
set -e
REPO_ROOT="${1:-/tmp/pkg_repo}"
echo "Building pkg6depotd..."
cargo build -p pkg6depotd
if [ ! -d "$REPO_ROOT" ]; then
echo "Warning: Repository root $REPO_ROOT does not exist."
echo "You might want to fetch a repository first using fetch_repo.sh inside a VM"
echo "and then sync it to this path."
fi
# Create a temporary config file based on the one in the root but with the correct repo path
CONFIG_FILE="/tmp/pkg6depotd_test.kdl"
cat > "$CONFIG_FILE" <<EOF
server {
bind "0.0.0.0:8080"
workers 4
}
repository {
root "$REPO_ROOT"
mode "readonly"
}
telemetry {
service-name "pkg6depotd"
log-format "json"
}
EOF
echo "Starting pkg6depotd with config $CONFIG_FILE..."
RUST_LOG=debug ./target/debug/pkg6depotd -c "$CONFIG_FILE" start