From: Xiaofeng Wang Date: Fri, 20 Mar 2026 08:50:58 +0000 (+0800) Subject: ci: Add Packit CI with RPM builds and TMT integration tests X-Git-Tag: archive/raspbian/2026.2-1+rpi1^2~9^2~1^2~5^2~3 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=b0cd80d9;p=ostree.git ci: Add Packit CI with RPM builds and TMT integration tests Add Packit-based CI pipeline that builds RPMs via COPR and runs TMT integration tests on bootc image-mode systems. This follows the bootc-dev/bootc pattern of per-plan VM isolation using bcvk. Key changes: - Dockerfile: Add rpmbuild stage, use RPM overlay for rootfs, run provision-derived.sh for VM provisioning (cloud-init, rsync, etc.) - Justfile: Add package target, test-tmt target with bcvk per-plan VMs, longer SSH wait for cloud-init first boot - .github/workflows/bootc.yaml: Split into unit-tests and integration jobs, archive TMT logs with PR number in artifact name - .packit.yaml: COPR builds + TMT tests for centos-stream-9/10 and fedora-43/44 on x86_64/aarch64 - tmt/: FMF test plans and shell-based tests (booted verification, privileged ostree tests) translated from Rust integration tests - hack/: Packit provisioning scripts to convert package-mode VMs to image-mode via bootc install to-filesystem Co-Authored-By: Claude Opus 4.6 Signed-off-by: Xiaofeng Wang --- diff --git a/.fmf/version b/.fmf/version new file mode 100644 index 00000000..d00491fd --- /dev/null +++ b/.fmf/version @@ -0,0 +1 @@ +1 diff --git a/.github/workflows/bootc.yaml b/.github/workflows/bootc.yaml index d7142110..8912a4f5 100644 --- a/.github/workflows/bootc.yaml +++ b/.github/workflows/bootc.yaml @@ -15,29 +15,53 @@ concurrency: 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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..dd033746 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.cosa +_kola_temp + +# Added by cargo +/target diff --git a/.packit.yaml b/.packit.yaml new file mode 100644 index 00000000..77bde495 --- /dev/null +++ b/.packit.yaml @@ -0,0 +1,51 @@ +--- +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" diff --git a/Dockerfile b/Dockerfile index 3dd64c24..43edd194 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,6 +33,24 @@ make -j $(nproc) 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 <&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}} . @@ -64,7 +82,20 @@ build-host-inst: build-host 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=$? @@ -80,9 +111,96 @@ integration-container *ARGS: 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 diff --git a/ci/packit-fix-spec.sh b/ci/packit-fix-spec.sh new file mode 100755 index 00000000..1cf191e8 --- /dev/null +++ b/ci/packit-fix-spec.sh @@ -0,0 +1,17 @@ +#!/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" diff --git a/hack/Containerfile.packit b/hack/Containerfile.packit new file mode 100644 index 00000000..efb3261f --- /dev/null +++ b/hack/Containerfile.packit @@ -0,0 +1,69 @@ +# 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 <&2; exit 1 ;; +esac + +# Extra packages we install +grep -Ev -e '^#' packages.txt | xargs dnf -y install + +# Cloud bits +cat <> /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 + + diff --git a/hack/provision-packit.sh b/hack/provision-packit.sh new file mode 100755 index 00000000..727e35f7 --- /dev/null +++ b/hack/provision-packit.sh @@ -0,0 +1,113 @@ +#!/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 <