From d6aa5274d4b7b6173d7ac80bcb18a624e0d0d6f1 Mon Sep 17 00:00:00 2001 From: Clint Branham Date: Thu, 19 Mar 2026 12:37:35 -0500 Subject: [PATCH] refactor(ci): customize cloud image on nested PVE instead of container Move virt-customize to run on the nested PVE host (which already has libguestfs) instead of inside the CI container. This removes libguestfs-tools, linux-image-generic, and qemu-utils from the Dockerfile, shrinking the image from ~1.2GB to ~770MB. - prepare-test-vm.sh: downloads Debian cloud image on nested PVE, runs virt-customize there to install qemu-guest-agent, then creates VM with imported disk + cloud-init - Remove build-test-image.sh (no longer needed) - Remove /opt/pve-images volume mount Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/integration-tests.yml | 8 --- tests/infrastructure/Dockerfile | 5 -- .../scripts/build-test-image.sh | 50 --------------- .../infrastructure/scripts/prepare-test-vm.sh | 63 ++++++++++--------- 4 files changed, 35 insertions(+), 91 deletions(-) delete mode 100755 tests/infrastructure/scripts/build-test-image.sh diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 13c66e5..870569f 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -101,7 +101,6 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} volumes: - /opt/pve-isos:/opt/pve-isos - - /opt/pve-images:/opt/pve-images strategy: fail-fast: false max-parallel: 1 @@ -246,12 +245,6 @@ jobs: # ── Prepare test Linux VM ──────────────────────────────────────── - - name: Build Alpine test image (cached on host volume) - if: inputs.skip_provision != true - shell: bash - run: | - bash ${SCRIPTS_DIR}/build-test-image.sh /opt/pve-images/alpine-guest-agent.qcow2 - - name: Deploy test Linux VM to nested PVE if: inputs.skip_provision != true id: linux_vm @@ -260,7 +253,6 @@ jobs: OUTPUT=$(bash ${SCRIPTS_DIR}/prepare-test-vm.sh \ "${{ steps.provision.outputs.host }}" \ "${PVE_PASSWORD}" \ - "/opt/pve-images/alpine-guest-agent.qcow2" \ 200) LINUX_VMID=$(echo "$OUTPUT" | grep "^LINUX_VMID=" | cut -d= -f2) echo "linux_vmid=${LINUX_VMID}" >> "$GITHUB_OUTPUT" diff --git a/tests/infrastructure/Dockerfile b/tests/infrastructure/Dockerfile index 72d0fd1..7b8d801 100644 --- a/tests/infrastructure/Dockerfile +++ b/tests/infrastructure/Dockerfile @@ -15,9 +15,6 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ lsb-release \ ca-certificates \ python3 \ - libguestfs-tools \ - linux-image-generic \ - qemu-utils \ xorriso \ && rm -rf /var/lib/apt/lists/* @@ -64,7 +61,5 @@ RUN echo "=== Tool verification ===" && \ curl --version | head -1 && \ jq --version && \ python3 --version && \ - virt-customize --version && \ ssh -V 2>&1 && \ - scp 2>&1 | head -1 || true && \ echo "=== All tools verified ===" diff --git a/tests/infrastructure/scripts/build-test-image.sh b/tests/infrastructure/scripts/build-test-image.sh deleted file mode 100755 index ffb16e7..0000000 --- a/tests/infrastructure/scripts/build-test-image.sh +++ /dev/null @@ -1,50 +0,0 @@ -#!/usr/bin/env bash -# Builds a small Alpine Linux cloud image with qemu-guest-agent pre-installed. -# The image is cached on the runner so it only needs to be built once. -# -# Prerequisites: libguestfs-tools (virt-customize), curl, qemu-utils -# Install on Debian/Ubuntu: apt-get install -y libguestfs-tools curl qemu-utils -# -# Usage: build-test-image.sh -# e.g.: build-test-image.sh /opt/pve-images/alpine-guest-agent.qcow2 - -set -euo pipefail - -OUTPUT="${1:?Usage: build-test-image.sh }" - -# Skip if image already exists -if [ -f "$OUTPUT" ]; then - echo "Image already exists at $OUTPUT, skipping build" - exit 0 -fi - -ALPINE_VERSION="3.21" -ALPINE_RELEASE="3.21.3" -IMAGE_URL="https://dl-cdn.alpinelinux.org/alpine/v${ALPINE_VERSION}/releases/cloud/nocloud_alpine-${ALPINE_RELEASE}-x86_64-bios-cloudinit-r0.qcow2" - -TEMP_DIR=$(mktemp -d) -TEMP_IMAGE="${TEMP_DIR}/alpine-cloud.qcow2" - -cleanup() { - rm -rf "$TEMP_DIR" -} -trap cleanup EXIT - -echo "=== Building Alpine test image ===" -echo "Downloading Alpine ${ALPINE_RELEASE} cloud image..." -curl -sL -o "$TEMP_IMAGE" "$IMAGE_URL" - -echo "Customizing image (installing qemu-guest-agent)..." -# virt-customize modifies the image in place: -# - Install qemu-guest-agent package -# - Enable the service on boot (OpenRC) -# - Ensure the virtio serial device is available for guest agent communication -virt-customize -a "$TEMP_IMAGE" \ - --install qemu-guest-agent \ - --run-command 'rc-update add qemu-guest-agent default' \ - --run-command 'echo "GA_PATH=/dev/vport2p1" >> /etc/conf.d/qemu-guest-agent' - -# Move to final location -mkdir -p "$(dirname "$OUTPUT")" -mv "$TEMP_IMAGE" "$OUTPUT" -echo "Image ready at $OUTPUT ($(du -h "$OUTPUT" | cut -f1))" diff --git a/tests/infrastructure/scripts/prepare-test-vm.sh b/tests/infrastructure/scripts/prepare-test-vm.sh index b2269a0..4f399d0 100755 --- a/tests/infrastructure/scripts/prepare-test-vm.sh +++ b/tests/infrastructure/scripts/prepare-test-vm.sh @@ -1,54 +1,60 @@ #!/usr/bin/env bash -# Deploys the pre-built Alpine test image to a nested PVE instance and creates -# a VM with guest agent enabled. Waits for the guest agent to respond. +# Downloads a Debian cloud image, installs qemu-guest-agent via +# virt-customize (which is already available on the nested PVE host), +# creates a VM with the customized disk, and waits for the guest agent. # -# The VM gets: -# - The Alpine cloud image imported as its boot disk -# - Cloud-init drive for initial configuration -# - Guest agent enabled (agent=1) -# - DHCP networking on vmbr0 +# All heavy lifting happens on the nested PVE via SSH — no libguestfs +# or large dependencies needed in the CI container. # -# Usage: prepare-test-vm.sh +# Usage: prepare-test-vm.sh # # Outputs (to stdout, for capture by caller): # LINUX_VMID= set -euo pipefail -NESTED_IP="${1:?Usage: prepare-test-vm.sh }" +NESTED_IP="${1:?Usage: prepare-test-vm.sh }" ROOT_PASS="$2" -IMAGE_PATH="$3" -VMID="$4" +VMID="$3" + +CLOUD_IMAGE_URL="https://cloud.debian.org/images/cloud/bookworm/latest/debian-12-genericcloud-amd64.qcow2" SSH_OPTS="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=ERROR" SSH_CMD="sshpass -p ${ROOT_PASS} ssh ${SSH_OPTS} root@${NESTED_IP}" -SCP_CMD="sshpass -p ${ROOT_PASS} scp ${SSH_OPTS}" echo "=== Preparing test Linux VM (VMID ${VMID}) on ${NESTED_IP} ===" -# Copy image to nested PVE -echo "Uploading Alpine image to nested PVE..." -${SCP_CMD} "${IMAGE_PATH}" "root@${NESTED_IP}:/tmp/alpine-test.qcow2" - -# Create VM and import disk -echo "Creating VM and importing disk..." +# Download image, customize, create VM — all on the nested PVE +echo "Downloading and customizing Debian cloud image on nested PVE..." ${SSH_CMD} bash <&1 | tail -1 +# Import the customized disk +echo "Importing disk..." +qm importdisk ${VMID} /tmp/debian-cloud.qcow2 local-lvm 2>&1 | tail -1 -# Attach the imported disk and configure boot +# Attach disk and configure boot qm set ${VMID} \ --scsi0 local-lvm:vm-${VMID}-disk-0 \ --boot order=scsi0 \ @@ -57,22 +63,23 @@ qm set ${VMID} \ # Add cloud-init drive qm set ${VMID} --ide2 local-lvm:cloudinit -# Set cloud-init config (root user with password) +# Set cloud-init config qm set ${VMID} \ --ciuser root \ --cipassword "${ROOT_PASS}" \ --ipconfig0 ip=dhcp # Start the VM +echo "Starting VM ${VMID}..." qm start ${VMID} -# Clean up uploaded image -rm -f /tmp/alpine-test.qcow2 +# Clean up +rm -f /tmp/debian-cloud.qcow2 REMOTE # Wait for guest agent to respond echo "Waiting for guest agent on VM ${VMID}..." -TIMEOUT=120 +TIMEOUT=180 ELAPSED=0 while [ $ELAPSED -lt $TIMEOUT ]; do if ${SSH_CMD} "qm agent ${VMID} ping" 2>/dev/null; then