Skip to content
Merged
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
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ jobs:
options: "--privileged --pid=host -v /var/tmp:/var/tmp --tmpfs /tmp:rw,exec,nosuid,nodev -v /:/run/host"

steps:
- run: dnf -y install cargo clippy composefs-devel e2fsprogs just rustfmt gcc-c++
- run: dnf -y install cargo clippy composefs-devel e2fsprogs just ostree rustfmt gcc-c++
- name: Enable fs-verity on /
run: tune2fs -O verity $(findmnt -vno SOURCE /run/host)
- uses: actions/checkout@v7
Expand All @@ -53,6 +53,8 @@ jobs:
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- name: Install ostree
run: sudo apt-get update && sudo apt-get install -y ostree
- run: just test-integration

# Fuzz smoke test — runs each fuzz target briefly to catch panics
Expand Down Expand Up @@ -123,6 +125,9 @@ jobs:

- uses: Swatinem/rust-cache@v2

- name: Install ostree
run: sudo apt-get update && sudo apt-get install -y ostree

- name: Run integration tests (unprivileged + privileged via VM)
run: just test-integration-vm

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ default-members = [
"crates/composefs-http",
"crates/composefs-ioctls",
"crates/composefs-oci",
"crates/composefs-ostree",
"crates/composefs-setup-root",
"crates/composefs-storage",
"crates/composefs-erofs-debug",
Expand Down Expand Up @@ -38,6 +39,7 @@ composefs-ioctls = { version = "0.7.0", path = "crates/composefs-ioctls", defaul
composefs-oci = { version = "0.7.0", path = "crates/composefs-oci", default-features = false }
composefs-boot = { version = "0.7.0", path = "crates/composefs-boot", default-features = false }
composefs-http = { version = "0.7.0", path = "crates/composefs-http", default-features = false }
composefs-ostree = { version = "0.7.0", path = "crates/composefs-ostree", default-features = false }
cap-std-ext = "5.1.2"
ocidir = "0.7.2"

Expand Down
4 changes: 2 additions & 2 deletions contrib/packaging/install-test-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ set -euo pipefail

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

# OSTree symlink targets — /root, /home, /srv, etc. are symlinks
# into /var on OSTree systems, so the target directories must exist.
Expand Down
4 changes: 3 additions & 1 deletion crates/composefs-ctl/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ name = "cfsctl"
path = "src/main.rs"

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

Expand All @@ -34,6 +35,7 @@ composefs-boot = { workspace = true }
composefs-oci = { workspace = true, optional = true, features = ["boot"] }
composefs-http = { workspace = true, optional = true }
cstorage = { package = "composefs-storage", path = "../composefs-storage", version = "0.7.0", features = ["userns-helper"], optional = true }
composefs-ostree = { workspace = true, 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 }
Expand Down
182 changes: 181 additions & 1 deletion crates/composefs-ctl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ use std::sync::Arc;

use anyhow::{Context as _, Result};
use clap::{Parser, Subcommand, ValueEnum};
#[cfg(feature = "oci")]
#[cfg(any(feature = "oci", feature = "ostree"))]
use comfy_table::{Table, presets::UTF8_FULL};
#[cfg(any(feature = "oci", feature = "http"))]
use indicatif::{MultiProgress, ProgressBar, ProgressStyle};
Expand Down Expand Up @@ -501,6 +501,76 @@ enum OciCommand {
},
}

#[cfg(feature = "ostree")]
#[derive(Debug, Subcommand)]
enum OstreeCommand {
PullLocal {
ostree_repo_path: PathBuf,
/// Ostree ref name or commit ID (64-character hex)
ostree_ref: String,
#[clap(long)]
base_name: Option<String>,
},
Pull {
ostree_repo_url: String,
/// Ostree ref name or commit ID (64-character hex)
ostree_ref: String,
#[clap(long)]
base_name: Option<String>,
},
/// Mount an ostree commit's composefs EROFS at the given mountpoint
Mount {
/// Ostree commit ref or commit ID
commit: String,
/// Target mountpoint
mountpoint: String,
/// Writable upper layer directory for overlayfs
#[arg(long, requires = "workdir")]
upperdir: Option<PathBuf>,
/// Work directory for overlayfs (required with --upperdir)
#[arg(long, requires = "upperdir")]
workdir: Option<PathBuf>,
/// Mount read-write (requires --upperdir)
#[arg(long, requires = "upperdir")]
read_write: bool,
},
/// Dump the filesystem of an ostree commit as a composefs dumpfile to stdout
Dump {
/// Ostree commit ref name
commit_name: String,
},
/// Compute the composefs image ID of an ostree commit
ComputeId {
/// Ostree commit ref name
commit_name: String,
},
/// Show the contents of an ostree commit
Inspect {
/// Ostree ref name, commit ID, or commit ID prefix
source: String,
/// Print only the commit metadata key-value pairs
#[clap(long)]
metadata: bool,
},
/// Tag an ostree commit with a name
///
/// The source can be an ostree commit checksum or an existing ref name.
Tag {
/// Ostree commit checksum (hex) or existing ref name
source: String,
/// Tag name to assign
name: String,
},
/// Remove a named ostree reference
Untag {
/// Tag name to remove
name: String,
},
/// List all ostree commits in the repository
#[clap(name = "images")]
ListCommits,
}

/// Common options for reading a filesystem from a path
#[derive(Debug, Parser)]
struct FsReadOptions {
Expand Down Expand Up @@ -570,6 +640,11 @@ enum Command {
#[clap(subcommand)]
cmd: OciCommand,
},
#[cfg(feature = "ostree")]
Ostree {
#[clap(subcommand)]
cmd: OstreeCommand,
},
/// Mounts a composefs image, possibly enforcing fsverity of the image
Mount {
/// the name of the image to mount, either an fs-verity hash or prefixed with 'ref/'
Expand Down Expand Up @@ -1566,6 +1641,111 @@ where
unreachable!("oci varlink is handled before opening a repository");
}
},
#[cfg(feature = "ostree")]
Command::Ostree { cmd: ostree_cmd } => match ostree_cmd {
OstreeCommand::PullLocal {
ref ostree_repo_path,
ref ostree_ref,
base_name,
} => {
eprintln!("Fetching {ostree_ref}");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't log via eprintln! we have the progress API now.

Also on that topic...I think we should expose a varlink API for this now, right?

I guess neither of these need to strictly block merging though.


🤔 I guess actually...if we go down this varlink path, perhaps in theory we could have both the oci and ostree fetchers be extension binaries i.e. something like /usr/libexec/composefs/ext/oci is automatically cfsctl oci? That could be interesting...and would actually force us to have a good "core" varlink api.

let (verity, stats) = composefs_ostree::pull_local(
&repo,
ostree_repo_path,
ostree_ref,
base_name.as_deref(),
)
.await?;

let image_id = composefs_ostree::get_image_ref(&repo, &stats.commit_id)?;
println!("commit {}", stats.commit_id);
println!("verity {}", verity.to_hex());
println!("image {}", image_id.to_hex());
if !composefs_ostree::is_commit_id(ostree_ref) {
println!("tagged {ostree_ref}");
}
println!(
"objects {} metadata + {} files fetched",
stats.metadata_fetched, stats.files_fetched
);
}
OstreeCommand::Pull {
ref ostree_repo_url,
ref ostree_ref,
base_name,
} => {
eprintln!("Fetching {ostree_ref}");
let (verity, stats) = composefs_ostree::pull(
&repo,
ostree_repo_url,
ostree_ref,
base_name.as_deref(),
)
.await?;

let image_id = composefs_ostree::get_image_ref(&repo, &stats.commit_id)?;
println!("commit {}", stats.commit_id);
println!("verity {}", verity.to_hex());
println!("image {}", image_id.to_hex());
if !composefs_ostree::is_commit_id(ostree_ref) {
println!("tagged {ostree_ref}");
}
println!(
"objects {} metadata + {} files fetched",
stats.metadata_fetched, stats.files_fetched
);
}
OstreeCommand::Mount {
ref commit,
ref mountpoint,
ref upperdir,
ref workdir,
read_write,
} => {
let mount_options =
get_mount_options(upperdir.as_deref(), workdir.as_deref(), read_write)?;
let image_id = composefs_ostree::get_image_ref(&repo, commit)?;
repo.mount_at(&image_id.to_hex(), mountpoint.as_str(), &mount_options)?;
}
OstreeCommand::Dump { ref commit_name } => {
let fs = composefs_ostree::create_filesystem(&repo, commit_name)?;
fs.print_dumpfile()?;
}
OstreeCommand::ComputeId { ref commit_name } => {
let image_id = composefs_ostree::ensure_ostree_erofs(&repo, commit_name)?;
println!("{}", image_id.to_hex());
}
OstreeCommand::Inspect {
ref source,
metadata,
} => {
composefs_ostree::inspect(&repo, source, metadata)?;
}
OstreeCommand::Tag {
ref source,
ref name,
} => {
composefs_ostree::tag(&repo, source, name)?;
println!("Tagged {source} as {name}");
}
OstreeCommand::Untag { ref name } => {
composefs_ostree::untag(&repo, name)?;
}
OstreeCommand::ListCommits => {
let commits = composefs_ostree::list_commits(&repo)?;
if commits.is_empty() {
println!("No ostree commits found");
} else {
let mut table = Table::new();
table.load_preset(UTF8_FULL);
table.set_header(["NAME", "COMMIT"]);
for c in commits {
table.add_row([c.name.as_str(), &c.commit_id]);
}
println!("{table}");
}
}
},
Command::CreateImage {
fs_opts,
ref image_name,
Expand Down
1 change: 1 addition & 0 deletions crates/composefs-integration-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ composefs = { workspace = true }
# Only the test_util module is used — for creating test OCI images.
# All verification must go through the cfsctl CLI.
composefs-oci = { workspace = true, features = ["test", "boot", "containers-storage"] }
composefs-ostree = { workspace = true }
# Used by the varlink tests to drive the service through zlink's native typed
# proxy bindings (alongside the external `varlinkctl` CLI), and to assert on the
# typed wire reply/error types.
Expand Down
1 change: 1 addition & 0 deletions crates/composefs-integration-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,6 @@ pub mod cstor;
pub mod digest_stability;
pub mod oci_compat;
pub mod old_format;
pub mod ostree;
pub mod privileged;
pub mod varlink;
Loading