Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
90532fb
tests: Use realistic file sizes and xattrs in test image layers
cgwalters Apr 7, 2026
d10272c
composefs: Remove unused test imports
cgwalters Apr 2, 2026
a535fb6
bootc: Improve patch target for reliable rebuilds
cgwalters Apr 1, 2026
e58f206
Add cstorage crate for containers-storage access
cgwalters Apr 7, 2026
b840846
repo: Add ensure_object_from_file with reflink/hardlink/copy fallback
cgwalters Apr 7, 2026
d23f3f0
oci: Expand ImportStats for zero-copy tracking and add PullOptions
cgwalters Apr 7, 2026
7bf3951
tests: Add CLI integration tests for tag, untag, GC, and compute-id
cgwalters Apr 7, 2026
1f5d04e
tests: Add test dependencies and integration binary disambiguation
cgwalters Apr 7, 2026
b7f66aa
oci: Add containers-storage integration with zero-copy import
cgwalters Apr 7, 2026
cfcb575
Rework integration testing
cgwalters Apr 8, 2026
d9c8cd3
oci: Add upfront writable check before pulling
cgwalters Apr 3, 2026
b71c814
tests: Add unit SELinux labeling tests
cgwalters Apr 3, 2026
e1828dc
oci: Add bootable variant EROFS generation and tracking
cgwalters Mar 14, 2026
1579757
doc/plans: Update OCI sealing spec (kernel sigs, flattened layers)
cgwalters Feb 12, 2026
6c4d940
WIP: Add OCI image signing, sealing, and signature-verified mount
cgwalters Mar 13, 2026
63d9ff6
oci: Add oci-client-based referrer artifact fetching
cgwalters Mar 15, 2026
a1d203c
cfsctl: Add --require-signature/--trust-cert to oci mount, sealed fie…
cgwalters Mar 15, 2026
bd98355
oci: Rewrite referrer subject.digest when exporting to OCI layout
cgwalters Mar 15, 2026
9a9370c
oci: Add referrers tag scheme fallback for GHCR compatibility
cgwalters Mar 15, 2026
fe83ccb
oci: Register referrers against the local manifest digest, not the re…
cgwalters Mar 15, 2026
9558b57
ci: Add workflow to build and push cfsctl as OCI artifact
cgwalters Mar 15, 2026
e69f941
ci: Build cfsctl with rhel9 feature for CentOS Stream 10 compatibility
cgwalters Mar 15, 2026
fa979f5
deps: Upgrade ocidir 0.6 -> 0.7, unify oci-spec on 0.8
cgwalters Apr 10, 2026
40f2426
cfsctl: Replace process::exit(1) with anyhow::bail in verify
cgwalters Apr 10, 2026
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
16 changes: 13 additions & 3 deletions .config/nextest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ failure-output = "immediate"
success-output = "never"
status-level = "pass"

# Tests that pull OCI images need more time (especially on cold CI runners)
[[profile.default.overrides]]
filter = 'test(/digest_stability|oci_pull/)'
slow-timeout = { period = "300s", terminate-after = 2 }

# Profile for integration tests — run with limited parallelism due to QEMU/KVM resources
[profile.integration]
test-threads = 2
Expand All @@ -30,7 +35,12 @@ path = "junit.xml"
store-success-output = true
store-failure-output = true

# VM tests boot an ephemeral QEMU instance per test, limit parallelism
# Privileged tests boot an ephemeral QEMU instance per test — limit
# parallelism to avoid OOM kills on 16 GB CI runners.
# Requiring all 2 threads effectively serialises them.
#
# NOTE: use /regex/ syntax, not ~substring — the ~ operator treats ^ $ as
# literal characters, so ~^foo never matches anything.
[[profile.integration.overrides]]
filter = 'test(~^vm_)'
threads-required = 4
filter = 'test(/^privileged_/)'
threads-required = 2
51 changes: 51 additions & 0 deletions .github/workflows/build-cfsctl-artifact.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Build cfsctl OCI artifact

on:
push:
branches: ['**']
workflow_dispatch:

