-
Notifications
You must be signed in to change notification settings - Fork 177
tests: Add composefs-upgrade test for sealed UKI builds #2049
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,62 @@ | ||
| # Just creates a file as a new layer for a synthetic upgrade test | ||
| FROM localhost/bootc | ||
| RUN touch --reference=/usr/bin/bash /usr/share/testing-bootc-upgrade-apply | ||
| # Creates a synthetic upgrade image for testing. | ||
| # For non-UKI builds, this just adds a marker file on top of localhost/bootc. | ||
| # For UKI builds (boot_type=uki), the image is re-sealed with a new composefs | ||
| # digest and (optionally signed) UKI. | ||
| # | ||
| # Build secrets required (for sealed builds): | ||
| # secureboot_key, secureboot_cert | ||
| ARG boot_type=bls | ||
| ARG seal_state=unsealed | ||
| ARG filesystem=ext4 | ||
|
|
||
| # Capture contrib/packaging scripts for use in later stages | ||
| FROM scratch AS packaging | ||
| COPY contrib/packaging / | ||
|
|
||
| # Create the upgrade content (a simple marker file). | ||
| # For UKI builds, we also remove the existing UKI so that seal-uki can | ||
| # regenerate it with the correct composefs digest for this derived image. | ||
| FROM localhost/bootc AS upgrade-base | ||
| ARG boot_type | ||
| RUN touch --reference=/usr/bin/bash /usr/share/testing-bootc-upgrade-apply && \ | ||
| if test "${boot_type}" = "uki"; then rm -rf /boot/EFI/Linux/*.efi; fi | ||
|
|
||
| # Tools for sealing (only meaningfully used for UKI builds) | ||
| FROM localhost/bootc AS tools | ||
| RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ | ||
| --mount=type=bind,from=packaging,src=/,target=/run/packaging \ | ||
| /run/packaging/initialize-sealing-tools | ||
|
|
||
| # Generate a sealed UKI for the upgrade image. | ||
| # bootc is already installed in localhost/bootc (our tools base); the | ||
| # container ukify command it provides is needed for seal-uki. | ||
| FROM tools AS sealed-upgrade-uki | ||
| ARG boot_type seal_state filesystem | ||
| RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ | ||
| --mount=type=secret,id=secureboot_key \ | ||
| --mount=type=secret,id=secureboot_cert \ | ||
| --mount=type=bind,from=packaging,src=/,target=/run/packaging \ | ||
| --mount=type=bind,from=upgrade-base,src=/,target=/run/target <<EORUN | ||
| set -xeuo pipefail | ||
|
|
||
| allow_missing_verity=false | ||
| if [[ $filesystem == "xfs" ]]; then | ||
| allow_missing_verity=true | ||
| fi | ||
|
|
||
| if test "${boot_type}" = "uki"; then | ||
| /run/packaging/seal-uki /run/target /out /run/secrets $allow_missing_verity $seal_state | ||
cgwalters marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| fi | ||
| EORUN | ||
|
|
||
| # Final stage: the upgrade image, optionally with a re-sealed UKI | ||
| FROM upgrade-base | ||
| ARG boot_type | ||
| RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ | ||
| --mount=type=bind,from=packaging,src=/,target=/run/packaging \ | ||
| --mount=type=bind,from=sealed-upgrade-uki,src=/,target=/run/sealed-uki <<EORUN | ||
| set -xeuo pipefail | ||
| if test "${boot_type}" = "uki"; then | ||
| /run/packaging/finalize-uki /run/sealed-uki/out | ||
| fi | ||
| EORUN | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| # number: 39 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we not use the if ($imgsrc | str ends-with "-local") {
bootc image copy-to-storage
# A simple derived container that adds a file
"FROM localhost/bootc
RUN touch /usr/share/testing-bootc-upgrade-apply
" | save Dockerfile
# Build it
podman build -t $imgsrc .
}It won't rebuild if we have the upgrade image built already I think for now this is really good to have test, but imo we should have the same test (code) for all cases, so we don't end up missing something while special casing certain tests |
||
| # extra: | ||
| # try_bind_storage: true | ||
| # tmt: | ||
| # summary: Test composefs upgrade with pre-built (optionally sealed) image | ||
| # duration: 30m | ||
| # | ||
| # This test verifies that upgrading a composefs system works correctly, | ||
| # including sealed UKI images. The upgrade image is pre-built on the host | ||
| # with proper sealing and made available via bind-storage-ro. | ||
| # | ||
| use std assert | ||
| use tap.nu | ||
|
|
||
| bootc status | ||
| journalctl --list-boots | ||
|
|
||
| let st = bootc status --json | from json | ||
| let booted = $st.status.booted.image | ||
| let is_composefs = (tap is_composefs) | ||
|
|
||
| # This test only makes sense for composefs | ||
| if not $is_composefs { | ||
| tap begin "composefs upgrade (skipped - not composefs)" | ||
| print "# SKIP: not running on composefs" | ||
| tap ok | ||
| exit 0 | ||
| } | ||
|
|
||
| def upgrade_image [] { | ||
| $env.BOOTC_upgrade_image? | default "localhost/bootc-upgrade" | ||
| } | ||
|
|
||
| # First boot: save the original verity digest, then switch to the upgrade image | ||
| def first_boot [] { | ||
| tap begin "composefs upgrade with pre-built image" | ||
|
|
||
| # Save the original verity so we can check for two UKIs after upgrade | ||
| $st.status.booted.composefs.verity | save /var/original-verity | ||
|
|
||
| let img = (upgrade_image) | ||
| print $"Switching to upgrade image: ($img)" | ||
|
|
||
| # The upgrade image should be available via host container storage | ||
| # (passed through --bind-storage-ro by bcvk) | ||
| bootc switch --transport containers-storage $img | ||
| tmt-reboot | ||
| } | ||
|
|
||
| # Second boot: verify the upgrade succeeded and both UKIs exist | ||
| def second_boot [] { | ||
| print "Verifying composefs upgrade" | ||
|
|
||
| # Verify we booted from the upgrade image | ||
| let img = (upgrade_image) | ||
| assert equal $booted.image.transport containers-storage | ||
| assert equal $booted.image.image $img | ||
|
|
||
| # Verify composefs is still active after the upgrade | ||
| assert (tap is_composefs) "composefs should still be active after upgrade" | ||
|
|
||
| # Verify the upgrade marker file exists | ||
| assert ("/usr/share/testing-bootc-upgrade-apply" | path exists) "upgrade marker file should exist" | ||
|
|
||
| # Verify composefs properties are preserved after the upgrade | ||
| let composefs_info = $st.status.booted.composefs | ||
| print $"composefs info: ($composefs_info)" | ||
|
|
||
| # Verify there is a valid verity digest (composefs was properly deployed) | ||
| assert (($composefs_info.verity | str length) > 0) "composefs verity digest should be present" | ||
|
|
||
| # For UKI boot type, verify both the original and upgrade UKIs exist on the ESP | ||
| if ($composefs_info.bootType | str downcase) == "uki" { | ||
| let bootloader = ($composefs_info.bootloader | str downcase) | ||
|
|
||
| # UKIs are stored in EFI/Linux/bootc/ on the ESP | ||
| let boot_dir = if $bootloader == "systemd" { | ||
| mkdir /var/tmp/efi | ||
cgwalters marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| mount /dev/vda2 /var/tmp/efi | ||
| "/var/tmp/efi/EFI/Linux/bootc" | ||
| } else { | ||
| "/sysroot/boot/EFI/Linux/bootc" | ||
| } | ||
|
|
||
| let original_verity = (open /var/original-verity | str trim) | ||
| let upgrade_verity = $composefs_info.verity | ||
|
|
||
| print $"boot_dir: ($boot_dir)" | ||
| print $"original verity: ($original_verity)" | ||
| print $"upgrade verity: ($upgrade_verity)" | ||
|
|
||
| # The two verities must differ since the upgrade image has different content | ||
| assert ($original_verity != $upgrade_verity) "upgrade should produce a different verity digest" | ||
|
|
||
| # There should be two .efi UKI files on the ESP: one for the booted | ||
| # deployment (upgrade) and one for the rollback (original) | ||
| let efi_files = (glob $"($boot_dir)/*.efi") | ||
| print $"EFI files: ($efi_files)" | ||
| assert ((($efi_files | length) >= 2)) $"expected at least 2 UKIs on ESP, found ($efi_files | length)" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The triple parentheses are redundant. |
||
| } | ||
|
|
||
| tap ok | ||
| } | ||
|
|
||
| def main [] { | ||
| match $env.TMT_REBOOT_COUNT? { | ||
| null | "0" => first_boot, | ||
| "1" => second_boot, | ||
| $o => { error make { msg: $"Invalid TMT_REBOOT_COUNT ($o)" } }, | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.