Skip to content

Commit 1c64ff6

Browse files
committed
Rework GHA testing: Use bcvk, cover composefs with tmt
Part 1: Use bcvk For local tests, right now testcloud+tmt doesn't support UEFI, see teemtee/tmt#4203 This is a blocker for us doing more testing with UKIs. In this patch we switch to provisioning VMs with bcvk, which fixes this - but beyond that a really compelling thing about this is that bcvk is *also* designed to be ergonomic and efficient beyond just being a test runner, with things like virtiofs mounting of host container storage, etc. In other words, bcvk is the preferred way to run local virt with bootc, and this makes our TMT tests use it. Now a major downside of this though is we're effectively implementing a new "provisioner" for tmt (bypassing the existing `virtual`). In the more medium term I think we want to add `bcvk` as a provisioner option to tmt. Anyways for now, this works by discovers test plans via `tmt plan ls`, spawning a separate VM per test, and then using uses tmt's connect provisioner to run tests targeting these externally provisioned systems. Part 2: Rework the Justfile and Dockerfile This adds `base` and `variant` arguments which are propagated through the system, and we have a new `variant` for sealed composefs. The readonly tests now pass with composefs. Assisted-by: Claude Code (Sonnet 4.5) Signed-off-by: Colin Walters <[email protected]>
1 parent f028c93 commit 1c64ff6

File tree

18 files changed

+837
-173
lines changed

18 files changed

+837
-173
lines changed

.github/workflows/ci.yml

Lines changed: 37 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -141,47 +141,23 @@ jobs:
141141
- name: Install tmt
142142
run: pip install --user "tmt[provision-virtual]"
143143

144-
- name: Build container and disk image
144+
- name: Setup env
145145
run: |
146-
set -xeuo pipefail
147-
build_args=()
148-
# Map from an ID-VERSIONID pair to a container ref
149-
target=${{ matrix.test_os }}
150-
OS_ID=$(echo "$target" | cut -d '-' -f 1)
151-
OS_VERSION_ID=$(echo "$target" | cut -d '-' -f 2)
152-
# Base image
153-
case "$OS_ID" in
154-
"centos")
155-
BASE="quay.io/centos-bootc/centos-bootc:stream${OS_VERSION_ID}"
156-
;;
157-
"fedora")
158-
BASE="quay.io/fedora/fedora-bootc:${OS_VERSION_ID}"
159-
;;
160-
*) echo "Unknown OS: ${OS_ID}" 1>&2; exit 1
161-
;;
162-
esac
163-
build_args+=("--build-arg=base=$BASE")
164-
just build ${build_args[@]}
165-
just build-integration-test-image
166-
# Cross check we're using the right base
167-
used_vid=$(podman run --rm localhost/bootc-integration bash -c '. /usr/lib/os-release && echo $VERSION_ID')
168-
test "$OS_VERSION_ID" = "${used_vid}"
169-
170-
- name: Run container tests
171-
run: |
172-
just test-container
146+
BASE=$(just pullspec-for-os ${{ matrix.test_os }})
147+
echo "BOOTC_base=${BASE}" >> $GITHUB_ENV
173148
174-
- name: Generate disk image
149+
- name: Build container
175150
run: |
176-
mkdir -p target
177-
just build-disk-image localhost/bootc-integration target/bootc-integration-test.qcow2
151+
just build-integration-test-image
152+
# Extra cross-check (duplicating the integration test) that we're using the right base
153+
used_vid=$(podman run --rm localhost/bootc-integration bash -c '. /usr/lib/os-release && echo ${ID}-${VERSION_ID}')
154+
test ${{ matrix.test_os }} = "${used_vid}"
178155
179-
- name: Workaround https://github.com/teemtee/testcloud/issues/18
180-
run: sudo rm -f /usr/bin/chcon && sudo ln -sr /usr/bin/true /usr/bin/chcon
156+
- name: Unit and container integration tests
157+
run: just test-container
181158

182159
- name: Run all TMT tests
183-
run: |
184-
just test-tmt-nobuild
160+
run: just test-tmt
185161

186162
- name: Archive TMT logs
187163
if: always()
@@ -194,7 +170,10 @@ jobs:
194170
strategy:
195171
fail-fast: false
196172
matrix:
173+
# TODO expand this matrix, we need to make it better to override the target
174+
# OS via Justfile variables too
197175
test_os: [centos-10]
176+
variant: [composefs-sealeduki-sdboot]
198177

