diff --git a/.containerignore b/.containerignore index 3db8a1e8..ae4cbfca 100644 --- a/.containerignore +++ b/.containerignore @@ -3,3 +3,4 @@ !Cargo.toml !Cargo.lock !crates/ +!contrib/ diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index edde9fae..f8707110 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -8,7 +8,6 @@ on: env: CARGO_TERM_COLOR: always - COMPOSEFS_TEST_IMAGE: localhost/composefs-rs-test:latest concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -18,13 +17,42 @@ permissions: contents: read jobs: + # Fast smoke test — catches basic breakage before spending time on + # container builds and VM boots. Runs only the unprivileged tests + # directly on the runner (no container image, no root required). + smoke: + name: Unprivileged smoke test + runs-on: ubuntu-24.04 + timeout-minutes: 15 + steps: + - uses: actions/checkout@v5 + - uses: extractions/setup-just@v2 + - uses: dtolnay/rust-toolchain@stable + - uses: Swatinem/rust-cache@v2 + - run: just integration-unprivileged + + # Full integration tests: builds a bootc container image, runs all + # tests (both unprivileged and privileged). Privileged tests execute + # inside bcvk ephemeral VMs booted from the container image. integration: - name: Integration tests + name: Integration tests (${{ matrix.name }}) + needs: smoke runs-on: ubuntu-24.04 timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + include: + - name: centos + base_image: quay.io/centos-bootc/centos-bootc:stream10 + - name: debian + base_image: ghcr.io/bootcrew/debian-bootc:latest + env: + COMPOSEFS_BASE_IMAGE: ${{ matrix.base_image }} steps: - uses: actions/checkout@v5 + - uses: extractions/setup-just@v2 - name: Setup uses: bootc-dev/actions/bootc-ubuntu-setup@main @@ -35,8 +63,5 @@ jobs: - uses: Swatinem/rust-cache@v2 - - name: Run unprivileged integration tests - run: just integration-unprivileged - - - name: Run privileged integration tests (via bcvk VM) + - name: Run integration tests (unprivileged + privileged via VM) run: just integration-container diff --git a/Containerfile b/Containerfile index c97cf185..59c80c3e 100644 --- a/Containerfile +++ b/Containerfile @@ -1,27 +1,29 @@ -# Containerfile for composefs-rs +# Containerfile for composefs-rs integration testing # # Builds cfsctl and integration test binaries, then produces a bootable # (bootc-compatible) container image suitable for privileged integration # testing via `bcvk ephemeral run-ssh`. # # Build: -# podman build --tag composefs-rs-test -f Containerfile . +# podman build --tag composefs-rs-test . +# podman build --build-arg base_image=ghcr.io/bootcrew/debian-bootc:latest --tag composefs-rs-test-debian . # # Uses BuildKit-style cache mounts for fast incremental Rust builds. +# Note: when switching between base images locally, run +# podman system prune --volumes +# to clear stale build caches that may be incompatible across distros. + +ARG base_image=quay.io/centos-bootc/centos-bootc:stream10 # -- source snapshot (keeps layer graph clean) -- FROM scratch AS src COPY . /src # -- build stage -- -FROM quay.io/centos-bootc/centos-bootc:stream10 AS build +FROM ${base_image} AS build -RUN dnf install -y \ - rust cargo clippy rustfmt \ - openssl-devel \ - gcc \ - composefs \ - && dnf clean all +COPY --from=src /src/contrib /src/contrib +RUN /src/contrib/packaging/install-build-deps.sh COPY --from=src /src /src WORKDIR /src @@ -42,9 +44,10 @@ RUN --network=none \ cp /src/target/release/cfsctl-integration-tests /usr/bin/cfsctl-integration-tests # -- final bootable image -- -FROM quay.io/centos-bootc/centos-bootc:stream10 +FROM ${base_image} -RUN dnf install -y composefs openssl && dnf clean all +COPY --from=src /src/contrib /src/contrib +RUN /src/contrib/packaging/install-test-deps.sh && rm -rf /src COPY --from=build /usr/bin/cfsctl /usr/bin/cfsctl COPY --from=build /usr/bin/cfsctl-integration-tests /usr/bin/cfsctl-integration-tests diff --git a/Justfile b/Justfile index 5e26d253..1be39887 100644 --- a/Justfile +++ b/Justfile @@ -31,7 +31,13 @@ fmt: # Run all checks (clippy + fmt + test) check: clippy fmt-check test -COMPOSEFS_TEST_IMAGE := "localhost/composefs-rs-test:latest" +# Base image for test container builds. +# Override to test on different distros: +# just base_image=ghcr.io/bootcrew/debian-bootc:latest integration-container +base_image := env("COMPOSEFS_BASE_IMAGE", "quay.io/centos-bootc/centos-bootc:stream10") + +# Derive test image name from base_image +_test_image := if base_image =~ "debian" { "localhost/composefs-rs-test-debian:latest" } else { "localhost/composefs-rs-test:latest" } # Run integration tests (builds cfsctl first); pass extra args to the harness test-integration *ARGS: build @@ -43,11 +49,11 @@ integration-unprivileged: build # Build the test container image for VM-based integration tests integration-container-build: - podman build -t {{COMPOSEFS_TEST_IMAGE}} -f Containerfile . + podman build --build-arg base_image={{base_image}} -t {{_test_image}} . # Run all integration tests; privileged tests dispatch to a bcvk VM integration-container: build integration-container-build - COMPOSEFS_TEST_IMAGE={{COMPOSEFS_TEST_IMAGE}} \ + COMPOSEFS_TEST_IMAGE={{_test_image}} \ CFSCTL_PATH=$(pwd)/target/debug/cfsctl \ cargo run -p integration-tests diff --git a/contrib/packaging/install-build-deps.sh b/contrib/packaging/install-build-deps.sh new file mode 100755 index 00000000..c20a2234 --- /dev/null +++ b/contrib/packaging/install-build-deps.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# Install build-time dependencies for composefs-rs. +# +# This script detects the OS via /etc/os-release and installs the +# appropriate compiler toolchain and development libraries needed to +# build cfsctl and the integration test binary. +set -euo pipefail + +# shellcheck source=lib.sh +. "$(dirname "$0")/lib.sh" + +case "${ID}" in + centos|fedora|rhel) + pkg_install \ + rust cargo \ + openssl-devel \ + gcc + ;; + debian|ubuntu) + pkg_install \ + rustc cargo \ + libssl-dev zlib1g-dev pkg-config \ + gcc libc6-dev + + # /var/roothome is needed because /root is an OSTree symlink to + # it, and cargo/rustup need a writable home directory. + mkdir -p /var/roothome + ;; + *) + echo "Unsupported distro: ${ID}" >&2 + exit 1 + ;; +esac diff --git a/contrib/packaging/install-test-deps.sh b/contrib/packaging/install-test-deps.sh new file mode 100755 index 00000000..f414dcd0 --- /dev/null +++ b/contrib/packaging/install-test-deps.sh @@ -0,0 +1,50 @@ +#!/bin/bash +# Install runtime / test dependencies for composefs-rs integration tests. +# +# This runs in the final bootable image and installs everything needed +# for `cfsctl-integration-tests` to run, including SSH and networking +# configuration for bcvk VM-based testing. +set -euo pipefail + +# shellcheck source=lib.sh +. "$(dirname "$0")/lib.sh" + +case "${ID}" in + centos|fedora|rhel) + pkg_install composefs openssl + ;; + debian|ubuntu) + pkg_install \ + openssl e2fsprogs bubblewrap openssh-server + + # OSTree symlink targets — /root, /home, /srv, etc. are symlinks + # into /var on OSTree systems, so the target directories must exist. + mkdir -p /var/roothome /var/home /var/srv /var/opt /var/mnt /var/local + + # Enable systemd-networkd with DHCP so that bcvk VMs get + # network connectivity automatically. + mkdir -p /etc/systemd/network + printf '[Match]\nName=en*\n\n[Network]\nDHCP=yes\n' \ + > /etc/systemd/network/80-vm-dhcp.network + systemctl enable systemd-networkd + + # Configure sshd for bcvk — allow root login with keys and + # relax StrictModes (the OSTree symlink layout confuses the + # ownership checks otherwise). + mkdir -p /etc/ssh/sshd_config.d + printf 'PermitRootLogin prohibit-password\nStrictModes no\n' \ + > /etc/ssh/sshd_config.d/99-bcvk.conf + + # Regenerate initramfs with the systemd-creds module so that + # bcvk can import credentials into the VM at boot. + for kdir in /usr/lib/modules/*/; do + KVER=$(basename "${kdir}") + dracut --force --add "systemd-creds" \ + "/usr/lib/modules/${KVER}/initramfs.img" "${KVER}" + done + ;; + *) + echo "Unsupported distro: ${ID}" >&2 + exit 1 + ;; +esac diff --git a/contrib/packaging/lib.sh b/contrib/packaging/lib.sh new file mode 100644 index 00000000..0dacc394 --- /dev/null +++ b/contrib/packaging/lib.sh @@ -0,0 +1,46 @@ +#!/bin/bash +# Shared helpers for composefs-rs packaging scripts. +# +# Sources /etc/os-release and provides distro-agnostic functions for +# bootstrapping APT on debian-bootc images and installing packages. + +# shellcheck source=/dev/null +. /etc/os-release +export ID + +# debian_apt_init — bootstrap the /var directory structure that APT and +# dpkg need to function. debian-bootc images use OSTree's /var as a +# mutable state partition, so it starts completely empty. This is +# idempotent and safe to call multiple times. +debian_apt_init() { + mkdir -p /var/lib/apt/lists/partial \ + /var/lib/dpkg/info /var/lib/dpkg/updates /var/lib/dpkg/triggers \ + /var/cache/apt/archives/partial \ + /var/log /run/lock + touch /var/lib/dpkg/status /var/lib/dpkg/available +} + +# pkg_install PACKAGE... — install packages using the system package +# manager. On Debian/Ubuntu this handles the full APT bootstrap cycle; +# on Fedora/CentOS/RHEL it uses dnf. +# +# APT::Sandbox::User=root works around setgroups(2) failures that happen +# inside rootless podman builds where the process cannot change groups. +pkg_install() { + case "${ID}" in + centos|fedora|rhel) + dnf install -y "$@" + dnf clean all + ;; + debian|ubuntu) + debian_apt_init + apt-get -o APT::Sandbox::User=root update + apt-get -o APT::Sandbox::User=root install -y --no-install-recommends "$@" + rm -rf /var/lib/apt/lists/* + ;; + *) + echo "Unsupported distro: ${ID}" >&2 + return 1 + ;; + esac +}