permissions:
contents: read
packages: write

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

env:
CARGO_TERM_COLOR: always

jobs:
build-and-push:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2

- name: Build cfsctl (release)
run: cargo build --release -p cfsctl --features composefs/rhel9,composefs-oci/oci-client

- name: Install oras CLI
run: |
curl -sLO https://github.com/oras-project/oras/releases/download/v1.3.1/oras_1.3.1_linux_amd64.tar.gz
tar xzf oras_1.3.1_linux_amd64.tar.gz -C /usr/local/bin oras
oras version

- name: Login to GHCR
run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin

- name: Push OCI artifact
working-directory: target/release
run: |
set -eux
oras push ghcr.io/${GITHUB_REPOSITORY}/cfsctl:${GITHUB_REF_NAME} \
--artifact-type application/vnd.composefs.cfsctl.v1 \
cfsctl:application/octet-stream
SHA_SHORT=$(echo "${GITHUB_SHA}" | head -c 12)
oras push "ghcr.io/${GITHUB_REPOSITORY}/cfsctl:${SHA_SHORT}" \
--artifact-type application/vnd.composefs.cfsctl.v1 \
cfsctl:application/octet-stream
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ jobs:
- uses: actions/checkout@v6
- uses: bootc-dev/actions/bootc-ubuntu-setup@main
- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest
- uses: Swatinem/rust-cache@v2
- run: just test-integration

Expand Down Expand Up @@ -114,6 +115,7 @@ jobs:
libvirt: true

- uses: dtolnay/rust-toolchain@stable
- uses: taiki-e/install-action@nextest

- uses: Swatinem/rust-cache@v2

Expand Down
19 changes: 19 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
[workspace]
members = ["crates/*"]
# Exclude integration-tests from default `cargo test` — those require a
# built cfsctl binary and (for privileged tests) a VM. Run them via
# `just test-integration` or `just test-integration-vm` instead.
default-members = [
"crates/cfsctl",
"crates/composefs",
"crates/composefs-boot",
"crates/composefs-fuse",
"crates/composefs-http",
"crates/composefs-ioctls",
"crates/composefs-oci",
"crates/composefs-setup-root",
"crates/cstorage",
"crates/erofs-debug",
"crates/splitfdstream",
]
resolver = "2"

[workspace.package]
Expand All @@ -23,6 +39,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 = { version = "0.1.0", default-features = false }

[profile.dev.package.sha2]
# this is *really* slow otherwise
opt-level = 3
Expand Down
31 changes: 25 additions & 6 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,37 @@ cfsctl_features := env("COMPOSEFS_CFSCTL_FEATURES", "pre-6.15")
_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_
# Prefers nextest for parallelism control and better UX; falls back to direct harness.
test-integration *ARGS: build
#!/usr/bin/env bash
set -euo pipefail
export CFSCTL_PATH=$(pwd)/target/debug/cfsctl
if command -v cargo-nextest &> /dev/null; then
cargo nextest run -p integration-tests -E 'not test(/^privileged_/)' {{ ARGS }}
else
cargo test -p integration-tests --test cfsctl-integration-tests -- --skip privileged_ {{ ARGS }}
fi

# Build the test container image for VM-based integration tests
_integration-container-build:
podman build --build-arg base_image={{base_image}} --build-arg cfsctl_features={{cfsctl_features}} -t {{_test_image}} .

# Run all integration tests including privileged VM tests (requires podman + libvirt)
test-integration-vm: build _integration-container-build
COMPOSEFS_TEST_IMAGE={{_test_image}} \
CFSCTL_PATH=$(pwd)/target/debug/cfsctl \
cargo run -p integration-tests
# Uses nextest with the integration profile for parallelism control of VM tests.
test-integration-vm *ARGS: build _integration-container-build
#!/usr/bin/env bash
set -euo pipefail
export COMPOSEFS_TEST_IMAGE={{_test_image}}
export CFSCTL_PATH=$(pwd)/target/debug/cfsctl
if command -v cargo-nextest &> /dev/null; then
cargo nextest run -P integration -p integration-tests {{ ARGS }}
else
cargo test -p integration-tests --test cfsctl-integration-tests -- {{ ARGS }}
fi

