Restructures the integration test workflow from a monolithic sequential job into separate provision → test → cleanup stages: - provision: creates ALL nested PVE VMs in a single terraform apply (parallel), waits for APIs, creates tokens, passes outputs to tests - test: matrix [pve9, pve8] consumes provision outputs, no provisioning - cleanup: always runs, API-only teardown for all VMs Terraform refactored to for_each with pve_instances map variable, enabling parallel ISO upload and VM creation. New caching scripts reduce redundant downloads: - ensure-base-iso.sh: downloads PVE ISOs to /opt/pve-isos if missing - ensure-cloud-images.sh: caches cloud image + OVA with 7-day TTL - prepare-auto-iso.sh: --cache-dir flag with hash-based skip Runner no longer needs manual ISO provisioning (zero-touch setup). cleanup-images bumped to min-versions-to-keep: 3 to survive overlapping runs. All jobs gated against dependabot. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.2 KiB
Plan: Integration Test Workflow Refactoring
Status: Planned (not started) Created: 2026-03-23 Priority: Medium — improves CI speed and reliability but not blocking
Goals
- Parallel VM provisioning — provision PVE 8 and PVE 9 VMs simultaneously instead of sequentially
- File caching — cache ISOs, cloud images, and OVA files on the self-hosted runner
- Zero-touch runner setup — runner no longer needs manual ISO provisioning
- Extensibility — easy to add cluster peers, storage VMs in the future
Proposed Job Graph
build ─────────────┐
├──→ provision ──→ test-pve8 ──┐
container-image ───┘ (all VMs test-pve9 ──┤──→ cleanup
parallel) test-cluster─┘ (always)
(future)
Changes Required
1. Terraform: for_each multi-VM provisioning
- Refactor main.tf from single-VM to
pve_instancesmap variable - Both ISOs upload and both VMs create in a single
terraform apply - Update variables.tf and outputs.tf
2. New caching scripts
ensure-base-iso.sh— download PVE base ISO if not cached in /opt/pve-isosensure-cloud-images.sh— download cloud image + OVA with ETag-based 7-day TTL- Modify
prepare-auto-iso.sh— add--cache-dirflag with hash-based skip
3. New provision job (self-hosted)
- Preflight cleanup for ALL VM IDs
- Prepare both auto-install ISOs (cached)
- Download cloud images once (cached)
- Single
terraform apply - Wait for both PVE installs + create API tokens
- Expose connection details as job outputs
4. Test jobs consume provision outputs
- Matrix with
max-parallel: 2 - Tests get PVETEST_HOST, PVETEST_APITOKEN from
needs.provision.outputs.* - No provisioning in test jobs
5. Cleanup job with if: always()
- API-only cleanup via preflight-cleanup.sh (stateless)
- Terraform state as artifact for belt-and-suspenders
Estimated Time Savings
| Phase | Current | Proposed |
|---|---|---|
| Build + container | 5-10 min | 5-10 min |
| Provision (sequential → parallel) | 20-30 min | 10-15 min |
| Tests (sequential → parallel if runner allows) | 10-20 min | 5-10 min |
| Teardown | 4-6 min | 3-5 min |
| Total | ~40-60 min | ~25-40 min |
Dependabot / CI Isolation Lessons (2026-03-23)
During scan-6 we discovered two issues that this refactoring must account for:
1. cleanup-images must be gated against dependabot
The cleanup-images job uses actions/delete-package-versions with if: always() to
prune old GHCR container images. When a dependabot PR ran, the integration job was
skipped (correctly), but cleanup-images still fired and deleted the container image
that the main branch integration run was actively using, causing PVE 8 to fail with
"image not found".
Fix already applied: All jobs now have if: github.actor != 'dependabot[bot]'.
For the refactoring: The new cleanup job (Terraform destroy + VM cleanup) must
also be gated. Use if: always() && github.actor != 'dependabot[bot]' on all jobs
that touch shared resources (self-hosted runner, GHCR, PVE host).
2. Container image tags must survive concurrent cleanup
The current cleanup-images job deletes all but 1 container image version. If two
workflow runs overlap (e.g., a push to main while a prior run is still testing), the
cleanup from the first run can delete the image needed by the second.
For the refactoring: Consider one of:
- Tag images by run ID instead of commit SHA, and only delete images older than the current run
- Pin
min-versions-to-keep: 3to survive overlapping runs - Move cleanup to a scheduled workflow (weekly) instead of per-run
3. Self-hosted runner disk space
The runner ran out of disk space during an integration test run, leaving it in a broken state that required manual rebuilding. The caching strategy must account for this:
- Set a maximum cache size or file count in
/opt/pve-isos - Auto-prune ISOs not referenced by the current workflow matrix
- The cleanup job should clean up uploaded ISOs from the PVE host, not just VMs
- Consider a periodic runner maintenance script that frees disk space
Implementation Order
- Refactor Terraform (main.tf, variables.tf, outputs.tf) → for_each
- Create ensure-base-iso.sh and ensure-cloud-images.sh
- Add --cache-dir to prepare-auto-iso.sh
- Create parallel wait wrapper for create-api-token.sh
- Restructure workflow into provision → test → cleanup jobs
- Update prepare-test-environment.sh for cache dir
- Ensure all jobs gated with
github.actor != 'dependabot[bot]' - Address container image cleanup race condition
- Test on runner via workflow_dispatch
Files to Modify
- .github/workflows/integration-tests.yml
- tests/infrastructure/main.tf
- tests/infrastructure/variables.tf
- tests/infrastructure/outputs.tf
- tests/infrastructure/scripts/prepare-auto-iso.sh
- tests/infrastructure/scripts/prepare-test-environment.sh
- tests/infrastructure/scripts/create-api-token.sh (wrapper)
- New: tests/infrastructure/scripts/ensure-base-iso.sh
- New: tests/infrastructure/scripts/ensure-cloud-images.sh