Skip to content
Open
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
47 changes: 46 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,51 @@ jobs:
name: container-export-test-${{ env.ARCH }}
path: target/anaconda-test/*.log

test-ukiboot:
if: needs.compute-ci-level.outputs.run_heavy == 'true' && contains(fromJson(needs.compute-ci-level.outputs.package_os_matrix), 'fedora-44')
needs: [compute-ci-level, package]
runs-on: ubuntu-26.04
env:
BOOTC_base: quay.io/fedora/fedora-bootc:44
BOOTC_buildroot_base: quay.io/fedora/fedora:44
BOOTC_variant: ostree
BOOTC_SKIP_PACKAGE: "1"
steps:
- uses: actions/checkout@v7
- name: Bootc Ubuntu Setup
uses: bootc-dev/actions/bootc-ubuntu-setup@main
with:
libvirt: true
- name: Install bcvk with aboot support
# Temporary: Drop once bootc-dev/actions has 0.21.0 or later
run: |
set -euo pipefail
# renovate: datasource=github-releases depName=bootc-dev/bcvk
version=0.21.0
target=bcvk-x86_64-unknown-linux-gnu
td=$(mktemp -d)
curl -fL --retry 5 --retry-delay 10 "https://github.com/bootc-dev/bcvk/releases/download/v${version}/${target}.tar.gz" -o "$td/bcvk.tar.gz"
tar -xzf "$td/bcvk.tar.gz" -C "$td"
sudo install -m 0755 "$td/$target" /usr/local/bin/bcvk
bcvk --version
- name: Install tmt
uses: ./.github/actions/install-tmt
- name: Download package artifacts
uses: actions/download-artifact@v8
with:
name: packages-fedora-44
path: target/packages/
- name: Fetch external dependencies
run: just build-fetch
- name: Install, update, and roll back ukiboot image
run: just test-ukiboot
- name: Archive test logs
if: always()
uses: actions/upload-artifact@v7
with:
name: ukiboot-test-x86_64
path: /var/tmp/tmt

# Gates the full heavy suite. Runs in merge_group / workflow_dispatch, and
# also directly on PRs whenever compute-ci-level found no merge queue
# active (see the merge-queue check in that job) — in that case a PR is
Expand All @@ -666,7 +711,7 @@ jobs:
# rather than being skipped along with it.
required-checks-heavy:
if: ${{ always() && (github.event_name == 'merge_group' || github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && needs.compute-ci-level.outputs.merge_queue_enabled != 'true')) }}
needs: [compute-ci-level, cargo-deny, validate, install-tests, docs, package, test-integration, test-upgrade, test-baseconfigs, test-container-export]
needs: [compute-ci-level, cargo-deny, validate, install-tests, docs, package, test-integration, test-upgrade, test-baseconfigs, test-container-export, test-ukiboot]
# Cheap aggregation (jq only) — no need for a full VM.
runs-on: ubuntu-slim
steps:
Expand Down
62 changes: 42 additions & 20 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ clap_mangen = { version = "0.3.0" }
# pulls), which drags in rustls-webpki/untrusted/webpki-root-certs whose
# licenses aren't in our cargo-deny allow list. Re-enable once that's sorted.
# See: https://github.com/bootc-dev/bootc/pull/2295
composefs-ctl = { git = "https://github.com/composefs/composefs-rs", tag = "v0.9.2", default-features = false, features = [
composefs-ctl = { version = "0.9.4", default-features = false, features = [
"pre-6.15",
"pre-6.16",
"oci",
Expand Down Expand Up @@ -87,7 +87,7 @@ linux-kernel-cmdline = { version = "0.1.1", features = ["serde"] }
xshell = "0.2.6"

# To develop against a local composefs-rs checkout, add a [patch] section at the end of this file:
# [patch."https://github.com/composefs/composefs-rs"]
# [patch.crates-io]
# composefs-ctl = { path = "/composefs-rs/crates/composefs-ctl" }

# See https://github.com/coreos/cargo-vendor-filterer
Expand Down
13 changes: 13 additions & 0 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ base_img := env("BOOTC_image", "localhost/bootc")
upgrade_img := base_img + "-upgrade"
# Base image with tmt dependencies added, used as the boot source for upgrade tests
upgrade_source_img := base_img + "-upgrade-source"
ukiboot_img := base_img + "-ukiboot"

# Build variant: ostree (default) or composefs
variant := env("BOOTC_variant", "ostree")
Expand Down Expand Up @@ -192,6 +193,18 @@ test-tmt *ARGS: build
@just _build-upgrade-image
@just test-tmt-nobuild {{ARGS}}

[group('testing')]
build-ukiboot-test-image: build
podman build {{_nocache_arg}} --build-arg=base={{base_img}} -f tmt/tests/Dockerfile.ukiboot -t {{ukiboot_img}}:v1 .

[group('testing')]
build-ukiboot-update-test-image: build-ukiboot-test-image
podman build {{_nocache_arg}} --build-arg=base={{base_img}} --build-arg=TEST_VERSION=2 -f tmt/tests/Dockerfile.ukiboot -t {{ukiboot_img}}:v2 .

[group('testing')]
test-ukiboot: build-ukiboot-update-test-image
cargo xtask run-tmt --env=BOOTC_variant=composefs --filesystem=ext4 --boot-type=aboot --seal-state=unsealed --upgrade-image={{ukiboot_img}}:v2 {{ukiboot_img}}:v1 aboot

# Split out from `test-container` because, unlike the container integration tests,
# unit tests don't depend on variant/filesystem/bootloader/boot_type/seal_state, so
# CI runs this once per OS (in the `package` job) instead of once per test-integration
Expand Down
115 changes: 115 additions & 0 deletions crates/lib/src/aboot.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//! Build Android boot or ukiboot images using aboot-update.

use std::fs::File;
use std::io::BufReader;
use std::process::Command;

use anyhow::{Context, Result, ensure};
use bootc_utils::CommandRunExt;
use camino::Utf8Path;
use fn_error_context::context;
use linux_kernel_cmdline::utf8::Cmdline;

use crate::boot_artifact::prepare_boot_artifact;

#[context("Building aboot image")]
pub(crate) async fn build_aboot(
rootfs: &Utf8Path,
kernel_dir: Option<&Utf8Path>,
out: Option<&Utf8Path>,
allow_missing_fsverity: bool,
write_dumpfile_to: Option<&Utf8Path>,
) -> Result<()> {
if !crate::utils::have_executable("aboot-update")? {
anyhow::bail!("aboot-update executable not found in PATH");
}

let inputs = prepare_boot_artifact(
rootfs,
kernel_dir,
&[],
allow_missing_fsverity,
None,
write_dumpfile_to,
)
.await?;

let mut cmd = Command::new("aboot-update");
cmd.arg("--root")
.arg(rootfs)
.arg("--cmdline")
.arg(&inputs.cmdline);
if let Some(kernel_dir) = kernel_dir {
cmd.arg("--kernel-dir").arg(kernel_dir);
}
if let Some(out) = out {
cmd.arg("--out").arg(out);
}
cmd.arg(&inputs.kernel_version);

tracing::debug!("Executing aboot-update: {:?}", cmd);
cmd.run_inherited().context("Running aboot-update")?;

let output_dir = out
.map(ToOwned::to_owned)
.unwrap_or_else(|| rootfs.join("boot"));
let image_path = output_dir.join(format!("aboot-{}.img", inputs.kernel_version));
let actual_cmdline = read_cmdline(&image_path)?;
validate_composefs_cmdline(&inputs.cmdline, &actual_cmdline)
}

fn read_cmdline(path: &Utf8Path) -> Result<String> {
let image = File::open(path).with_context(|| format!("Opening generated image {path}"))?;
let mut image = BufReader::new(image);
let (_, cmdline) = crate::kernel::read_aboot_metadata(&mut image)
.with_context(|| format!("Parsing generated aboot image {path}"))?;
Ok(cmdline.to_string())
}

fn validate_composefs_cmdline(expected: &str, actual: &str) -> Result<()> {
let composefs_args = |cmdline: &str| -> Vec<String> {
Cmdline::from(cmdline)
.iter_str()
.filter(|arg| arg.starts_with("composefs=") || arg.starts_with("composefs.digest="))
.map(str::to_owned)
.collect()
};
let mut expected = composefs_args(expected);
ensure!(
!expected.is_empty(),
"Generated command line has no composefs parameter"
);
let mut actual = composefs_args(actual);
expected.sort_unstable();
actual.sort_unstable();
ensure!(
actual == expected,
"Generated image has the wrong composefs parameters"
);
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn validates_composefs_cmdline() {
validate_composefs_cmdline("quiet composefs=?abcd", "bootconfig composefs=?abcd").unwrap();
validate_composefs_cmdline(
"composefs.digest=v1-sha512-12:abcd composefs=efgh",
"composefs=efgh composefs.digest=v1-sha512-12:abcd",
)
.unwrap();

for actual in [
"quiet",
"composefs=efgh",
"composefs=abcd composefs=abcd",
"composefs=?abcd composefs=?abcd",
"composefs.digest=v1-sha512-12:abcd composefs=efgh composefs=efgh",
] {
assert!(validate_composefs_cmdline("composefs=?abcd", actual).is_err());
}
}
}
Loading
Loading