# Install cargo-nextest if not already installed
install-nextest:
@which cargo-nextest > /dev/null 2>&1 || cargo install cargo-nextest --locked

# Run everything: checks + full integration tests including VM
ci: check test-integration-vm
Expand Down
27 changes: 17 additions & 10 deletions bootc/Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -45,20 +45,22 @@ clone:
# `rev` (done when the bootc branch is updated) busts the podman build
# cache. This recipe just adds a [patch] override so Cargo resolves from
# the bind-mounted local source instead of fetching from git.
#
# Errors if the composefs-rs tree has uncommitted changes.
patch: clone
#!/bin/bash
set -euo pipefail
cd "$COMPOSEFS_BOOTC_PATH"

cfs_path="$_COMPOSEFS_SRC/crates/cfsctl"

# Check if already patched (idempotent)
if grep -q 'Patched by composefs-rs' Cargo.toml 2>/dev/null; then
echo "bootc already patched for composefs-rs"
exit 0
# Require a clean composefs-rs working tree so we test a real commit
if ! git -C "$_COMPOSEFS_SRC" diff --quiet HEAD 2>/dev/null; then
echo "error: composefs-rs has uncommitted changes — commit or stash first" >&2
git -C "$_COMPOSEFS_SRC" status --short >&2
exit 1
fi

echo "Patching bootc Cargo.toml to use $_COMPOSEFS_SRC"
cfs_path="$_COMPOSEFS_SRC/crates/cfsctl"

cd "$COMPOSEFS_BOOTC_PATH"

# Add or update the [patch] section with a path override
patch_value="cfsctl = { path = \"${cfs_path}\" } # Patched by composefs-rs"
Expand All @@ -85,7 +87,12 @@ patch: clone
# We intentionally don't run `cargo update` here because it rewrites
# the workspace dependency line in Cargo.toml (replacing git+rev with path).

echo "bootc patched successfully"
# Update the rev comment in the [patch] section so Cargo.toml actually
# changes when composefs-rs moves to a new commit. Since the file is
# part of the podman build context this busts the layer cache.
_rev=$(git -C "$_COMPOSEFS_SRC" rev-parse HEAD)
sed -i "s/^# Patched by composefs-rs.*/# Patched by composefs-rs at ${_rev}/" Cargo.toml
echo "bootc patched for composefs-rs at ${_rev}"

# Build sealed bootc image using local composefs-rs
# The path dependency is auto-detected and bind-mounted by bootc's Justfile
Expand Down Expand Up @@ -158,6 +165,6 @@ config:

Example Usage:
just bootc/build # Clone main, patch, and build
COMPOSEFS_BOOTC_REF=v1.2.0 just bootc/build # Use specific tag
COMPOSEFS_BOOTC_REF=v1.0.0 just bootc/build # Use specific tag
COMPOSEFS_BOOTC_REF=refs/pull/1791/head just bootc/build # Use PR
EOF
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 xfsprogs
;;
debian|ubuntu)
pkg_install \
openssl e2fsprogs bubblewrap openssh-server
openssl e2fsprogs bubblewrap openssh-server fsverity-utils \
podman skopeo

# OSTree symlink targets — /root, /home, /srv, etc. are symlinks
# into /var on OSTree systems, so the target directories must exist.
Expand Down
8 changes: 6 additions & 2 deletions 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']
oci = ['composefs-oci', 'oci-spec']
containers-storage = ['composefs-oci/containers-storage', 'cstorage']
rhel9 = ['composefs/rhel9']
'pre-6.15' = ['composefs/pre-6.15']

Expand All @@ -29,8 +30,11 @@ composefs = { workspace = true }
composefs-boot = { workspace = true }
composefs-oci = { workspace = true, optional = true, features = ["boot"] }
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 }
oci-spec = { version = "0.8.0", default-features = false, optional = true }
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
Loading
Loading