ci: Add Packit CI with RPM builds and TMT integration tests
authorXiaofeng Wang <henrywangxf@me.com>
Fri, 20 Mar 2026 08:50:58 +0000 (16:50 +0800)
committerXiaofeng Wang <henrywangxf@me.com>
Mon, 23 Mar 2026 01:30:46 +0000 (09:30 +0800)
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 <noreply@anthropic.com>
Signed-off-by: Xiaofeng Wang <henrywangxf@me.com>
13 files changed:
.fmf/version [new file with mode: 0644]
.github/workflows/bootc.yaml
.gitignore [new file with mode: 0644]
.packit.yaml [new file with mode: 0644]
Dockerfile
Justfile
ci/packit-fix-spec.sh [new file with mode: 0755]
hack/Containerfile.packit [new file with mode: 0644]
hack/packages.txt [new file with mode: 0644]
hack/provision-derived.sh [new file with mode: 0755]
hack/provision-packit.sh [new file with mode: 0755]
tmt/plans/integration.fmf [new file with mode: 0644]
tmt/tests/tests.fmf [new file with mode: 0644]

diff --git a/.fmf/version b/.fmf/version
new file mode 100644 (file)
index 0000000..d00491f
--- /dev/null
@@ -0,0 +1 @@
+1
index d7142110b4fd05204108557d789c62f1b21077a3..8912a4f547f550a7030c014d9b173f7367d93ebb 100644 (file)
@@ -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 (file)
index 0000000..dd03374
--- /dev/null
@@ -0,0 +1,5 @@
+.cosa
+_kola_temp
+
+# Added by cargo
+/target
diff --git a/.packit.yaml b/.packit.yaml
new file mode 100644 (file)
index 0000000..77bde49
--- /dev/null
@@ -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"
index 3dd64c242058c45169fb18aee27458693c18c053..43edd19451860be9d4bd42ed94d1361bedc393c3 100644 (file)
@@ -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 <<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}
@@ -55,16 +73,19 @@ RUN --network=none \
     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
index b2f53c142e4640b8fff67a6b0c6f642528dc5cc3..fa8c7fec51eb9e4fc3173f582c1e35caf0140cd3 100644 (file)
--- a/Justfile
+++ b/Justfile
@@ -4,6 +4,24 @@ osid := `. /usr/lib/os-release && echo $ID`
 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}} .
@@ -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 (executable)
index 0000000..1cf191e
--- /dev/null
@@ -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 (file)
index 0000000..efb3261
--- /dev/null
@@ -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 <<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
diff --git a/hack/packages.txt b/hack/packages.txt
new file mode 100644 (file)
index 0000000..f158e27
--- /dev/null
@@ -0,0 +1,5 @@
+# Needed by tmt
+rsync
+cloud-init
+/usr/bin/flock
+/usr/bin/awk
diff --git a/hack/provision-derived.sh b/hack/provision-derived.sh
new file mode 100755 (executable)
index 0000000..1d32463
--- /dev/null
@@ -0,0 +1,47 @@
+#!/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
+
+
diff --git a/hack/provision-packit.sh b/hack/provision-packit.sh
new file mode 100755 (executable)
index 0000000..727e35f
--- /dev/null
@@ -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 <<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."
diff --git a/tmt/plans/integration.fmf b/tmt/plans/integration.fmf
new file mode 100644 (file)
index 0000000..e15eb85
--- /dev/null
@@ -0,0 +1,45 @@
+# 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
diff --git a/tmt/tests/tests.fmf b/tmt/tests/tests.fmf
new file mode 100644 (file)
index 0000000..8fd478a
--- /dev/null
@@ -0,0 +1,9 @@
+/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