Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ composefs-oci = { version = "0.3.0", path = "crates/composefs-oci", default-feat
composefs-boot = { version = "0.3.0", path = "crates/composefs-boot", default-features = false }
composefs-http = { version = "0.3.0", path = "crates/composefs-http", default-features = false }

# JSON-RPC with FD passing for userns helper
jsonrpc-fdpass = { git = "https://github.com/cgwalters/jsonrpc-fdpass", rev = "b30fa1d" }

[profile.dev.package.sha2]
# this is *really* slow otherwise
opt-level = 3
Expand Down
24 changes: 20 additions & 4 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,13 @@ cfsctl_features := env("COMPOSEFS_CFSCTL_FEATURES", "pre-6.15")
# Derive test image name from base_image
_test_image := if base_image =~ "debian" { "localhost/composefs-rs-test-debian:latest" } else if base_image =~ "stream9" { "localhost/composefs-rs-test-c9s:latest" } else { "localhost/composefs-rs-test:latest" }

# Run unprivileged integration tests against the cfsctl binary (no root, no VM)
test-integration: build
CFSCTL_PATH=$(pwd)/target/debug/cfsctl cargo run -p integration-tests -- --skip privileged_
# Run integration tests (builds cfsctl first); pass extra args to the harness
test-integration *ARGS: build
CFSCTL_PATH=$(pwd)/target/debug/cfsctl cargo run -p integration-tests --bin cfsctl-integration-tests -- {{ ARGS }}

# Run only the fast unprivileged integration tests (no root, no VM)
integration-unprivileged: build
CFSCTL_PATH=$(pwd)/target/debug/cfsctl cargo run -p integration-tests --bin cfsctl-integration-tests -- --skip privileged_

# Build the test container image for VM-based integration tests
_integration-container-build:
Expand All @@ -63,7 +67,19 @@ _integration-container-build:
test-integration-vm: build _integration-container-build
COMPOSEFS_TEST_IMAGE={{_test_image}} \
CFSCTL_PATH=$(pwd)/target/debug/cfsctl \
cargo run -p integration-tests
cargo run -p integration-tests --bin cfsctl-integration-tests

# Run all tests with all features enabled
test-all:
cargo test --workspace --all-features

# Build with containers-storage feature
build-cstorage:
cargo build --workspace --features containers-storage

# Run integration tests (requires podman and skopeo)
integration-test: build-release
CFSCTL_PATH=$(pwd)/target/release/cfsctl cargo run --release -p integration-tests --bin cfsctl-integration-tests

# Run everything: checks + full integration tests including VM
ci: check test-integration-vm
Expand Down
5 changes: 3 additions & 2 deletions contrib/packaging/install-test-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ set -euo pipefail

case "${ID}" in
centos|fedora|rhel)
pkg_install composefs openssl
pkg_install composefs openssl podman skopeo
;;
debian|ubuntu)
pkg_install \
openssl e2fsprogs bubblewrap openssh-server
openssl e2fsprogs bubblewrap openssh-server \
podman skopeo

# OSTree symlink targets — /root, /home, /srv, etc. are symlinks
# into /var on OSTree systems, so the target directories must exist.
Expand Down
5 changes: 4 additions & 1 deletion crates/cfsctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ version.workspace = true
path = "src/lib.rs"

[features]
default = ['pre-6.15', 'oci']
default = ['pre-6.15', 'oci', 'containers-storage']
http = ['composefs-http']
oci = ['composefs-oci']
containers-storage = ['composefs-oci/containers-storage', 'cstorage']
rhel9 = ['composefs/rhel9']
'pre-6.15' = ['composefs/pre-6.15']

