mirror of
https://github.com/containers/bootc.git
synced 2026-02-05 06:45:13 +01:00
tests-integration: Add basic local tmt flow
Part of https://github.com/containers/bootc/issues/543 I'm not totally happy with this, but it does demonstrate the basic wiring flow of: - `cargo xtask test-tmt` That will do a container build, make a disk image from it, and run a "hello world" tmt test. A lot more to do here including wiring up our existing tests into this, and deduplicating with the other integration tests. A key aspect too will be exploring workflows that e.g. expose a registry locally. Signed-off-by: Colin Walters <walters@verbum.org>
This commit is contained in:
8
Cargo.lock
generated
8
Cargo.lock
generated
@@ -954,6 +954,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indoc"
|
||||
version = "2.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5"
|
||||
|
||||
[[package]]
|
||||
name = "instant"
|
||||
version = "0.1.12"
|
||||
@@ -1922,7 +1928,9 @@ dependencies = [
|
||||
"cap-std-ext",
|
||||
"clap",
|
||||
"fn-error-context",
|
||||
"indoc",
|
||||
"libtest-mimic",
|
||||
"oci-spec",
|
||||
"rustix",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
4
Makefile
4
Makefile
@@ -32,8 +32,8 @@ bin-archive: all
|
||||
test-bin-archive: all
|
||||
$(MAKE) install-with-tests DESTDIR=tmp-install && $(TAR_REPRODUCIBLE) --zstd -C tmp-install -cf target/bootc.tar.zst . && rm tmp-install -rf
|
||||
|
||||
install-kola-tests:
|
||||
install -D -t $(DESTDIR)$(prefix)/lib/coreos-assembler/tests/kola/bootc tests/kolainst/*
|
||||
test-tmt:
|
||||
cargo xtask test-tmt
|
||||
|
||||
validate:
|
||||
cargo fmt
|
||||
|
||||
11
plans/integration.fmf
Normal file
11
plans/integration.fmf
Normal file
@@ -0,0 +1,11 @@
|
||||
# This tmt test just demonstrates local tmt usage.
|
||||
# We'll hopefully expand it to do more interesting things in the
|
||||
# future and unify with the other test plans.
|
||||
provision:
|
||||
how: virtual
|
||||
# Generated by `cargo xtask `
|
||||
image: file://./target/testbootc-cloud.qcow2
|
||||
summary: Basic smoke test
|
||||
execute:
|
||||
how: tmt
|
||||
script: bootc status
|
||||
@@ -16,7 +16,9 @@ camino = "1.1.6"
|
||||
cap-std-ext = "4"
|
||||
clap = { version= "4.5.4", features = ["derive","cargo"] }
|
||||
fn-error-context = "0.2.1"
|
||||
indoc = "2.0.5"
|
||||
libtest-mimic = "0.7.3"
|
||||
oci-spec = "0.6.5"
|
||||
rustix = { "version" = "0.38.34", features = ["thread", "fs", "system", "process"] }
|
||||
serde = { features = ["derive"], version = "1.0.199" }
|
||||
serde_json = "1.0.116"
|
||||
|
||||
165
tests-integration/src/runvm.rs
Normal file
165
tests-integration/src/runvm.rs
Normal file
@@ -0,0 +1,165 @@
|
||||
use anyhow::{Context, Result};
|
||||
use camino::{Utf8Path, Utf8PathBuf};
|
||||
use clap::Subcommand;
|
||||
use fn_error_context::context;
|
||||
use xshell::{cmd, Shell};
|
||||
|
||||
const BUILDER_ANNOTATION: &str = "bootc.diskimage-builder";
|
||||
const TEST_IMAGE: &str = "localhost/bootc";
|
||||
const TESTVMDIR: &str = "testvm";
|
||||
const DISK_CACHE: &str = "disk.qcow2";
|
||||
const IMAGEID_XATTR: &str = "user.bootc.container-image-digest";
|
||||
|
||||
#[derive(Debug, Subcommand)]
|
||||
#[clap(rename_all = "kebab-case")]
|
||||
pub(crate) enum Opt {
|
||||
PrepareTmt {
|
||||
#[clap(long)]
|
||||
/// The container image to spawn, otherwise one will be built
|
||||
testimage: Option<String>,
|
||||
},
|
||||
CreateQcow2 {
|
||||
/// Input container image
|
||||
container: String,
|
||||
/// Write disk to this path
|
||||
disk: Utf8PathBuf,
|
||||
},
|
||||
}
|
||||
|
||||
struct TestContext {
|
||||
sh: xshell::Shell,
|
||||
targetdir: Utf8PathBuf,
|
||||
}
|
||||
|
||||
fn image_digest(sh: &Shell, cimage: &str) -> Result<String> {
|
||||
let key = "{{ .Digest }}";
|
||||
let r = cmd!(sh, "podman inspect --type image --format {key} {cimage}").read()?;
|
||||
Ok(r)
|
||||
}
|
||||
|
||||
fn builder_from_image(sh: &Shell, cimage: &str) -> Result<String> {
|
||||
let mut inspect: serde_json::Value =
|
||||
serde_json::from_str(&cmd!(sh, "podman inspect --type image {cimage}").read()?)?;
|
||||
let inspect = inspect
|
||||
.as_array_mut()
|
||||
.and_then(|v| v.pop())
|
||||
.ok_or_else(|| anyhow::anyhow!("Failed to parse inspect output"))?;
|
||||
let config = inspect
|
||||
.get("Config")
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing config"))?;
|
||||
let config: oci_spec::image::Config =
|
||||
serde_json::from_value(config.clone()).context("Parsing config")?;
|
||||
let builder = config
|
||||
.labels()
|
||||
.as_ref()
|
||||
.and_then(|l| l.get(BUILDER_ANNOTATION))
|
||||
.ok_or_else(|| anyhow::anyhow!("Missing {BUILDER_ANNOTATION}"))?;
|
||||
Ok(builder.to_owned())
|
||||
}
|
||||
|
||||
#[context("Running bootc-image-builder")]
|
||||
fn run_bib(sh: &Shell, cimage: &str, tmpdir: &Utf8Path, diskpath: &Utf8Path) -> Result<()> {
|
||||
let diskpath: Utf8PathBuf = sh.current_dir().join(diskpath).try_into()?;
|
||||
let digest = image_digest(sh, cimage)?;
|
||||
println!("{cimage} digest={digest}");
|
||||
if diskpath.try_exists()? {
|
||||
let mut buf = [0u8; 2048];
|
||||
if rustix::fs::getxattr(diskpath.as_std_path(), IMAGEID_XATTR, &mut buf)
|
||||
.context("Reading xattr")
|
||||
.is_ok()
|
||||
{
|
||||
let buf = String::from_utf8_lossy(&buf);
|
||||
if &*buf == digest.as_str() {
|
||||
println!("Existing disk {diskpath} matches container digest {digest}");
|
||||
return Ok(());
|
||||
} else {
|
||||
println!("Cache miss; previous digest={buf}");
|
||||
}
|
||||
}
|
||||
}
|
||||
let builder = if let Ok(b) = std::env::var("BOOTC_BUILDER") {
|
||||
b
|
||||
} else {
|
||||
builder_from_image(sh, cimage)?
|
||||
};
|
||||
let _g = sh.push_dir(tmpdir);
|
||||
let bibwork = "bib-work";
|
||||
sh.remove_path(bibwork)?;
|
||||
sh.create_dir(bibwork)?;
|
||||
let _g = sh.push_dir(bibwork);
|
||||
let pwd = sh.current_dir();
|
||||
cmd!(sh, "podman run --rm --privileged -v /var/lib/containers/storage:/var/lib/containers/storage --security-opt label=type:unconfined_t -v {pwd}:/output {builder} --type qcow2 --local {cimage}").run()?;
|
||||
let tmp_disk: Utf8PathBuf = sh
|
||||
.current_dir()
|
||||
.join("qcow2/disk.qcow2")
|
||||
.try_into()
|
||||
.unwrap();
|
||||
rustix::fs::setxattr(
|
||||
tmp_disk.as_std_path(),
|
||||
IMAGEID_XATTR,
|
||||
digest.as_bytes(),
|
||||
rustix::fs::XattrFlags::empty(),
|
||||
)
|
||||
.context("Setting xattr")?;
|
||||
cmd!(sh, "mv -Tf {tmp_disk} {diskpath}").run()?;
|
||||
cmd!(sh, "rm -rf {bibwork}").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Given the input container image reference, create a disk
|
||||
/// image in the target directory.
|
||||
#[context("Creating disk")]
|
||||
fn create_disk(ctx: &TestContext, cimage: &str) -> Result<Utf8PathBuf> {
|
||||
let sh = &ctx.sh;
|
||||
let targetdir = ctx.targetdir.as_path();
|
||||
let _targetdir_guard = sh.push_dir(targetdir);
|
||||
sh.create_dir(TESTVMDIR)?;
|
||||
let output_disk: Utf8PathBuf = sh
|
||||
.current_dir()
|
||||
.join(TESTVMDIR)
|
||||
.join(DISK_CACHE)
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
let bibwork = "bib-work";
|
||||
sh.remove_path(bibwork)?;
|
||||
sh.create_dir(bibwork)?;
|
||||
|
||||
run_bib(sh, cimage, bibwork.into(), &output_disk)?;
|
||||
|
||||
Ok(output_disk)
|
||||
}
|
||||
|
||||
pub(crate) fn run(opt: Opt) -> Result<()> {
|
||||
let ctx = &{
|
||||
let sh = xshell::Shell::new()?;
|
||||
let mut targetdir: Utf8PathBuf = cmd!(sh, "git rev-parse --show-toplevel").read()?.into();
|
||||
targetdir.push("target");
|
||||
TestContext { targetdir, sh }
|
||||
};
|
||||
match opt {
|
||||
Opt::PrepareTmt { mut testimage } => {
|
||||
let testimage = if let Some(i) = testimage.take() {
|
||||
i
|
||||
} else {
|
||||
cmd!(
|
||||
&ctx.sh,
|
||||
"podman build --build-arg=variant=tmt -t {TEST_IMAGE} -f hack/Containerfile ."
|
||||
)
|
||||
.run()?;
|
||||
TEST_IMAGE.to_string()
|
||||
};
|
||||
|
||||
let disk = create_disk(ctx, &testimage)?;
|
||||
println!("Created: {disk}");
|
||||
Ok(())
|
||||
}
|
||||
Opt::CreateQcow2 { container, disk } => {
|
||||
let g = ctx.sh.push_dir(&ctx.targetdir);
|
||||
ctx.sh.remove_path("tmp")?;
|
||||
ctx.sh.create_dir("tmp")?;
|
||||
drop(g);
|
||||
run_bib(&ctx.sh, &container, "tmp".into(), &disk)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ use clap::Parser;
|
||||
mod container;
|
||||
mod hostpriv;
|
||||
mod install;
|
||||
mod runvm;
|
||||
mod selinux;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -32,6 +33,8 @@ pub(crate) enum Opt {
|
||||
#[clap(flatten)]
|
||||
testargs: libtest_mimic::Arguments,
|
||||
},
|
||||
#[clap(subcommand)]
|
||||
RunVM(runvm::Opt),
|
||||
/// Extra helper utility to verify SELinux label presence
|
||||
#[clap(name = "verify-selinux")]
|
||||
VerifySELinux {
|
||||
@@ -48,6 +51,7 @@ fn main() {
|
||||
Opt::InstallAlongside { image, testargs } => install::run_alongside(&image, testargs),
|
||||
Opt::HostPrivileged { image, testargs } => hostpriv::run_hostpriv(&image, testargs),
|
||||
Opt::Container { testargs } => container::run(testargs),
|
||||
Opt::RunVM(opts) => runvm::run(opts),
|
||||
Opt::VerifySELinux { rootfs, warn } => {
|
||||
let root = &Dir::open_ambient_dir(&rootfs, cap_std::ambient_authority()).unwrap();
|
||||
let mut path = PathBuf::from(".");
|
||||
|
||||
@@ -14,7 +14,7 @@ Integration test includes two scenarios, `RPM build` and `bootc install/upgrade`
|
||||
podman run --rm --privileged -v ./:/workdir:z -e TEST_OS=$TEST_OS -e ARCH=$ARCH -e RHEL_REGISTRY_URL=$RHEL_REGISTRY_URL -e DOWNLOAD_NODE=$DOWNLOAD_NODE --workdir /workdir quay.io/fedora/fedora:40 ./tests/integration/mockbuild.sh
|
||||
```
|
||||
|
||||
#### Run Integartion Test
|
||||
#### Run Integration Test
|
||||
|
||||
Run on a shared test infrastructure using the [`testing farm`](https://docs.testing-farm.io/Testing%20Farm/0.1/cli.html) tool. For example, running on AWS.
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ const TASKS: &[(&str, fn(&Shell) -> Result<()>)] = &[
|
||||
("package", package),
|
||||
("package-srpm", package_srpm),
|
||||
("custom-lints", custom_lints),
|
||||
("test-tmt", test_tmt),
|
||||
];
|
||||
|
||||
fn try_main() -> Result<()> {
|
||||
@@ -142,6 +143,13 @@ fn man2markdown(sh: &Shell) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[context("test-integration")]
|
||||
fn test_tmt(sh: &Shell) -> Result<()> {
|
||||
cmd!(sh, "cargo run -p tests-integration run-vm prepare-tmt").run()?;
|
||||
cmd!(sh, "tmt run plans -n integration").run()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return a string formatted version of the git commit timestamp, up to the minute
|
||||
/// but not second because, well, we're not going to build more than once a second.
|
||||
#[context("Finding git timestamp")]
|
||||
|
||||
Reference in New Issue
Block a user