199178
runs-on: ubuntu-24.04
200179

@@ -204,9 +183,29 @@ jobs:
204183
uses: ./.github/actions/bootc-ubuntu-setup
205184
with:
206185
libvirt: true
186+
- name: Install tmt
187+
run: pip install --user "tmt[provision-virtual]"
188+
189+
- name: Setup env
190+
run: |
191+
BASE=$(just pullspec-for-os ${{ matrix.test_os }})
192+
echo "BOOTC_base=${BASE}" >> $GITHUB_ENV
193+
echo "BOOTC_variant="${{ matrix.variant }} >> $GITHUB_ENV
207194
208195
- name: Build container
209-
run: just build-sealed
196+
run: |
197+
just build-integration-test-image
198+
199+
- name: Unit and container integration tests
200+
run: just test-container
210201

211-
- name: Test
212-
run: just test-composefs
202+
- name: Run readonly TMT tests
203+
# TODO: expand to more tests
204+
run: just test-tmt readonly
205+
206+
- name: Archive TMT logs
207+
if: always()
208+
uses: actions/upload-artifact@v5
209+
with:
210+
name: tmt-log-PR-${{ github.event.number }}-${{ matrix.test_os }}-${{ env.ARCH }}-${{ matrix.tmt_plan }}
211+
path: /var/tmp/tmt

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Dockerfile

Lines changed: 50 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
# Build this project from source and write the updated content
22
# (i.e. /usr/bin/bootc and systemd units) to a new derived container
33
# image. See the `Justfile` for an example
4-
#
5-
# Use e.g. --build-arg=base=quay.io/fedora/fedora-bootc:42 to target
6-
# Fedora instead.
74

5+
# Note this is usually overridden via Justfile
86
ARG base=quay.io/centos-bootc/centos-bootc:stream10
97

108
# This first image captures a snapshot of the source code,
@@ -94,20 +92,60 @@ RUN --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothom
9492

9593
# The final image that derives from the original base and adds the release binaries
9694
FROM base
97-
# Set this to 1 to default to systemd-boot
98-
ARG sdboot=0
95+
# See the Justfile for possible variants
96+
ARG variant
9997
RUN <<EORUN
10098
set -xeuo pipefail
10199
# Ensure we've flushed out prior state (i.e. files no longer shipped from the old version);
102100
# and yes, we may need to go to building an RPM in this Dockerfile by default.
103101
rm -vf /usr/lib/systemd/system/multi-user.target.wants/bootc-*
104-
if test "$sdboot" = 1; then
105-
dnf -y install systemd-boot-unsigned
106-
# And uninstall bootupd
107-
rpm -e bootupd
108-
rm /usr/lib/bootupd/updates -rf
109-
dnf clean all
110-
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
102+
case "${variant}" in
103+
*-sdboot)
104+
dnf -y install systemd-boot-unsigned
105+
# And uninstall bootupd
106+
rpm -e bootupd
107+
rm /usr/lib/bootupd/updates -rf
108+
dnf clean all
109+
rm -rf /var/cache /var/lib/{dnf,rhsm} /var/log/*
110+
;;
111+
esac
112+
EORUN
113+
# Support overriding the rootfs at build time conveniently
114+
ARG rootfs=
115+
RUN <<EORUN
116+
set -xeuo pipefail
117+
# Do we have an explicit build-time override? Then write it.
118+
if test -n "$rootfs"; then
119+
cat > /usr/lib/bootc/install/80-rootfs-override.toml <<EOF
120+
[install.filesystem.root]
121+
type = "$rootfs"
122+
EOF
123+
else
124+
# Query the default rootfs
125+
base_rootfs=$(bootc install print-configuration | jq -r '.filesystem.root.type // ""')
126+
# No filesystem override set. If we're doing composefs, we need a FS that
127+
# supports fsverity. If btrfs is available we'll pick that, otherwise ext4.
128+
fs=
129+
case "${variant}" in
130+
composefs*)
131+
btrfs=$(grep -qEe '^CONFIG_BTRFS_FS' /usr/lib/modules/*/config && echo btrfs || true)
132+
fs=${btrfs:-ext4}
133+
;;
134+
*)
135+
# No explicit filesystem set and we're not using composefs. Default to xfs
136+
# with the rationale that we're trying to get filesystem coverage across
137+
# all the cases in general.
138+
if test -z "${base_rootfs}"; then
139+
fs=xfs
140+
fi
141+
;;
142+
esac
143+
if test -n "$fs"; then
144+
cat > /usr/lib/bootc/install/80-ext4-composefs.toml <<EOF
145+
[install.filesystem.root]
146+
type = "${fs}"
147+
EOF
148+
fi
111149
fi
112150
EORUN
113151
# Create a layer that is our new binaries