Expand All @@ -29,8 +30,10 @@ composefs = { workspace = true }
composefs-boot = { workspace = true }
composefs-oci = { workspace = true, optional = true }
composefs-http = { workspace = true, optional = true }
cstorage = { path = "../cstorage", version = "0.3.0", features = ["userns-helper"], optional = true }
env_logger = { version = "0.11.0", default-features = false }
hex = { version = "0.4.0", default-features = false }
indicatif = { version = "0.17.0", default-features = false }
rustix = { version = "1.0.0", default-features = false, features = ["fs", "process"] }
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde_json = { version = "1.0", default-features = false, features = ["std"] }
Expand Down
51 changes: 38 additions & 13 deletions crates/cfsctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -558,20 +558,45 @@ where
OciCommand::Pull { ref image, name } => {
// If no explicit name provided, use the image reference as the tag
let tag_name = name.as_deref().unwrap_or(image);
let (result, stats) =
composefs_oci::pull_image(&Arc::new(repo), image, Some(tag_name), None).await?;
let repo = Arc::new(repo);

println!("manifest {}", result.manifest_digest);
println!("config {}", result.config_digest);
println!("verity {}", result.manifest_verity.to_hex());
println!("tagged {tag_name}");
println!(
"objects {} copied, {} already present, {} bytes copied, {} bytes inlined",
stats.objects_copied,
stats.objects_already_present,
stats.bytes_copied,
stats.bytes_inlined,
);
// Check if this is a containers-storage import
#[cfg(feature = "containers-storage")]
let is_cstor = composefs_oci::cstor::parse_containers_storage_ref(image).is_some();
#[cfg(not(feature = "containers-storage"))]
let is_cstor = false;

if is_cstor {
// Use unified pull which handles containers-storage routing
let result = composefs_oci::pull(&repo, image, Some(tag_name), None).await?;

println!("config {}", result.config_digest);
println!("verity {}", result.config_verity.to_hex());
println!("tagged {tag_name}");
println!(
"objects {} copied, {} already present, {} bytes copied, {} bytes inlined",
result.stats.objects_copied,
result.stats.objects_already_present,
result.stats.bytes_copied,
result.stats.bytes_inlined,
);
} else {
// Use the normal skopeo-based pull which produces full manifest info
let (result, stats) =
composefs_oci::pull_image(&repo, image, Some(tag_name), None).await?;

println!("manifest {}", result.manifest_digest);
println!("config {}", result.config_digest);
println!("verity {}", result.manifest_verity.to_hex());
println!("tagged {tag_name}");
println!(
"objects {} copied, {} already present, {} bytes copied, {} bytes inlined",
stats.objects_copied,
stats.objects_already_present,
stats.bytes_copied,
stats.bytes_inlined,
);
}
}
OciCommand::ListImages { json } => {
let images = composefs_oci::oci_image::list_images(&repo)?;
Expand Down
16 changes: 14 additions & 2 deletions crates/cfsctl/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,20 @@ use anyhow::Result;
use clap::Parser;
use composefs::fsverity::{Sha256HashValue, Sha512HashValue};

#[tokio::main]
async fn main() -> Result<()> {
fn main() -> Result<()> {
// If we were spawned as a userns helper process, handle that and exit.
// This MUST be called before the tokio runtime is created.
#[cfg(feature = "containers-storage")]
cstorage::init_if_helper();

// Now we can create the tokio runtime for the main application
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(async_main())
}

async fn async_main() -> Result<()> {
env_logger::init();

let args = App::parse();
Expand Down
6 changes: 6 additions & 0 deletions crates/composefs-oci/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ repository.workspace = true
rust-version.workspace = true
version.workspace = true

[features]
default = []
containers-storage = ["dep:cstorage", "dep:base64", "cstorage/userns-helper"]

[dependencies]
anyhow = { version = "1.0.87", default-features = false }
fn-error-context = "0.2"
async-compression = { version = "0.4.0", default-features = false, features = ["tokio", "zstd", "gzip"] }
base64 = { version = "0.22", default-features = false, features = ["std"], optional = true }
bytes = { version = "1", default-features = false }
composefs = { workspace = true }
containers-image-proxy = { version = "0.9.2", default-features = false }
cstorage = { path = "../cstorage", version = "0.3.0", optional = true }
hex = { version = "0.4.0", default-features = false }
indicatif = { version = "0.17.0", default-features = false, features = ["tokio"] }
oci-spec = { version = "0.8.0", default-features = false }
Expand Down
Loading
Loading