diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 5104c558e..f5b339410 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -18,7 +18,7 @@ jobs: publish-images: strategy: matrix: - test_os: [fedora-42, fedora-43, fedora-44, centos-9, centos-10] + test_os: [fedora-43, fedora-44, centos-9, centos-10] variant: [ostree, composefs-sealeduki-sdboot] exclude: # centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0081dfa11..995ab4b19 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,7 +120,7 @@ jobs: strategy: fail-fast: false matrix: - test_os: [fedora-42, fedora-43, fedora-44, centos-9, centos-10] + test_os: [fedora-43, fedora-44, centos-9, centos-10] runs-on: ubuntu-24.04 @@ -153,7 +153,7 @@ jobs: fail-fast: false matrix: # No fedora-44 due to https://bugzilla.redhat.com/show_bug.cgi?id=2429501 - test_os: [fedora-42, fedora-43, centos-9, centos-10] + test_os: [fedora-43, centos-9, centos-10] variant: [ostree, composefs-sealeduki-sdboot] exclude: # centos-9 UKI is experimental/broken (https://github.com/bootc-dev/bootc/issues/1812) diff --git a/.packit.yaml b/.packit.yaml index 7461868c6..d57a14873 100644 --- a/.packit.yaml +++ b/.packit.yaml @@ -40,9 +40,6 @@ jobs: - centos-stream-10-x86_64 - centos-stream-10-aarch64 - centos-stream-10-s390x - - fedora-42-x86_64 - - fedora-42-aarch64 - - fedora-42-s390x - fedora-43-x86_64 - fedora-43-aarch64 - fedora-43-s390x @@ -70,8 +67,6 @@ jobs: - centos-stream-9-aarch64 - centos-stream-10-x86_64 - centos-stream-10-aarch64 - - fedora-42-x86_64 - - fedora-42-aarch64 - fedora-43-x86_64 - fedora-43-aarch64 # https://bugzilla.redhat.com/show_bug.cgi?id=2429501 diff --git a/Dockerfile b/Dockerfile index 11ca9ab5c..b635e0b03 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ FROM $base as buildroot ARG initramfs=1 # This installs our buildroot, and we want to cache it independently of the rest. # Basically we don't want changing a .rs file to blow out the cache of packages. -RUN --mount=type=tmpfs,target=/run \ +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packaging,src=/,target=/run/packaging \ /run/packaging/install-buildroot # Now copy the rest of the source @@ -32,11 +32,11 @@ WORKDIR /src # See https://www.reddit.com/r/rust/comments/126xeyx/exploring_the_problem_of_faster_cargo_docker/ # We aren't using the full recommendations there, just the simple bits. # First we download all of our Rust dependencies -RUN --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome cargo fetch FROM buildroot as sdboot-content # Writes to /out -RUN --mount=type=tmpfs,target=/run /src/contrib/packaging/configure-systemdboot download +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp /src/contrib/packaging/configure-systemdboot download # We always do a "from scratch" build # https://docs.fedoraproject.org/en-US/bootc/building-from-scratch/ @@ -47,14 +47,19 @@ RUN --mount=type=tmpfs,target=/run /src/contrib/packaging/configure-systemdboot # local sources. We'll override it later. # NOTE: All your base belong to me. FROM $base as target-base -RUN --mount=type=tmpfs,target=/run /usr/libexec/bootc-base-imagectl build-rootfs --manifest=standard /target-rootfs +# Handle version skew between base image and mirrors for CentOS Stream +# xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174 +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ + --mount=type=bind,from=packaging,src=/,target=/run/packaging \ + /run/packaging/enable-compose-repos +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp /usr/libexec/bootc-base-imagectl build-rootfs --manifest=standard /target-rootfs FROM scratch as base COPY --from=target-base /target-rootfs/ / # SKIP_CONFIGS=1 skips LBIs, test kargs, and install configs (for FCOS testing) ARG SKIP_CONFIGS -# Use tmpfs,target=/run with bind mounts inside to avoid leaking mount stubs into the image -RUN --mount=type=tmpfs,target=/run \ +# Use tmpfs for /run and /tmp with bind mounts inside to avoid leaking mount stubs into the image +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=src,src=/src/hack,target=/run/hack \ cd /run/hack/ && SKIP_CONFIGS="${SKIP_CONFIGS}" ./provision-derived.sh # Note we don't do any customization here yet @@ -83,12 +88,12 @@ ARG pkgversion ARG SOURCE_DATE_EPOCH ENV SOURCE_DATE_EPOCH=${SOURCE_DATE_EPOCH} # Build RPM directly from source, using cached target directory -RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome RPM_VERSION="${pkgversion}" /src/contrib/packaging/build-rpm FROM buildroot as sdboot-signed # The secureboot key and cert are passed via Justfile # We write the signed binary into /out -RUN --network=none --mount=type=tmpfs,target=/run \ +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=sdboot-content,src=/,target=/run/sdboot-package \ --mount=type=secret,id=secureboot_key \ --mount=type=secret,id=secureboot_cert \ @@ -99,22 +104,22 @@ FROM build as units # A place that we're more likely to be able to set xattrs VOLUME /var/tmp ENV TMPDIR=/var/tmp -RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make install-unit-tests +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make install-unit-tests # This just does syntax checking FROM buildroot as validate -RUN --network=none --mount=type=tmpfs,target=/run --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make validate +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=cache,target=/src/target --mount=type=cache,target=/var/roothome make validate # Common base for final images: configures variant, rootfs, and injects extra content FROM base as final-common ARG variant -RUN --network=none --mount=type=tmpfs,target=/run \ +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=sdboot-content,src=/,target=/run/sdboot-content \ --mount=type=bind,from=sdboot-signed,src=/,target=/run/sdboot-signed \ /run/packaging/configure-variant "${variant}" ARG rootfs="" -RUN --network=none --mount=type=tmpfs,target=/run \ +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp \ --mount=type=bind,from=packaging,src=/,target=/run/packaging \ /run/packaging/configure-rootfs "${variant}" "${rootfs}" COPY --from=packaging /usr-extras/ /usr/ @@ -123,10 +128,14 @@ COPY --from=packaging /usr-extras/ /usr/ # Use with: podman build --target=final --build-context packages=path/to/packages # We use --build-context instead of -v to avoid volume mount stubs leaking into /run. FROM final-common as final -RUN --network=none --mount=type=tmpfs,target=/run \ +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=packages,src=/,target=/run/packages \ /run/packaging/install-rpm-and-setup /run/packages -# Use tmpfs on /run to hide any content created by podman for DNS resolution -# (e.g., /run/systemd/resolve/stub-resolv.conf on Ubuntu hosts) -RUN --network=none --mount=type=tmpfs,target=/run bootc container lint --fatal-warnings +# lint: allow non-tmpfs +RUN --network=none <&2; exit 1 + ;; +esac +sdboot="usr/lib/systemd/boot/efi/systemd-boot${suffix}.efi" sdboot_bn=$(basename ${sdboot}) case $op in diff --git a/contrib/packaging/enable-compose-repos b/contrib/packaging/enable-compose-repos new file mode 100755 index 000000000..20b5ba30d --- /dev/null +++ b/contrib/packaging/enable-compose-repos @@ -0,0 +1,44 @@ +#!/bin/bash +# Enable compose repos to avoid version skew between base image and mirrors +# xref https://gitlab.com/redhat/centos-stream/containers/bootc/-/issues/1174 +set -euo pipefail + +. /usr/lib/os-release + +case "${ID}" in + centos) + # The base image may have been built from a compose that has newer packages + # than what's available on the public mirrors. Enable the compose repos + # with higher priority to ensure we get matching versions. + + # Extract the gpgkey from the existing centos.repo - c9s uses + # RPM-GPG-KEY-centosofficial while c10s uses RPM-GPG-KEY-centosofficial-SHA256 + gpgkey=$(grep -m1 '^gpgkey=' /etc/yum.repos.d/centos.repo | cut -d= -f2) + if [[ -z "${gpgkey}" ]]; then + echo "Error: Could not find gpgkey in /etc/yum.repos.d/centos.repo" >&2 + exit 1 + fi + + cat > /etc/yum.repos.d/centos-compose.repo << EOF +[compose-baseos] +name=CentOS Stream \$releasever Compose BaseOS +baseurl=https://composes.stream.centos.org/stream-\$releasever/production/latest-CentOS-Stream/compose/BaseOS/\$basearch/os/ +gpgcheck=1 +enabled=1 +priority=1 +gpgkey=${gpgkey} + +[compose-appstream] +name=CentOS Stream \$releasever Compose AppStream +baseurl=https://composes.stream.centos.org/stream-\$releasever/production/latest-CentOS-Stream/compose/AppStream/\$basearch/os/ +gpgcheck=1 +enabled=1 +priority=1 +gpgkey=${gpgkey} +EOF + echo "Enabled CentOS Stream compose repos (gpgkey: ${gpgkey})" + ;; + *) + # No compose repo needed for other distros + ;; +esac diff --git a/crates/xtask/src/buildsys.rs b/crates/xtask/src/buildsys.rs index 259a07fa2..c1dc50e46 100644 --- a/crates/xtask/src/buildsys.rs +++ b/crates/xtask/src/buildsys.rs @@ -13,7 +13,7 @@ const DOCKERFILE_NETWORK_CUTOFF: &str = "external dependency cutoff point"; /// /// - Reproducible builds for the RPM /// - Dockerfile network isolation after cutoff point -/// - Dockerfile tmpfs on /run for all RUN instructions +/// - Dockerfile tmpfs on /run and /tmp for all RUN instructions #[context("Checking build system")] pub fn check_buildsys(sh: &Shell, dockerfile_path: &Utf8Path) -> Result<()> { check_package_reproducibility(sh)?; @@ -75,11 +75,16 @@ fn check_dockerfile_rules(dockerfile_path: &Utf8Path) -> Result<()> { } const RUN_NETWORK_NONE: &str = "RUN --network=none"; -const RUN_TMPFS: &str = "--mount=type=tmpfs,target=/run"; +const RUN_TMPFS_RUN: &str = "--mount=type=tmpfs,target=/run"; +const RUN_TMPFS_TMP: &str = "--mount=type=tmpfs,target=/tmp"; +const ALLOW_NON_TMPFS: &str = "# lint: allow non-tmpfs"; /// Verify Dockerfile rules: -/// - All RUN instructions must include `--mount=type=tmpfs,target=/run` to prevent -/// podman's DNS resolver files from leaking into the image +/// - All RUN instructions must include `--mount=type=tmpfs,target=/run` and +/// `--mount=type=tmpfs,target=/tmp` to prevent podman's DNS resolver files +/// and temporary files from leaking into the image +/// - A comment `# lint: allow non-tmpfs` on the preceding line exempts a RUN +/// instruction from the tmpfs requirement /// - After the network cutoff, all RUN instructions must start with `--network=none` /// /// Returns Ok(()) if all RUN instructions comply, or an error listing violations. @@ -96,20 +101,38 @@ pub fn verify_dockerfile_rules(dockerfile: &str) -> Result<()> { })?; let mut errors = Vec::new(); + let mut skip_tmpfs_check = false; for (idx, line) in dockerfile.lines().enumerate() { let line_num = idx + 1; // 1-based line numbers let trimmed = line.trim(); + // Check for the allow comment directive + if trimmed.starts_with(ALLOW_NON_TMPFS) { + skip_tmpfs_check = true; + continue; + } + // Check if this is a RUN instruction if trimmed.starts_with("RUN ") { - // All RUN instructions must include tmpfs mount on /run - if !trimmed.contains(RUN_TMPFS) { - errors.push(format!( - " line {}: RUN instruction must include `{}`", - line_num, RUN_TMPFS - )); + if !skip_tmpfs_check { + // All RUN instructions must include tmpfs mount on /run + if !trimmed.contains(RUN_TMPFS_RUN) { + errors.push(format!( + " line {}: RUN instruction must include `{}`", + line_num, RUN_TMPFS_RUN + )); + } + + // All RUN instructions must include tmpfs mount on /tmp + if !trimmed.contains(RUN_TMPFS_TMP) { + errors.push(format!( + " line {}: RUN instruction must include `{}`", + line_num, RUN_TMPFS_TMP + )); + } } + skip_tmpfs_check = false; // After cutoff, must start with exactly "RUN --network=none" if idx > cutoff_line && !trimmed.starts_with(RUN_NETWORK_NONE) { @@ -139,35 +162,57 @@ mod tests { fn test_dockerfile_rules_valid() { let dockerfile = r#" FROM base -RUN --mount=type=tmpfs,target=/run echo "before cutoff, network allowed" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff, network allowed" # external dependency cutoff point -RUN --network=none --mount=type=tmpfs,target=/run echo "good" -RUN --network=none --mount=type=tmpfs,target=/run --mount=type=bind,from=foo,target=/bar some-command +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good" +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --mount=type=bind,from=foo,target=/bar some-command +# lint: allow non-tmpfs +RUN --network=none bootc container lint --fatal-warnings "#; verify_dockerfile_rules(dockerfile).unwrap(); } #[test] - fn test_dockerfile_rules_missing_tmpfs_before_cutoff() { + fn test_dockerfile_rules_missing_tmpfs_run_before_cutoff() { let dockerfile = r#" FROM base -RUN echo "bad - missing tmpfs" +RUN --mount=type=tmpfs,target=/tmp echo "bad - missing /run tmpfs" # external dependency cutoff point -RUN --network=none --mount=type=tmpfs,target=/run echo "good" +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good" "#; let err = verify_dockerfile_rules(dockerfile).unwrap_err(); let msg = err.to_string(); assert!(msg.contains("line 3"), "error should mention line 3: {msg}"); - assert!(msg.contains("tmpfs"), "error should mention tmpfs: {msg}"); + assert!( + msg.contains("target=/run"), + "error should mention target=/run: {msg}" + ); + } + + #[test] + fn test_dockerfile_rules_missing_tmpfs_tmp_before_cutoff() { + let dockerfile = r#" +FROM base +RUN --mount=type=tmpfs,target=/run echo "bad - missing /tmp tmpfs" +# external dependency cutoff point +RUN --network=none --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "good" +"#; + let err = verify_dockerfile_rules(dockerfile).unwrap_err(); + let msg = err.to_string(); + assert!(msg.contains("line 3"), "error should mention line 3: {msg}"); + assert!( + msg.contains("target=/tmp"), + "error should mention target=/tmp: {msg}" + ); } #[test] fn test_dockerfile_rules_missing_network_flag_after_cutoff() { let dockerfile = r#" FROM base -RUN --mount=type=tmpfs,target=/run echo "before cutoff" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff" # external dependency cutoff point -RUN --mount=type=tmpfs,target=/run echo "bad - missing network flag" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "bad - missing network flag" "#; let err = verify_dockerfile_rules(dockerfile).unwrap_err(); let msg = err.to_string(); @@ -182,9 +227,9 @@ RUN --mount=type=tmpfs,target=/run echo "bad - missing network flag" fn test_dockerfile_rules_missing_tmpfs_after_cutoff() { let dockerfile = r#" FROM base -RUN --mount=type=tmpfs,target=/run echo "before cutoff" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff" # external dependency cutoff point -RUN --network=none echo "bad - missing tmpfs" +RUN --network=none echo "bad - missing both tmpfs" "#; let err = verify_dockerfile_rules(dockerfile).unwrap_err(); let msg = err.to_string(); @@ -197,9 +242,9 @@ RUN --network=none echo "bad - missing tmpfs" // --network=none must come immediately after RUN let dockerfile = r#" FROM base -RUN --mount=type=tmpfs,target=/run echo "before cutoff" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp echo "before cutoff" # external dependency cutoff point -RUN --mount=type=tmpfs,target=/run --network=none echo "bad - network flag not first" +RUN --mount=type=tmpfs,target=/run --mount=type=tmpfs,target=/tmp --network=none echo "bad - network flag not first" "#; let err = verify_dockerfile_rules(dockerfile).unwrap_err(); let msg = err.to_string(); diff --git a/tmt/tests/booted/test-install-outside-container.nu b/tmt/tests/booted/test-install-outside-container.nu index d854da87f..fb205f6af 100644 --- a/tmt/tests/booted/test-install-outside-container.nu +++ b/tmt/tests/booted/test-install-outside-container.nu @@ -10,7 +10,7 @@ use tap.nu # this test in theory independent of starting from a bootc host, # but also because it's useful to test "skew" between the bootc binary # doing the install and the target image. -let target_image = "docker://quay.io/centos-bootc/centos-bootc:stream10" +let target_image = "docker://quay.io/centos-bootc/centos-bootc:stream9" # setup filesystem mkdir /var/mnt diff --git a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh index be9dd1d7b..5fe76d8a6 100644 --- a/tmt/tests/booted/test-install-to-filesystem-var-mount.sh +++ b/tmt/tests/booted/test-install-to-filesystem-var-mount.sh @@ -16,28 +16,20 @@ set -xeuo pipefail -# Use a generic target image to test skew between the bootc binary doing -# the install and the target image -TARGET_IMAGE="docker://quay.io/centos-bootc/centos-bootc:stream10" +# Build a derived image with LBIs removed for installation +TARGET_IMAGE="localhost/bootc-install" echo "Testing bootc install to-filesystem with separate /var mount" -# Disable SELinux enforcement for the install -setenforce 0 +# Copy the currently booted image to container storage for podman to use +bootc image copy-to-storage -# Enable usr-overlay to allow modifications -bootc usr-overlay - -# Install required packages (bootc images are immutable, so we need to install -# after usr-overlay is enabled) -dnf install -y parted lvm2 dosfstools e2fsprogs - -# Mask off conflicting ostree state -if test -d /sysroot/ostree; then - mount --bind /usr/share/empty /sysroot/ostree -fi -rm -vrf /usr/lib/bootupd/updates -rm -vrf /usr/lib/bootc/bound-images.d +# Build a derived image that removes LBIs +cat > /tmp/Containerfile.drop-lbis <<'EOF' +FROM localhost/bootc +RUN rm -rf /usr/lib/bootc/bound-images.d/* +EOF +podman build -t "$TARGET_IMAGE" -f /tmp/Containerfile.drop-lbis # Create a 12GB sparse disk image in /var/tmp (not /tmp which may be tmpfs) DISK_IMG=/var/tmp/disk-var-mount-test.img @@ -91,7 +83,7 @@ vgcreate BL "$LVM_PART" # Create logical volumes lvcreate -L 4G -n var02 BL -lvcreate -L 5G -n root02 BL +lvcreate -l 100%FREE -n root02 BL # Create filesystems on logical volumes mkfs.ext4 -F /dev/BL/var02 @@ -122,8 +114,7 @@ echo "Filesystem layout:" mount | grep /var/mnt/target || true df -h /var/mnt/target /var/mnt/target/boot /var/mnt/target/boot/efi /var/mnt/target/var -# Run bootc install to-filesystem -# This should succeed and handle the separate /var mount correctly +# Run bootc install to-filesystem from within the container image under test podman run \ --rm --privileged \ -v /var/mnt/target:/target \ diff --git a/tmt/tests/booted/test-install-unified-flag.nu b/tmt/tests/booted/test-install-unified-flag.nu index e788967a4..a3ec6ebf9 100644 --- a/tmt/tests/booted/test-install-unified-flag.nu +++ b/tmt/tests/booted/test-install-unified-flag.nu @@ -13,7 +13,7 @@ use tap.nu # Use a generic target image to test skew between the bootc binary doing # the install and the target image -let target_image = "docker://quay.io/centos-bootc/centos-bootc:stream10" +let target_image = "docker://quay.io/centos-bootc/centos-bootc:stream9" def main [] { tap begin "install with experimental unified storage flag"