Justfile

Lines changed: 35 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,35 @@
1111

1212
# --------------------------------------------------------------------
1313

14+
# ostree: The default
15+
# composefs-sealeduki-sdboot: A system with a sealed composefs using systemd-boot
16+
variant := env("BOOTC_variant", "ostree")
17+
base := env("BOOTC_base", "quay.io/centos-bootc/centos-bootc:stream10")
18+
19+
buildargs := "--build-arg=base=" + base + " --build-arg=variant=" + variant
20+
1421
# Build the container image from current sources.
1522
# Note commonly you might want to override the base image via e.g.
1623
# `just build --build-arg=base=quay.io/fedora/fedora-bootc:42`
17-
build *ARGS:
18-
podman build --jobs=4 -t localhost/bootc {{ARGS}} .
19-
20-
# Build a sealed image from current sources. This will default to
21-
# generating Secure Boot keys in target/test-secureboot.
22-
build-sealed *ARGS:
23-
podman build --build-arg=sdboot=1 --jobs=4 -t localhost/bootc-unsealed {{ARGS}} .
24-
./tests/build-sealed localhost/bootc-unsealed localhost/bootc
24+
build:
25+
podman build --jobs=4 -t localhost/bootc-bin {{buildargs}} .
26+
./tests/build-sealed {{variant}} localhost/bootc-bin localhost/bootc
2527

2628
# This container image has additional testing content and utilities
27-
build-integration-test-image *ARGS:
28-
cd hack && podman build --jobs=4 -t localhost/bootc-integration -f Containerfile {{ARGS}} .
29+
build-integration-test-image: build
30+
cd hack && podman build --jobs=4 -t localhost/bootc-integration-bin {{buildargs}} -f Containerfile .
31+
./tests/build-sealed {{variant}} localhost/bootc-integration-bin localhost/bootc-integration
2932
# Keep these in sync with what's used in hack/lbi
3033
podman pull -q --retry 5 --retry-delay 5s quay.io/curl/curl:latest quay.io/curl/curl-base:latest registry.access.redhat.com/ubi9/podman:latest
3134

32-
test-composefs: build-sealed
35+
# Build+test composefs; compat alias
36+
test-composefs:
37+
# These first two are currently a distinct test suite from tmt that directly
38+
# runs an integration test binary in the base image via bcvk
39+
just variant=composefs-sealeduki-sdboot build
3340
cargo run --release -p tests-integration -- composefs-bcvk localhost/bootc
41+
# We're trying to move more testing to tmt, so
42+
just variant=composefs-sealeduki-sdboot test-tmt readonly
3443

3544
# Only used by ci.yml right now
3645
build-install-test-image: build-integration-test-image
@@ -59,27 +68,27 @@ validate-local:
5968
build-disk *ARGS:
6069
./tests/build.sh {{ARGS}}
6170

62-
# The tests which run a fully booted bootc system (i.e. where in place
63-
# updates are supported) as if it were a production environment use
64-
# https://github.com/teemtee/tmt.
71+
# Run tmt-based test suites using local virtual machines with
72+
# bcvk.
6573
#
66-
# This task runs *all* of the tmt-based tests targeting the disk image generated
67-
# in the previous step.
68-
test-tmt *ARGS: build-disk
69-
./tests/run-tmt.sh {{ARGS}}
74+
# To run an individual test, pass it as an argument like:
75+
# `just test-tmt readonly`
76+
test-tmt *ARGS: build-integration-test-image
77+
cargo xtask run-tmt --env=BOOTC_variant={{variant}} localhost/bootc-integration {{ARGS}}
7078

