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
18 changes: 15 additions & 3 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,11 @@ _test_image := if base_image =~ "debian" { "localhost/composefs-rs-test-debian:l

# 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 -- {{ ARGS }}
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 -- --skip privileged_
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 @@ -55,7 +55,19 @@ integration-container-build:
integration-container: 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

# Clean build artifacts
clean:
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_json = { version = "1.0", default-features = false, features = ["std"] }
tokio = { version = "1.24.2", default-features = false, features = ["io-std", "io-util"] }
Expand Down
64 changes: 48 additions & 16 deletions crates/cfsctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,26 @@ pub use composefs_http;
#[cfg(feature = "oci")]
pub use composefs_oci;

use std::{ffi::OsString, path::PathBuf};

#[cfg(feature = "oci")]
use std::{
ffi::OsString,
fs::create_dir_all,
io::{IsTerminal, Read},
path::{Path, PathBuf},
sync::Arc,
path::Path,
};

#[cfg(any(feature = "oci", feature = "http"))]
use std::sync::Arc;

use anyhow::Result;
use clap::{Parser, Subcommand, ValueEnum};
#[cfg(feature = "oci")]
use comfy_table::{presets::UTF8_FULL, Table};

use rustix::fs::CWD;

#[cfg(feature = "oci")]
use composefs_boot::{write_boot, BootOps};

use composefs::{
Expand Down Expand Up @@ -336,6 +342,7 @@ where
}
}

#[cfg(feature = "oci")]
fn verity_opt<ObjectID>(opt: &Option<String>) -> Result<Option<ObjectID>>
where
ObjectID: FsVerityHashValue,
Expand Down Expand Up @@ -511,20 +518,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