cancel-in-progress: true
jobs:
- e2e:
+ unit-tests:
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
stream: [stream9, stream10]
steps:
- - uses: actions/checkout@v4
- - uses: bootc-dev/actions/bootc-ubuntu-setup@main
+ - uses: actions/checkout@v6
with:
- libvirt: true
- - name: build
- run: STREAM=${{ matrix.stream }} just build
+ fetch-depth: 0
+ - uses: bootc-dev/actions/bootc-ubuntu-setup@main
+
- name: unitcontainer
run: STREAM=${{ matrix.stream }} just unitcontainer
- name: unittest
run: STREAM=${{ matrix.stream }} just unittest
- - name: integration tests (bcvk)
- timeout-minutes: 15
- run: STREAM=${{ matrix.stream }} just integration-container
- - name: Upload test logs
+ - name: Upload unittest logs
if: failure()
uses: actions/upload-artifact@v4
with:
- name: test-suite-log-${{ matrix.stream }}
+ name: test-suite-log-PR-${{ github.event.number }}-${{ matrix.stream }}
path: target/unittest
+
+ integration:
+ runs-on: ubuntu-24.04
+ strategy:
+ fail-fast: false
+ matrix:
+ stream: [stream9, stream10]
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ fetch-depth: 0
+ - uses: bootc-dev/actions/bootc-ubuntu-setup@main
+ with:
+ libvirt: true
+ - name: Install tmt
+ run: pip install --user "tmt[provision-virtual]"
+
+ - name: build
+ run: STREAM=${{ matrix.stream }} just build
+ - name: TMT tests (bcvk)
+ timeout-minutes: 40
+ run: STREAM=${{ matrix.stream }} just test-tmt
+ - name: Archive TMT logs
+ if: always()
+ uses: actions/upload-artifact@v4
+ with:
+ name: tmt-log-PR-${{ github.event.number }}-${{ matrix.stream }}
+ path: /var/tmp/tmt
--- /dev/null
+.cosa
+_kola_temp
+
+# Added by cargo
+/target
--- /dev/null
+---
+upstream_package_name: ostree
+downstream_package_name: ostree
+
+upstream_tag_template: v{version}
+
+actions:
+ # Download spec from Fedora dist-git early so Packit can find it
+ post-upstream-clone:
+ - bash -c "curl -LO https://src.fedoraproject.org/rpms/ostree/raw/rawhide/f/ostree.spec"
+ # Create source archive and patch the spec
+ create-archive:
+ - bash -c "ci/make-git-snapshot.sh"
+ - bash -c "ci/packit-fix-spec.sh ostree.spec"
+ - bash -c "ls -1 libostree-*.tar.xz"
+ fix-spec-file:
+ - bash -c "cat ostree.spec"
+ - bash -c "ls -al"
+
+specfile_path: ostree.spec
+
+jobs:
+ - job: copr_build
+ trigger: pull_request
+ targets:
+ - centos-stream-9-x86_64
+ - centos-stream-9-aarch64
+ - centos-stream-10-x86_64
+ - centos-stream-10-aarch64
+ - fedora-43-x86_64
+ - fedora-43-aarch64
+ - fedora-44-x86_64
+ - fedora-44-aarch64
+
+ - job: tests
+ trigger: pull_request
+ targets:
+ - centos-stream-9-x86_64
+ - centos-stream-9-aarch64
+ - centos-stream-10-x86_64
+ - centos-stream-10-aarch64
+ - fedora-43-x86_64
+ - fedora-43-aarch64
+ - fedora-44-x86_64
+ - fedora-44-aarch64
+ tmt_plan: /tmt/plans/integration
+ tf_extra_params:
+ environments:
+ - tmt:
+ context:
+ running_env: "packit"
make install DESTDIR=/out
EORUN
+# Build RPMs from source using the .copr/Makefile pattern
+FROM buildroot as rpmbuild
+COPY --from=src /src /src
+WORKDIR /src
+RUN dnf -y install /usr/bin/rpmbuild
+RUN <<EORUN
+set -xeuo pipefail
+git config --global --add safe.directory '*'
+git submodule update --init
+ci/make-git-snapshot.sh
+ci/packit-fix-spec.sh ostree.spec
+rpmbuild -bs --define "_sourcedir ${PWD}" --define "_specdir ${PWD}" --define "_builddir ${PWD}" --define "_srcrpmdir ${PWD}" --define "_rpmdir ${PWD}" --define "_buildrootdir ${PWD}/.build" ostree.spec
+dnf builddep -y *.src.rpm
+ci/rpmbuild-cwd --rebuild *.src.rpm
+mkdir -p /rpms
+find . -name '*.rpm' -not -name '*.src.rpm' -exec mv {} /rpms/ \;
+EORUN
+
# This image holds both the main binary and the tests
FROM $base as bin-and-test
RUN rpm -e --nodeps ostree{,-libs}
cargo build --release && \
cp target/release/ostree-bootc-integration-tests /usr/bin/ostree-bootc-integration-tests
-# Override userspace
+# Override userspace with our built RPMs
FROM $base as rootfs
-# Remove the default binaries to ensure we're getting our overrides
-RUN rpm -e --nodeps ostree{,-libs}
-COPY --from=build /out/ /
+COPY --from=rpmbuild /rpms/*.rpm /tmp/rpms/
+RUN ls /tmp/rpms/ && \
+ rpm -Uvh --oldpackage $(ls /tmp/rpms/ostree-*.rpm | grep -v -e devel -e debug) && \
+ rm -rf /tmp/rpms
# The default final container, with also a regenerated
# initramfs in case ostree-prepare-root changed.
FROM rootfs
COPY --from=integration-build /usr/bin/ostree-bootc-integration-tests /usr/bin/ostree-bootc-integration-tests
+COPY hack /hack
+RUN cd /hack && ./provision-derived.sh cloudinit
# https://docs.fedoraproject.org/en-US/bootc/initramfs/#_regenerating_the_initrd
# since we have ostree-prepare-root there
RUN set -x; kver=$(cd /usr/lib/modules && echo *); dracut -vf /usr/lib/modules/$kver/initramfs.img $kver
stream := env('STREAM', 'stream9')
build_args := "--jobs=4 --build-arg=base=quay.io/centos-bootc/centos-bootc:"+stream
+# Build RPMs into target/packages/
+package:
+ #!/bin/bash
+ set -xeuo pipefail
+ packages=target/packages
+ if test -n "${OSTREE_SKIP_PACKAGE:-}"; then
+ if test '!' -d "${packages}"; then
+ echo "OSTREE_SKIP_PACKAGE is set, but missing ${packages}" 1>&2; exit 1
+ fi
+ exit 0
+ fi
+ podman build {{build_args}} -t localhost/ostree-pkg --target=rpmbuild .
+ mkdir -p "${packages}"
+ rm -vf "${packages}"/*.rpm
+ podman run --rm localhost/ostree-pkg tar -C /rpms/ -cf - . | tar -C "${packages}"/ -xvf -
+ chmod a+rx target "${packages}"
+ chmod a+r "${packages}"/*.rpm
+
# Build the container image from current sources
build *ARGS:
podman build {{build_args}} -t localhost/ostree {{ARGS}} .
integration-container *ARGS:
#!/bin/bash
set -euo pipefail
- bcvk libvirt run --name ostree-integration-test --replace --detach --ssh-wait localhost/ostree:latest
+ bcvk libvirt run --name ostree-integration-test --replace --detach localhost/ostree:latest
+ echo "Waiting for SSH..."
+ for i in $(seq 1 30); do
+ if bcvk libvirt ssh ostree-integration-test -- true 2>/dev/null; then
+ echo "SSH ready after ~$((i * 10))s"
+ break
+ fi
+ if [ "$i" = 30 ]; then
+ echo "Timeout waiting for SSH" >&2
+ bcvk libvirt rm --stop --force ostree-integration-test
+ exit 1
+ fi
+ sleep 10
+ done
rc=0
bcvk libvirt ssh ostree-integration-test -- env JUNIT_OUTPUT=/tmp/junit.xml \
ostree-bootc-integration-tests {{ARGS}} || rc=$?
integration-ephemeral *ARGS:
bcvk ephemeral run-ssh localhost/ostree:latest -- ostree-bootc-integration-tests {{ARGS}}
+# Run TMT tests inside bcvk-deployed VMs.
+# Each plan runs in its own VM for isolation, following the
+# bootc-dev/bootc cargo xtask run-tmt pattern.
+test-tmt *ARGS: build
+ #!/bin/bash
+ set -euo pipefail
+ image=localhost/ostree:latest
+ random_suffix=$RANDOM
+
+ # Discover test plans
+ plans=$(tmt plan ls | grep '^/')
+ if [ -z "$plans" ]; then
+ echo "No test plans found"
+ exit 0
+ fi
+ echo "Found test plans:"
+ echo "$plans"
+
+ all_passed=true
+ for plan in $plans; do
+ plan_name=$(echo "$plan" | sed 's|.*/||; s|[^a-zA-Z0-9]|-|g')
+ vm_name="ostree-tmt-${random_suffix}-${plan_name}"
+
+ echo ""
+ echo "========================================"
+ echo "Running plan: ${plan}"
+ echo "VM name: ${vm_name}"
+ echo "========================================"
+
+ # Launch VM with bcvk
+ if ! bcvk libvirt run --name "${vm_name}" --detach "${image}"; then
+ echo "Failed to launch VM for plan ${plan}" >&2
+ all_passed=false
+ continue
+ fi
+
+ # Wait for SSH with a longer timeout (cloud-init first boot can be slow)
+ echo "Waiting for SSH on ${vm_name}..."
+ for i in $(seq 1 30); do
+ if bcvk libvirt ssh "${vm_name}" -- true 2>/dev/null; then
+ echo "SSH ready after ~$((i * 10))s"
+ break
+ fi
+ if [ "$i" = 30 ]; then
+ echo "Timeout waiting for SSH on ${vm_name}" >&2
+ bcvk libvirt rm --stop --force "${vm_name}" 2>/dev/null || true
+ all_passed=false
+ continue 2
+ fi
+ sleep 10
+ done
+
+ # Extract SSH connection details
+ inspect_json=$(bcvk libvirt inspect "${vm_name}" --format json)
+ ssh_port=$(echo "$inspect_json" | jq -r '.ssh_port')
+ ssh_key_file=$(mktemp)
+ echo "$inspect_json" | jq -r '.ssh_private_key' > "${ssh_key_file}"
+ chmod 600 "${ssh_key_file}"
+
+ # Run tmt for this plan
+ if tmt run --id "${vm_name}" --all \
+ provision --how connect \
+ --guest localhost --user root \
+ --port "${ssh_port}" --key "${ssh_key_file}" \
+ plan --name "${plan}" \
+ {{ARGS}}; then
+ echo "Plan ${plan} passed"
+ else
+ echo "Plan ${plan} failed" >&2
+ all_passed=false
+ fi
+
+ # Cleanup
+ rm -f "${ssh_key_file}"
+ bcvk libvirt rm --stop --force "${vm_name}" 2>/dev/null || true
+ done
+
+ if [ "$all_passed" = false ]; then
+ echo "Some test plans failed" >&2
+ exit 1
+ fi
+ echo "All test plans passed"
+
# Remove any leftover integration test VMs
integration-cleanup:
- -bcvk libvirt rm --stop --force ostree-integration-test
+ #!/bin/bash
+ bcvk libvirt rm --stop --force ostree-integration-test 2>/dev/null || true
+ bcvk libvirt list --format json 2>/dev/null | jq -r '.[].name' | grep '^ostree-tmt-' | while read vm; do
+ bcvk libvirt rm --stop --force "$vm" 2>/dev/null || true
+ done
sourcefiles := "git ls-files '**.c' '**.cxx' '**.h' '**.hpp'"
# Reformat source files
--- /dev/null
+#!/bin/bash
+# Prepare the RPM spec file for building from source.
+# Downloads the spec from Fedora dist-git if not present, then patches it
+# for the current git version.
+# Used by both the Dockerfile rpmbuild stage and .packit.yaml.
+set -xeuo pipefail
+
+spec="${1:-ostree.spec}"
+
+if [ ! -f "$spec" ]; then
+ curl -LO https://src.fedoraproject.org/rpms/ostree/raw/rawhide/f/ostree.spec
+fi
+
+version=$(git describe --always --tags --match 'v2???.*' | sed -e 's,-,\.,g' -e 's,^v,,')
+sed -i "s,^Version:.*,Version: ${version}," "$spec"
+sed -i 's/^Patch/# Patch/g' "$spec"
+sed -i 's,%autorelease,1%{?dist},g' "$spec"
--- /dev/null
+# Build a bootc image with Packit-built ostree RPMs layered in.
+#
+# Usage (from provision-packit.sh):
+# podman build --from $BASE -t localhost/ostree -f Containerfile.packit .
+
+# The base bootc image is overridden via --from
+FROM quay.io/centos-bootc/centos-bootc:stream9
+
+WORKDIR /ostree-test
+
+# Save testing farm run files
+COPY ARTIFACTS /var/ARTIFACTS
+# Copy ostree repo
+COPY test-artifacts /var/share/test-artifacts
+
+RUN <<EORUN
+set -xeuo pipefail
+. /usr/lib/os-release
+if [[ $ID == "rhel" ]]; then
+ cp rhel.repo /etc/yum.repos.d/
+fi
+# OSCI uses /var/lib/tmt/scripts to save tmt-* commands
+# Fedora CI and Packit use /usr/local/bin
+if [[ -d scripts ]]; then
+ mkdir -p /var/lib/tmt
+ cp -r scripts /var/lib/tmt/
+else
+ cp -r bin /usr/local
+fi
+cp test-artifacts.repo /etc/yum.repos.d/
+
+# Update ostree packages from the COPR repo
+dnf -y update ostree ostree-libs
+
+# Build and install the integration test binary
+dnf -y install cargo rust
+EORUN
+
+COPY tests/bootc-integration /build/tests/bootc-integration
+WORKDIR /build/tests/bootc-integration
+RUN cargo build --release && \
+ cp target/release/ostree-bootc-integration-tests /usr/bin/ && \
+ rm -rf /build
+
+WORKDIR /ostree-test
+
+RUN <<EORUN
+set -xeuo pipefail
+
+# Required by tmt avc checking after test
+dnf -y install audit
+
+# Rpovision booted vm
+./provision-derived.sh cloudinit
+
+# Remove ostree repo, ostree updated already
+rm -f /etc/yum.repos.d/test-artifacts.repo
+# Clean up dnf
+dnf -y clean all
+rm -rf /var/cache /var/lib/dnf
+
+# Regenerate initramfs since ostree-prepare-root may have changed
+kver=$(cd /usr/lib/modules && echo *)
+dracut -vf /usr/lib/modules/$kver/initramfs.img $kver
+EORUN
+
+# Standard bootc labels
+LABEL containers.bootc 1
+LABEL ostree.bootable 1
--- /dev/null
+# Needed by tmt
+rsync
+cloud-init
+/usr/bin/flock
+/usr/bin/awk
--- /dev/null
+#!/bin/bash
+set -xeu
+
+# booted VM provision
+
+cloudinit=0
+case ${1:-} in
+ cloudinit) cloudinit=1 ;;
+ "") ;;
+ *) echo "Unhandled flag: ${1:-}" 1>&2; exit 1 ;;
+esac
+
+# Extra packages we install
+grep -Ev -e '^#' packages.txt | xargs dnf -y install
+
+# Cloud bits
+cat <<KARGEOF >> /usr/lib/bootc/kargs.d/20-console.toml
+kargs = ["console=ttyS0,115200n8"]
+KARGEOF
+if test $cloudinit = 1; then
+ dnf -y install cloud-init
+ ln -s ../cloud-init.target /usr/lib/systemd/system/default.target.wants
+ # Allow root SSH login for testing with bcvk/tmt
+ mkdir -p /etc/cloud/cloud.cfg.d
+ cat > /etc/cloud/cloud.cfg.d/80-enable-root.cfg <<'CLOUDEOF'
+# Enable root login for testing
+disable_root: false
+
+# In image mode, the host root filesystem is mounted at /sysroot, not /
+# That is the one we should attempt to resize, not what is mounted at /
+growpart:
+ mode: auto
+ devices: ["/sysroot"]
+resize_rootfs: false
+CLOUDEOF
+fi
+
+dnf clean all
+# Stock extra cleaning of logs and caches in general (mostly dnf)
+rm /var/log/* /var/cache /var/lib/{dnf,rpm-state,rhsm} -rf
+# And clean root's homedir
+rm /var/roothome/.config -rf
+cat >/usr/lib/tmpfiles.d/bootc-cloud-init.conf <<'EOF'
+d /var/lib/cloud 0755 root root - -
+EOF
+
+
--- /dev/null
+#!/bin/bash
+# Provision a Packit package-mode VM into an ostree image-mode system.
+#
+# This script:
+# 1. Creates a COPR repo file from the Packit test artifacts
+# 2. Builds a bootc container image with the updated ostree RPMs
+# 3. Runs bootc install to-filesystem --replace=alongside
+#
+# After this script completes, the TMT plan reboots into image mode.
+set -exuo pipefail
+
+OSTREE_TEMPDIR=$(mktemp -d)
+trap 'rm -rf -- "$OSTREE_TEMPDIR"' EXIT
+
+# Copy files in hack to OSTREE_TEMPDIR
+cp -a . "$OSTREE_TEMPDIR"
+
+# Keep testing farm run folder
+cp -r /var/ARTIFACTS "$OSTREE_TEMPDIR"
+
+# Copy ostree repo
+cp -r /var/share/test-artifacts "$OSTREE_TEMPDIR"
+
+# Some rhts-*, rstrnt-* and tmt-* commands are in /usr/local/bin
+if [[ -d /var/lib/tmt/scripts ]]; then
+ cp -r /var/lib/tmt/scripts "$OSTREE_TEMPDIR"
+ ls -al "${OSTREE_TEMPDIR}/scripts"
+else
+ cp -r /usr/local/bin "$OSTREE_TEMPDIR"
+ ls -al "${OSTREE_TEMPDIR}/bin"
+fi
+
+ARCH=$(uname -m)
+source /etc/os-release
+
+# Determine the base bootc image
+case "${ID}-${VERSION_ID}" in
+ centos-9) BASE=quay.io/centos-bootc/centos-bootc:stream9 ;;
+ centos-10) BASE=quay.io/centos-bootc/centos-bootc:stream10 ;;
+ fedora-*) BASE=quay.io/fedora/fedora-bootc:${VERSION_ID} ;;
+ *) echo "Unsupported OS: ${ID}-${VERSION_ID}" >&2; exit 1 ;;
+esac
+
+
+if [[ "$ID" == "rhel" ]]; then
+ # OSCI gating only
+ CURRENT_COMPOSE_ID=$(skopeo inspect --no-tags --retry-times=5 --tls-verify=false "docker://${BASE}" | jq -r '.Labels."redhat.compose-id"')
+
+ if [[ -n ${CURRENT_COMPOSE_ID} ]]; then
+ if [[ ${CURRENT_COMPOSE_ID} == *-updates-* ]]; then
+ BATCH_COMPOSE="updates/"
+ else
+ BATCH_COMPOSE=""
+ fi
+ else
+ BATCH_COMPOSE="updates/"
+ CURRENT_COMPOSE_ID=latest-RHEL-$VERSION_ID
+ fi
+
+ # use latest compose if specific compose is not accessible
+ RC=$(curl -skIw '%{http_code}' -o /dev/null "http://${NIGHTLY_COMPOSE_SITE}/rhel-${VERSION_ID%%.*}/nightly/${BATCH_COMPOSE}RHEL-${VERSION_ID%%.*}/${CURRENT_COMPOSE_ID}/STATUS")
+ if [[ $RC != "200" ]]; then
+ CURRENT_COMPOSE_ID=latest-RHEL-${VERSION_ID%%}
+ fi
+
+ # generate rhel repo
+ tee "${OSTREE_TEMPDIR}/rhel.repo" >/dev/null <<REPOEOF
+[rhel-baseos]
+name=baseos
+baseurl=http://${NIGHTLY_COMPOSE_SITE}/rhel-${VERSION_ID%%.*}/nightly/${BATCH_COMPOSE}RHEL-${VERSION_ID%%.*}/${CURRENT_COMPOSE_ID}/compose/BaseOS/${ARCH}/os/
+enabled=1
+gpgcheck=0
+
+[rhel-appstream]
+name=appstream
+baseurl=http://${NIGHTLY_COMPOSE_SITE}/rhel-${VERSION_ID%%.*}/nightly/${BATCH_COMPOSE}RHEL-${VERSION_ID%%.*}/${CURRENT_COMPOSE_ID}/compose/AppStream/${ARCH}/os/
+enabled=1
+gpgcheck=0
+REPOEOF
+ cp "${OSTREE_TEMPDIR}/rhel.repo" /etc/yum.repos.d
+fi
+
+ls -al /etc/yum.repos.d
+cat /etc/yum.repos.d/test-artifacts.repo
+ls -al /var/share/test-artifacts
+
+# The Packit COPR repo is already configured on the test VM.
+# Copy it for use inside the container build.
+cp /etc/yum.repos.d/test-artifacts.repo "$OSTREE_TEMPDIR/"
+
+# Copy integration test sources for building inside the container
+cp -a ../tests "$OSTREE_TEMPDIR/"
+
+# Let's check things in hack folder
+ls -al "$OSTREE_TEMPDIR"
+
+# Build the container image with updated ostree RPMs
+podman build --jobs=4 --from "$BASE" \
+ -t localhost/ostree:latest \
+ -v "$OSTREE_TEMPDIR":/ostree-test:z \
+ -f "$OSTREE_TEMPDIR/Containerfile.packit" \
+ "$OSTREE_TEMPDIR"
+
+# Install to the running system (will be activated after reboot)
+podman run \
+ --env BOOTC_SKIP_SELINUX_HOST_CHECK=1 \
+ --rm -ti --privileged \
+ -v /:/target --pid=host --security-opt label=disable \
+ -v /dev:/dev -v /var/lib/containers:/var/lib/containers \
+ localhost/ostree:latest \
+ bootc install to-filesystem --skip-fetch-check --replace=alongside /target
+
+echo "Provisioning complete. System will reboot into image mode."
--- /dev/null
+# Common settings for all plans
+provision:
+ how: virtual
+ image: centos-stream-9
+ memory: 4096
+ disk: 20
+prepare:
+ # Install packages needed for provisioning and testing
+ - how: install
+ order: 97
+ package:
+ - podman
+ - skopeo
+ - ostree
+ - bootc
+ - ansible-core
+ when: running_env == packit
+ # Build bootc image with Packit ostree RPMs and install to filesystem
+ - how: shell
+ order: 98
+ script:
+ - cd hack && ./provision-packit.sh
+ when: running_env == packit
+ # Reboot into image mode (tmt-reboot doesn't work in prepare)
+ - how: ansible
+ order: 99
+ playbook:
+ - https://github.com/bootc-dev/bootc/raw/refs/heads/main/hack/packit-reboot.yml
+ when: running_env == packit
+execute:
+ how: tmt
+
+/plan-booted-verification:
+ summary: Verify ostree system after bootc install + reboot
+ discover:
+ how: fmf
+ test:
+ - /tmt/tests/tests/test-booted-verification
+
+/plan-privileged:
+ summary: Privileged ostree tests (root only, no boot)
+ discover:
+ how: fmf
+ test:
+ - /tmt/tests/tests/test-ostree-privileged
--- /dev/null
+/test-booted-verification:
+ summary: Verify ostree system state on image-mode system
+ duration: 15m
+ test: ostree-bootc-integration-tests privileged_verify_ostree_booted privileged_verify_sysroot privileged_verify_composefs privileged_verify_ostree_cli privileged_verify_sysroot_readonly privileged_verify_ostree_run_metadata privileged_verify_immutable_bit privileged_verify_osinit_unshare privileged_verify_selinux_labels
+
+/test-ostree-privileged:
+ summary: Privileged ostree tests (nofifo, mtime, extensions)
+ duration: 10m
+ test: ostree-bootc-integration-tests privileged_verify_nofifo privileged_verify_mtime privileged_verify_extensions