71-
# Like test-tmt but assumes that a disk image is already built
72-
test-tmt-nobuild *ARGS:
73-
./tests/run-tmt.sh {{ARGS}}
74-
75-
# Run just one tmt test: `just test-tmt-one test-20-local-upgrade`
76-
test-tmt-one PLAN: build-disk
77-
./tests/run-tmt.sh plan --name {{PLAN}}
79+
# Cleanup all test VMs created by tmt tests
80+
tmt-vm-cleanup:
81+
bcvk libvirt rm --stop --force --label bootc.test=1
7882

7983
# Run tests (unit and integration) that are containerized
8084
test-container: build-units build-integration-test-image
8185
podman run --rm --read-only localhost/bootc-units /usr/bin/bootc-units
82-
podman run --rm localhost/bootc-integration bootc-integration-tests container
86+
# Pass these through for cross-checking
87+
podman run --rm --env=BOOTC_variant={{variant}} --env=BOOTC_base={{base}} localhost/bootc-integration bootc-integration-tests container
88+
89+
# Print the container image reference for a given short $ID-VERSION_ID
90+
pullspec-for-os NAME:
91+
@jq -r --arg v "{{NAME}}" '.[$v]' < hack/os-image-map.json
8392

8493
build-mdbook:
8594
cd docs && podman build -t localhost/bootc-mdbook -f Dockerfile.mdbook

crates/tests-integration/src/container.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::process::Command;
22

33
use anyhow::{Context, Result};
4+
use camino::Utf8Path;
45
use fn_error_context::context;
56
use libtest_mimic::Trial;
67
use xshell::{cmd, Shell};
@@ -49,10 +50,42 @@ fn test_system_reinstall_help() -> Result<()> {
4950
Ok(())
5051
}
5152

53+
/// Verify that the values of `variant` and `base` from Justfile actually applied
54+
/// to this container image.
55+
fn test_variant_base_crosscheck() -> Result<()> {
56+
if let Some(variant) = std::env::var("BOOTC_variant").ok() {
57+
// TODO add this to `bootc status` or so?
58+
let boot_efi = Utf8Path::new("/boot/EFI");
59+
match variant.as_str() {
60+
"ostree" => {
61+
assert!(!boot_efi.try_exists()?);
62+
}
63+
"composefs-sealeduki-sdboot" => {
64+
assert!(boot_efi.try_exists()?);
65+
}
66+
o => panic!("Unhandled variant: {o}"),
67+
}
68+
}
69+
if let Some(base) = std::env::var("BOOTC_base").ok() {
70+
// Hackily reverse back from container pull spec to ID-VERSION_ID
71+
// TODO: move the OsReleaseInfo into an internal crate we use
72+
let osrelease = std::fs::read_to_string("/usr/lib/os-release")?;
73+
if base.contains("centos-bootc") {
74+
assert!(osrelease.contains(r#"ID="centos""#))
75+
} else if base.contains("fedora-bootc") {
76+
assert!(osrelease.contains(r#"ID=fedora"#));
77+
} else {
78+
eprintln!("notice: Unhandled base {base}")
79+
}
80+
}
81+
Ok(())
82+
}
83+
5284
/// Tests that should be run in a default container image.
5385
#[context("Container tests")]
5486
pub(crate) fn run(testargs: libtest_mimic::Arguments) -> Result<()> {
5587
let tests = [
88+
new_test("variant-base-crosscheck", test_variant_base_crosscheck),
5689
new_test("bootc upgrade", test_bootc_upgrade),
5790
new_test("install config", test_bootc_install_config),
5891
new_test("status", test_bootc_status),

crates/xtask/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ anyhow = { workspace = true }
1717
anstream = { workspace = true }
1818
camino = { workspace = true }
1919
chrono = { workspace = true, features = ["std"] }
20+
clap = { workspace = true, features = ["derive"] }
2021
fn-error-context = { workspace = true }
2122
owo-colors = { workspace = true }
2223
serde = { workspace = true, features = ["derive"] }
@@ -27,6 +28,7 @@ xshell = { workspace = true }
2728

2829
# Crate-specific dependencies
2930
mandown = "1.1.0"
31+
rand = "0.8"
3032
tar = "0.4"
3133

3234
[lints]

0 commit comments

Comments
 (0)