diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index d735989..e7a7ed6 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -29,13 +29,18 @@ dotnet test tests/PSProxmoxVE.Core.Tests/ pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed" ``` -A Docker-based dev container replicates the full CI setup locally: +The integration flow runs in the same container image CI uses. There is no wrapper +script — call `run-integration.sh` directly (x86 only): -```powershell -./tests/dev.ps1 # Open pwsh shell in dev container -./tests/dev.ps1 build # Build the module -./tests/dev.ps1 test # Run unit tests -./tests/dev.ps1 integration # Provision nested PVE, run integration tests, cleanup (x86 only) +```bash +pve() { + docker compose -f tests/docker-compose.test.yml --profile infra run --rm dev-infra \ + bash tests/infrastructure/scripts/run-integration.sh "$@" +} + +pve provision 9 +pve test 9 Cluster,VMs # the area filter is optional +pve force-cleanup ``` ## Key Coding Conventions diff --git a/CLAUDE.md b/CLAUDE.md index 2beabbf..d0693aa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -72,37 +72,98 @@ Claude Code picks the env block up immediately — the session that adds it alre as the bot, no restart needed. A `Co-Authored-By` trailer is redundant once it is in effect, since the App is the commit author. -### Dev container (recommended) +### Local dev environment -A Docker-based dev environment replicates the full CI setup locally. Works on ARM Macs -(build + test) and x86 (full provisioning flow). +`tests/infrastructure/scripts/run-integration.sh` is the single source of truth for the +provision → test → cleanup lifecycle. CI calls it directly, and so should you — there is +no wrapper script. -```powershell -./tests/dev.ps1 # Open pwsh shell in dev container -./tests/dev.ps1 build # Build the module -./tests/dev.ps1 test # Run unit tests (ARM + x86) -./tests/dev.ps1 integration # Provision nested PVE, run tests, cleanup (x86 only) -./tests/dev.ps1 provision # Provision nested PVE only, no tests (x86 only) -``` - -Configure parent PVE credentials by copying `tests/.env.test.example` to `tests/.env.test`. - -### Build & test without container +Build and unit tests run natively, no container needed: ```bash -# Build dotnet build PSProxmoxVE.sln - -# xUnit tests dotnet test tests/PSProxmoxVE.Core.Tests/ - -# Pester tests (requires pwsh) -pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -Output Detailed" - -# Run all tests via dev container -./tests/dev.ps1 test +pwsh -Command "Invoke-Pester tests/PSProxmoxVE.Tests/ -ExcludeTagFilter Integration -Output Detailed" ``` +An installed `PSProxmoxVE` in `~/.local/share/powershell/Modules/` shadows the local build, +because `_TestHelper.ps1` tries `Import-Module PSProxmoxVE` by name first. Force the local +build with `Import-Module ./src/PSProxmoxVE/bin/Debug/netstandard2.0/PSProxmoxVE.psd1 -Force`, +or delete the installed copy. (`dotnet build` writes there; only `dotnet publish -o +./publish/netstandard2.0`, which CI runs, creates `publish/`.) + +The integration flow needs the `dev-infra` container — the same image CI runs its jobs in +(`tests/Dockerfile.test`, target `dev-infra`). On x86 Linux, compose builds and runs it: + +```bash +pve() { + docker compose -f tests/docker-compose.test.yml --profile infra run --rm dev-infra \ + bash tests/infrastructure/scripts/run-integration.sh "$@" +} + +pve provision 9 +pve test 9 Cluster,VMs # the area filter is optional +pve force-cleanup +``` + +### Running it on macOS (Apple Silicon) + +The image is amd64-only — `proxmox-auto-install-assistant` and the HashiCorp apt repo publish no +arm64 — so it runs under emulation. **Turn on Docker Desktop's "Use Rosetta for x86_64/amd64 +emulation" (Settings → General) first.** Under the default qemu translation `pwsh` starts and +reports its version, then segfaults on module discovery (`uncaught target signal 11`). That fails +the image build at `Install-Module Pester`, and would fail Pester at test time. The build exits 1 +with no diagnostic output, so it reads as a Dockerfile defect rather than an emulation problem. + +With Rosetta on, the same Dockerfile builds to within 150 bytes of the image CI pushes. + +Two ways to get the image. Pulling what CI built is faster and is the exact artifact CI ran: + +```bash +# Needs a CLASSIC PAT with read:packages — GHCR does not accept fine-grained tokens. +read -rs PAT && echo "$PAT" | docker login ghcr.io -u --password-stdin && unset PAT +docker pull --platform linux/amd64 ghcr.io/goodolclint/psproxmoxve-integration:latest + +# or build it locally +docker build --platform linux/amd64 --target dev-infra -f tests/Dockerfile.test -t pve-dev . +``` + +Then drive `run-integration.sh` directly. Compose is not used here: its `dev-infra` service builds +rather than pulls, and bind-mounts `/opt/pve-integration`, which does not exist on a Mac. + +```bash +pve() { + docker run --rm --platform linux/amd64 \ + --env-file tests/.env.test \ + -v "$HOME/pve-integration:/opt/pve-integration" \ + -v "$PWD:/repo" -w /repo \ + ghcr.io/goodolclint/psproxmoxve-integration:latest \ + bash tests/infrastructure/scripts/run-integration.sh "$@" +} + +mkdir -p ~/pve-integration +pve provision 9 +pve test 9 +pve force-cleanup # always run this — see below +``` + +**Expect lifecycle-test failures that CI does not see.** Emulation runs the suite roughly 40% +slower, which widens the `qemu-server` flock race in #113 — typically `Reset-PveVm`, clone and +`Set-PveVmConfig` failing with `can't lock file '/var/lock/qemu-server/lock-.conf'`. Those +are the emulated client losing a race CI wins, not regressions. Provisioning and cleanup are +unaffected. + +### Before any integration run, on any host + +Copy `tests/.env.test.example` to `tests/.env.test` — it lists every required variable, +including the Terraform storage pools CI supplies from repository variables. + +**The nested VMIDs are fixed constants** (storage VM 5080 at `run-integration.sh:80`; nodes 5091 +and 5092 in `pve_vmid()` at `:112`) and +are shared with CI on the same parent cluster. Never start a local run while a CI integration run +is in flight, and always finish with `force-cleanup`: leftover guests fail the next run's +headroom guard. + ## Key Conventions - All cmdlets use `Pve` noun prefix diff --git a/DECISIONS.md b/DECISIONS.md index 72aa810..ed5ca63 100644 --- a/DECISIONS.md +++ b/DECISIONS.md @@ -714,3 +714,46 @@ if [[ -z "${boot_after}" || "${boot_after}" == "${boot_before}" ]]; then fi bash "${SCRIPT_DIR}/wait-for-api.sh" "${NESTED_IP}" 8006 600 ``` + +--- + +## D019 — Local dev calls run-integration.sh directly; there is no wrapper script + +**Status**: Active +**Finding refs**: (none — found auditing the local dev path against the post-ARC CI, 2026-09-01) +**Resolved in scan**: n/a + +### Decision +`tests/infrastructure/scripts/run-integration.sh` is the only entry point to the +provision → test → cleanup lifecycle, for CI and for local development alike. Local runs +invoke it inside the `dev-infra` container — the same image CI runs its jobs in. Do not add +a convenience wrapper around it. + +Build and unit tests need no container at all; they run natively against the solution. + +### Rationale +`tests/dev.ps1` was a 291-line PowerShell wrapper over roughly six `docker compose` and +`docker exec` calls. Every capability it had was already available elsewhere: build and unit +tests are plain `dotnet` and `Invoke-Pester` invocations, and the module build it performed +is duplicated inside `run-integration.sh` itself, which publishes and installs the module +before running the suite. + +Being a second entry point, it drifted from the script it wrapped and from the CI it claimed +to replicate. By the time it was removed it still offered a `-Version 8` leg retired in #88, +mounted the Docker socket for storage containers replaced by the storage VM in #87, and +defaulted its remote-host examples to a runner decommissioned in the ARC migration. + +Four documentation files described a positional calling convention (`./tests/dev.ps1 test`) +that did not do what it read as. The script took its actions from switches (`-Test`), but +also declared `[string[]] $Tests`, so the bare word bound to `-Tests` — the integration-area +filter. With no action switch set, the script then fell through to its `-Shell` default and +silently opened an interactive container shell. Every documented command was wrong, and +wrong in the quietest possible way: it succeeded at something nobody asked for. + +A wrapper that must be kept in sync with the thing it wraps earns its place only when it +removes real friction. This one removed none. + +### Anti-pattern (do not reintroduce) +A `dev.ps1`, `Makefile` target, or shell function that re-implements provisioning steps, +module installation, or test invocation. If a local flow is awkward, fix it in +`run-integration.sh` so CI gets the fix too. diff --git a/README.md b/README.md index 0529a76..075321a 100644 --- a/README.md +++ b/README.md @@ -483,8 +483,10 @@ SDN management requires Proxmox VE 8.0 or later. Connected server is version 7.4 1. Clone the repository 2. Open `PSProxmoxVE.sln` in your IDE 3. Build: `dotnet build` -4. Run unit tests: `./tests/dev.ps1 test` -5. Run integration tests (provisions nested PVE, x86 only): `./tests/dev.ps1 integration` +4. Run unit tests — import the local build first, or an installed copy of the module shadows + it and the suite reports failures against correct code: + `pwsh -Command "Import-Module ./src/PSProxmoxVE/bin/Debug/netstandard2.0/PSProxmoxVE.psd1 -Force; Invoke-Pester tests/PSProxmoxVE.Tests/ -ExcludeTagFilter Integration"` +5. Run integration tests (provisions nested PVE, x86 only): see `CLAUDE.md`, "Local dev environment" ### Commit Convention diff --git a/tests/.env.test.example b/tests/.env.test.example index e0984c8..b63c91d 100644 --- a/tests/.env.test.example +++ b/tests/.env.test.example @@ -1,28 +1,49 @@ # PSProxmoxVE integration test configuration. # Copy to .env.test and fill in values. This file is gitignored. # -# These credentials point to the PARENT PVE host where nested test VMs -# will be provisioned. Integration tests run against the nested VMs, -# not against this host directly. +# These credentials point to the PARENT PVE cluster where nested test VMs are +# provisioned. Integration tests run against the nested VMs, not the parent. # -# Provisioned resources: +# Provisioned per run: # - 2x PVE 9 nodes (pve9a, pve9b) for cluster testing -# - 2x PVE 8 nodes (pve8a, pve8b) for cluster testing -# - Shared storage provided by Docker containers on the runner host (iSCSI + NFS) +# - 1x storage VM (VMID 5080) serving NFS, iSCSI, and the auto-install +# answer files over HTTP +# +# docker-compose.test.yml loads this file into both containers, so anything set +# here reaches Terraform (TF_VAR_*) and run-integration.sh alike. # ── Required: parent PVE for provisioning ───────────────────────────── PVE_ENDPOINT=https://pve.example.com:8006 PVE_API_TOKEN=user@realm!tokenid=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx -PVE_TARGET_NODE=pve1 PVE_PASSWORD= -# ── Optional overrides ──────────────────────────────────────────────── -# CACHE_DIR=/opt/pve-isos -# WORK_DIR=/tmp/pve-integration -# PVE_VERSIONS=9 8 +# Parent node to provision onto. "auto" picks the online node with the most +# free memory; the token's user then needs PVEAuditor on /nodes for the stats. +PVE_TARGET_NODE=auto + +# ── Required: Terraform storage and placement ───────────────────────── +# CI supplies these from GitHub repository variables. There are no defaults — +# terraform fails with "No value for required variable" if they are unset. +# iso_storage must accept the "iso" AND "import" content types. +TF_VAR_disk_storage= +TF_VAR_iso_storage= + +# ── Optional: match your parent cluster ─────────────────────────────── +# TF_VAR_network_bridge=Core +# TF_VAR_pool_id=ci # -# ── VMID overrides ─────────────────────────────────────────────────── +# DNS name of the storage VM. It boots via DHCP as hostname pvetest-storage +# and must resolve from the container and from the nested VMs' VLAN. +# STORAGE_VM_FQDN=pvetest-storage.test.local + +# ── Optional overrides ──────────────────────────────────────────────── +# The ISO CI pins. Leave unset and you provision a different PVE build than CI +# does — run-integration.sh defaults to 9.1. Keep in step with the PVE9_ISO in +# .github/workflows/integration-tests.yml. +# PVE9_ISO=proxmox-ve_9.2-1.iso +# +# CACHE_DIR=/opt/pve-integration +# WORK_DIR=/opt/pve-integration/work # PVE9A_VMID=5091 # PVE9B_VMID=5092 -# PVE8A_VMID=5081 -# PVE8B_VMID=5082 +# STORAGE_VMID=5080 diff --git a/tests/Dockerfile.test b/tests/Dockerfile.test index 9261022..e64e8e6 100644 --- a/tests/Dockerfile.test +++ b/tests/Dockerfile.test @@ -63,14 +63,3 @@ RUN apt-get update && apt-get install -y --no-install-recommends \ && apt-get update && apt-get install -y --no-install-recommends \ terraform proxmox-auto-install-assistant qemu-utils \ && rm -rf /var/lib/apt/lists/* - -# Install Docker CLI (for managing storage containers on the host via mounted socket) -RUN install -m 0755 -d /etc/apt/keyrings \ - && curl -fsSL https://download.docker.com/linux/ubuntu/gpg \ - | gpg --dearmor -o /etc/apt/keyrings/docker.gpg \ - && chmod a+r /etc/apt/keyrings/docker.gpg \ - && echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu noble stable" \ - > /etc/apt/sources.list.d/docker.list \ - && apt-get update \ - && apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \ - && rm -rf /var/lib/apt/lists/* diff --git a/tests/PSProxmoxVE.Tests/Integration/README.md b/tests/PSProxmoxVE.Tests/Integration/README.md index 9dc05a9..e047fd3 100644 --- a/tests/PSProxmoxVE.Tests/Integration/README.md +++ b/tests/PSProxmoxVE.Tests/Integration/README.md @@ -119,10 +119,20 @@ env: The integration suite is tagged `Integration`. Use the `-Tag` filter so that the unit tests and integration tests can be run independently. -### Via the dev container (recommended) +### Via the CI container (recommended) -```powershell -./tests/dev.ps1 integration +`run-integration.sh` provisions the nested PVE nodes, installs the module, and runs the +suite — the same path CI takes. x86 only. + +```bash +pve() { + docker compose -f tests/docker-compose.test.yml --profile infra run --rm dev-infra \ + bash tests/infrastructure/scripts/run-integration.sh "$@" +} + +pve provision 9 +pve test 9 Cluster,VMs # the area filter is optional +pve force-cleanup ``` ### Directly with Invoke-Pester diff --git a/tests/dev.ps1 b/tests/dev.ps1 deleted file mode 100644 index de004df..0000000 --- a/tests/dev.ps1 +++ /dev/null @@ -1,291 +0,0 @@ -#Requires -Version 5.1 -<# -.SYNOPSIS - Helper script for the dev/test containers. - -.DESCRIPTION - Manages Docker-based dev containers for building, testing, and running - integration tests for PSProxmoxVE. Works on Windows, macOS, and Linux. - - Use switches to compose actions: -Provision -Integration -Cleanup can - be combined in a single invocation. - - For x86-only commands (integration, provision, cleanup), use -DockerHost - to run containers on a remote Docker host via SSH. The script syncs the - repo to the remote host automatically. - -.PARAMETER Shell - Open an interactive pwsh shell in the dev container. - -.PARAMETER Build - Build the module inside the container. - -.PARAMETER Test - Run unit tests (Pester, excluding Integration tag). - -.PARAMETER Provision - Provision nested PVE VMs (x86 only). Required before -Integration - unless VMs are already running. - -.PARAMETER Integration - Run integration tests against provisioned PVE VMs. - -.PARAMETER Cleanup - Destroy provisioned VMs (x86 only). - -.PARAMETER Stop - Stop all containers. - -.PARAMETER Rebuild - Rebuild container images from scratch. - -.PARAMETER Reprovision - When used with -Provision, taints the PVE VMs in Terraform state - before applying, forcing them to be destroyed and recreated. - Useful when the VMs are in a bad state (e.g. clustered, broken). - -.PARAMETER Force - When used with -Cleanup, bypasses Terraform and destroys VMs - directly via the PVE API. Useful when Terraform state is corrupted - (e.g. after an interrupted provision). Also removes Terraform - state files so the next provision starts clean. - -.PARAMETER Tests - Filter integration tests by area name. Comma-separated list of test - area names that match the numbered file prefixes. Examples: - -Tests Connection,Nodes # runs 00_Connection + 01_Nodes - -Tests VMs,Snapshots # runs 06_VMs + 07_Snapshots - -Tests Cluster # runs 16_Cluster - When omitted, all integration test files are run. - -.PARAMETER Version - PVE version to test against (8, 9, or all). Default: all. - -.PARAMETER DockerHost - SSH destination for a remote Docker host (e.g. 172.16.40.113). - -.PARAMETER NoCleanup - When used with -Integration, skips cleanup after tests complete. - -.EXAMPLE - ./tests/dev.ps1 -Shell - # Opens a pwsh shell in the dev container - -.EXAMPLE - ./tests/dev.ps1 -Build -Test - # Builds the module and runs unit tests - -.EXAMPLE - ./tests/dev.ps1 -Provision -Integration -Cleanup -DockerHost 172.16.40.113 - # Full lifecycle: provision, test, cleanup on remote host - -.EXAMPLE - ./tests/dev.ps1 -Integration -Tests Connection,VMs -Version 9 -DockerHost 172.16.40.113 - # Run only Connection and VMs integration tests for PVE 9 - -.EXAMPLE - ./tests/dev.ps1 -Provision -Integration -Tests Cluster,HA -Version 9 -Cleanup -DockerHost 172.16.40.113 - # Provision, run cluster+HA tests for PVE 9, cleanup -#> -[CmdletBinding()] -param( - [switch] $Shell, - [switch] $Build, - [switch] $Test, - [switch] $Provision, - [switch] $Integration, - [switch] $Cleanup, - [switch] $Stop, - [switch] $Rebuild, - [switch] $Reprovision, - [switch] $Force, - - [string[]] $Tests, - - [Alias('PveVersion')] - [ValidateSet('8', '9', 'all')] - [string] $Version = 'all', - - [string] $DockerHost, - - [Alias('k')] - [switch] $NoCleanup -) - -$ErrorActionPreference = 'Stop' - -# If no action switches specified, default to -Shell -$anySwitchSet = $Shell -or $Build -or $Test -or $Provision -or $Integration -or $Cleanup -or $Stop -or $Rebuild -if (-not $anySwitchSet) { - if ($Force -or $Reprovision) { - throw "-Force and -Reprovision are modifiers — combine with an action switch (e.g. -Cleanup -Force, -Provision -Reprovision)." - } - $Shell = $true -} - -# Resolve repo root (parent of tests/) -$RepoRoot = Split-Path -Parent $PSScriptRoot -Push-Location $RepoRoot -try { - -# ── Remote Docker host support ──────────────────────────────────────── -$RemoteRepoPath = $null - -if ($DockerHost) { - $RemoteRepoPath = "/tmp/psproxmoxve-dev" - $env:DOCKER_HOST = "ssh://$DockerHost" - - Write-Host "Syncing repo to ${DockerHost}:${RemoteRepoPath}..." - ssh $DockerHost "mkdir -p $RemoteRepoPath" - if ($LASTEXITCODE -ne 0) { throw "Failed to create remote directory" } - - rsync -az --delete ` - --exclude 'bin/' ` - --exclude 'obj/' ` - --exclude 'publish/' ` - --exclude 'TestResults/' ` - --exclude '.terraform/' ` - --exclude 'terraform.tfstate*' ` - --include '.env.test' ` - ./ "${DockerHost}:${RemoteRepoPath}/" - if ($LASTEXITCODE -ne 0) { throw "Failed to sync repo to remote host" } - - Write-Host "Using remote Docker host: $DockerHost" -} - -$ComposeFile = 'tests/docker-compose.test.yml' -$ComposeArgs = @('-f', $ComposeFile) -$OverrideFile = $null - -if ($RemoteRepoPath) { - $OverrideFile = Join-Path ([System.IO.Path]::GetTempPath()) 'docker-compose.remote-override.yml' - @" -services: - dev: - volumes: - - ${RemoteRepoPath}:/repo - dev-infra: - volumes: - - ${RemoteRepoPath}:/repo - - /opt/pve-integration:/opt/pve-integration -"@ | Set-Content -Path $OverrideFile -Encoding utf8 - - $ComposeArgs = @('-f', $ComposeFile, '-f', $OverrideFile) -} - -$DevContainer = 'psproxmoxve-dev' -$InfraContainer = 'psproxmoxve-dev-infra' -$RunIntegration = 'tests/infrastructure/scripts/run-integration.sh' - -function Start-DevContainer { - docker compose @ComposeArgs up -d dev - if ($LASTEXITCODE -ne 0) { throw 'Failed to start dev container' } -} - -function Start-InfraContainer { - docker compose @ComposeArgs --profile infra up -d dev-infra - if ($LASTEXITCODE -ne 0) { throw 'Failed to start infra container (x86 only)' } -} - -function Invoke-BuildModule { - param([string] $Container) - docker exec $Container bash -c @" -dotnet publish src/PSProxmoxVE/PSProxmoxVE.csproj -c Release -f netstandard2.0 -o /tmp/publish 2>&1 | tail -1 && \ -cp -r /tmp/publish/* /usr/local/share/powershell/Modules/PSProxmoxVE/ && \ -echo 'Module installed to /usr/local/share/powershell/Modules/PSProxmoxVE' -"@ - if ($LASTEXITCODE -ne 0) { throw "Module build failed (exit code $LASTEXITCODE)" } -} - -# Build test filter argument for run-integration.sh -$TestFilter = '' -if ($Tests) { - $TestFilter = ($Tests -join ',') -} - -# ── Execute actions in order ────────────────────────────────────────── - -if ($Stop) { - docker compose @ComposeArgs --profile infra down -} - -if ($Rebuild) { - docker compose @ComposeArgs --profile infra down - docker compose @ComposeArgs build --no-cache dev - docker compose @ComposeArgs --profile infra build --no-cache dev-infra - docker compose @ComposeArgs up -d dev -} - -if ($Shell) { - if ($DockerHost) { - Write-Warning "Interactive shell over remote Docker is not supported. Use: ssh $DockerHost 'docker exec -it $DevContainer pwsh -NoProfile'" - return - } - Start-DevContainer - docker exec -it $DevContainer pwsh -NoProfile -} - -if ($Build) { - Start-DevContainer - Invoke-BuildModule $DevContainer -} - -if ($Test) { - Start-DevContainer - Invoke-BuildModule $DevContainer - docker exec $DevContainer pwsh -NoProfile -Command @' - $config = New-PesterConfiguration - $config.Run.Path = 'tests/PSProxmoxVE.Tests' - $config.Run.Exit = $true - $config.Filter.ExcludeTag = @('Integration') - $config.Output.Verbosity = 'Detailed' - Invoke-Pester -Configuration $config -'@ - if ($LASTEXITCODE -ne 0) { throw "Unit tests failed (exit code $LASTEXITCODE)" } -} - -if ($Provision) { - Start-InfraContainer - if ($Reprovision) { - docker exec $InfraContainer bash $RunIntegration taint $Version - if ($LASTEXITCODE -ne 0) { throw "Taint failed (exit code $LASTEXITCODE)" } - } - docker exec $InfraContainer bash $RunIntegration provision $Version - if ($LASTEXITCODE -ne 0) { throw "Provisioning failed (exit code $LASTEXITCODE)" } -} - -if ($Integration) { - Start-InfraContainer - - # Verify environment is ready (config file exists from provisioning) - $configCheck = docker exec $InfraContainer bash -c 'test -f "${CONFIG_FILE:-/opt/pve-integration/work/config.json}" && echo OK || echo MISSING' - if ($configCheck.Trim() -eq 'MISSING' -and -not $Provision) { - throw "Integration environment not ready. Run with -Provision first, or use -Provision -Integration together." - } - - docker exec $InfraContainer bash $RunIntegration test $Version $TestFilter - if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" } -} - -if ($Cleanup) { - Start-InfraContainer - if ($Force) { - if ($Version -ne 'all') { - throw "-Force cannot be combined with -Version. Force cleanup destroys all resources and wipes Terraform state." - } - docker exec $InfraContainer bash $RunIntegration force-cleanup - } else { - docker exec $InfraContainer bash $RunIntegration cleanup $Version - } - if ($LASTEXITCODE -ne 0) { throw "Cleanup failed (exit code $LASTEXITCODE)" } -} - -} finally { - if ($OverrideFile -and (Test-Path $OverrideFile)) { - Remove-Item $OverrideFile -Force -ErrorAction SilentlyContinue - } - if ($DockerHost) { - Remove-Item Env:\DOCKER_HOST -ErrorAction SilentlyContinue - } - Pop-Location -} diff --git a/tests/docker-compose.test.yml b/tests/docker-compose.test.yml index 11168c7..c0655d3 100644 --- a/tests/docker-compose.test.yml +++ b/tests/docker-compose.test.yml @@ -4,9 +4,11 @@ ## docker compose -f tests/docker-compose.test.yml up -d ## docker exec -it psproxmoxve-dev pwsh ## -## Full CI infra (x86 only — adds Terraform + PVE provisioning tools): -## docker compose -f tests/docker-compose.test.yml --profile infra up -d -## docker exec -it psproxmoxve-dev-infra bash +## Full CI infra (x86 only — adds Terraform + PVE provisioning tools). +## dev-infra is the same image CI runs its jobs in (Dockerfile.test target +## dev-infra), so the local flow is the CI flow: +## docker compose -f tests/docker-compose.test.yml --profile infra run --rm \ +## dev-infra bash tests/infrastructure/scripts/run-integration.sh provision 9 ## ## Stop: ## docker compose -f tests/docker-compose.test.yml down @@ -38,9 +40,6 @@ services: volumes: - ..:/repo - /opt/pve-integration:/opt/pve-integration - # WARNING: Mounting the Docker socket grants this container root-equivalent - # access to the host's Docker daemon. Only use on trusted, isolated machines. - - /var/run/docker.sock:/var/run/docker.sock working_dir: /repo stdin_open: true tty: true diff --git a/tests/infrastructure/runner/Dockerfile b/tests/infrastructure/runner/Dockerfile deleted file mode 100644 index 1dbab2f..0000000 --- a/tests/infrastructure/runner/Dockerfile +++ /dev/null @@ -1,106 +0,0 @@ -# ============================================================================= -# Dockerfile -# Self-hosted GitHub Actions runner for PSProxmoxVE integration tests. -# -# Build: -# docker build -t psproxmoxve-runner . -# -# Run: -# docker run -d \ -# -e REPO_URL=https://github.com/GoodOlClint/PSProxmoxVE \ -# -e RUNNER_TOKEN=AXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ -# -e RUNNER_LABELS=self-hosted,proxmox,integration \ -# -e RUNNER_NAME=docker-proxmox-runner \ -# --name psproxmoxve-runner \ -# psproxmoxve-runner -# -# The container must have network access to the Proxmox VE API under test. -# ============================================================================= -FROM ubuntu:24.04 - -LABEL maintainer="GoodOlClint" -LABEL description="Self-hosted GitHub Actions runner for PSProxmoxVE integration tests" - -# Avoid interactive prompts during package installation. -ENV DEBIAN_FRONTEND=noninteractive - -# --------------------------------------------------------------------------- -# 1. System packages -# --------------------------------------------------------------------------- -RUN apt-get update -qq && \ - apt-get install -y -qq --no-install-recommends \ - curl \ - jq \ - git \ - wget \ - apt-transport-https \ - software-properties-common \ - lsb-release \ - ca-certificates \ - gnupg \ - unzip \ - sudo \ - iputils-ping \ - && rm -rf /var/lib/apt/lists/* - -# --------------------------------------------------------------------------- -# 2. .NET SDK 10.0 -# --------------------------------------------------------------------------- -RUN wget -q "https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb" \ - -O /tmp/packages-microsoft-prod.deb && \ - dpkg -i /tmp/packages-microsoft-prod.deb && \ - rm -f /tmp/packages-microsoft-prod.deb && \ - apt-get update -qq && \ - apt-get install -y -qq --no-install-recommends dotnet-sdk-10.0 && \ - rm -rf /var/lib/apt/lists/* - -# --------------------------------------------------------------------------- -# 3. PowerShell 7.x -# --------------------------------------------------------------------------- -RUN apt-get update -qq && \ - apt-get install -y -qq --no-install-recommends powershell && \ - rm -rf /var/lib/apt/lists/* - -# --------------------------------------------------------------------------- -# 4. Terraform -# --------------------------------------------------------------------------- -RUN wget -qO- https://apt.releases.hashicorp.com/gpg | \ - gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg && \ - echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com noble main" \ - > /etc/apt/sources.list.d/hashicorp.list && \ - apt-get update -qq && \ - apt-get install -y -qq --no-install-recommends terraform && \ - rm -rf /var/lib/apt/lists/* - -# --------------------------------------------------------------------------- -# 5. Create runner user -# --------------------------------------------------------------------------- -RUN useradd -m -s /bin/bash github-runner && \ - echo "github-runner ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers.d/github-runner - -# --------------------------------------------------------------------------- -# 6. Download GitHub Actions runner -# --------------------------------------------------------------------------- -ENV RUNNER_DIR=/opt/github-runner - -RUN mkdir -p "$RUNNER_DIR" && \ - RUNNER_VERSION=$(curl -fsSL https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name' | sed 's/^v//') && \ - curl -fsSL "https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz" \ - -o /tmp/runner.tar.gz && \ - tar xzf /tmp/runner.tar.gz -C "$RUNNER_DIR" && \ - rm -f /tmp/runner.tar.gz && \ - chown -R github-runner:github-runner "$RUNNER_DIR" - -# Install runner dependencies (the runner ships a script for this). -RUN "$RUNNER_DIR/bin/installdependencies.sh" - -# --------------------------------------------------------------------------- -# 7. Entrypoint -# --------------------------------------------------------------------------- -COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh - -USER github-runner -WORKDIR /opt/github-runner - -ENTRYPOINT ["/entrypoint.sh"] diff --git a/tests/infrastructure/runner/README.md b/tests/infrastructure/runner/README.md deleted file mode 100644 index 4c6e2b1..0000000 --- a/tests/infrastructure/runner/README.md +++ /dev/null @@ -1,205 +0,0 @@ -# Self-Hosted GitHub Actions Runner for PSProxmoxVE - -## Overview - -The PSProxmoxVE integration tests require network access to a live Proxmox VE API. A self-hosted GitHub Actions runner, deployed on or near your Proxmox host, lets pushes to GitHub automatically trigger these tests without exposing your PVE management interface to the public internet. - -This directory contains everything needed to set up such a runner. - -## Option A: LXC Container (Recommended) - -Running the runner inside an LXC container on the Proxmox host itself is the simplest approach. The container has direct network access to the PVE API with no extra networking required. - -### 1. Create the LXC container - -From the Proxmox host shell (or the web UI): - -```bash -# Download a Debian 12 template if you don't already have one -pveam download local debian-12-standard_12.7-1_amd64.tar.zst - -# Create a privileged container -pct create 900 local:vztmpl/debian-12-standard_12.7-1_amd64.tar.zst \ - --hostname github-runner \ - --cores 2 \ - --memory 4096 \ - --swap 1024 \ - --rootfs local-lvm:20 \ - --net0 name=eth0,bridge=vmbr0,ip=dhcp \ - --unprivileged 0 \ - --features nesting=1 \ - --start 1 -``` - -Adjust the container ID (900), storage, and network bridge to match your environment. - -**Recommended resources:** -- 2 CPU cores -- 4 GB RAM -- 20 GB disk - -### 2. Run the setup script - -Enter the container and run the setup script: - -```bash -pct enter 900 - -apt-get update && apt-get install -y curl -curl -fsSL https://raw.githubusercontent.com/GoodOlClint/PSProxmoxVE/main/tests/infrastructure/runner/setup-runner.sh \ - -o /tmp/setup-runner.sh -chmod +x /tmp/setup-runner.sh - -/tmp/setup-runner.sh \ - --repo GoodOlClint/PSProxmoxVE \ - --token \ - --labels self-hosted,proxmox,integration -``` - -See [GitHub Configuration](#github-configuration) below for how to obtain the registration token. - -### 3. Verify - -The runner should appear as **Online** in your repository under **Settings > Actions > Runners** within a minute. - -## Option B: Docker Container - -If you prefer Docker, or want to run the runner on a different machine that has network access to your PVE host: - -### 1. Build the image - -```bash -cd tests/infrastructure/runner -docker build -t psproxmoxve-runner . -``` - -### 2. Run the container - -```bash -docker run -d \ - -e REPO_URL=https://github.com/GoodOlClint/PSProxmoxVE \ - -e RUNNER_TOKEN= \ - -e RUNNER_LABELS=self-hosted,proxmox,integration \ - -e RUNNER_NAME=docker-proxmox-runner \ - --name psproxmoxve-runner \ - --restart unless-stopped \ - psproxmoxve-runner -``` - -For ephemeral (single-job) mode, add `-e EPHEMERAL=true`. The container will exit after completing one job; combine with `--restart always` to re-register automatically for the next job. - -### 3. Verify - -```bash -docker logs -f psproxmoxve-runner -``` - -You should see the runner register and begin listening for jobs. - -## GitHub Configuration - -### Obtaining a Registration Token - -1. Navigate to your repository on GitHub. -2. Go to **Settings > Actions > Runners**. -3. Click **New self-hosted runner**. -4. Copy the registration token shown in the configuration instructions. - -> **Note:** Registration tokens expire after one hour. Generate a new one if yours has expired. - -### Required Labels - -The integration test workflow targets runners with the label `integration`. The setup script applies the following labels by default: - -- `self-hosted` -- `proxmox` -- `integration` - -You can override these with the `--labels` flag or `RUNNER_LABELS` environment variable. - -## Repository Secrets - -The integration tests read connection details from GitHub Actions secrets. Configure these in **Settings > Secrets and variables > Actions**: - -| Secret | Description | Example | -|---|---|---| -| `PVETEST_HOST` | IP or hostname of the Proxmox VE host (or a nested test PVE instance) | `192.168.1.100` | -| `PVETEST_PORT` | PVE API port | `8006` | -| `PVETEST_APITOKEN` | API token in `user@realm!tokenid=secret` format | `testuser@pve!ci=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` | -| `PVETEST_NODE` | Proxmox node name to run tests against | `pve` | -| `PVETEST_STORAGE` | Storage ID for upload/ISO tests | `local` | -| `PVETEST_ISO_PATH` | Path on the runner to a small test ISO file | `/opt/test-assets/test.iso` | - -### Creating a Dedicated API Token - -It is strongly recommended to create a dedicated, least-privilege API token for testing: - -```bash -# On the Proxmox host -pveum user add testuser@pve --password -pveum role add CITestRole --privs "VM.Allocate VM.Audit VM.Config.Disk VM.Config.CPU VM.Config.Memory VM.Config.Network VM.Config.Options VM.PowerMgmt Datastore.AllocateSpace Datastore.Audit SDN.Use" -pveum aclmod / --user testuser@pve --role CITestRole -pveum user token add testuser@pve ci --privsep 0 -``` - -The last command outputs the token secret -- store it as the `PVETEST_APITOKEN` secret. - -## Security Considerations - -- **Network isolation.** The runner has access to your local network. If possible, place it on a dedicated test VLAN that can only reach the PVE API and the internet (for downloading runner updates and GitHub communication). - -- **Dedicated API token.** Use a purpose-built API token with only the permissions the tests need. Never use `root@pam`. - -- **Registration token is single-use.** The GitHub registration token is consumed during setup and cannot be reused. A new token is needed only to re-register or remove the runner. - -- **Ephemeral runners.** For stronger isolation, use `--ephemeral` (setup script) or `-e EPHEMERAL=true` (Docker). The runner handles one job and then deregisters. This prevents state leakage between workflow runs. - -- **Repository scope.** Self-hosted runners registered at the repository level only receive jobs from that repository. Do not register at the organization level unless you understand the implications. - -- **Keep the host updated.** Regularly apply OS security patches to the runner container or VM. - -## Maintenance - -### Runner Auto-Updates - -The GitHub Actions runner automatically updates itself when GitHub releases a new version. No manual intervention is required. - -### Checking Runner Status - -```bash -# LXC / bare metal (systemd service) -systemctl status actions.runner.* - -# Docker -docker logs psproxmoxve-runner -``` - -### Removing the Runner - -**LXC / bare metal:** - -```bash -cd /opt/github-runner -sudo ./svc.sh stop -sudo ./svc.sh uninstall -./config.sh remove --token -``` - -Generate a removal token from **Settings > Actions > Runners** by clicking the runner name. - -**Docker:** - -```bash -docker stop psproxmoxve-runner -docker rm psproxmoxve-runner -``` - -If the container was not stopped gracefully (which triggers automatic deregistration), remove the runner manually from **Settings > Actions > Runners** in the GitHub UI. - -### Reinstalling / Re-registering - -If you need to re-register the runner (e.g., after moving it to a new host): - -1. Remove the old registration (see above). -2. Generate a new registration token from GitHub. -3. Run the setup script or Docker container again with the new token. diff --git a/tests/infrastructure/runner/entrypoint.sh b/tests/infrastructure/runner/entrypoint.sh deleted file mode 100755 index 314e43c..0000000 --- a/tests/infrastructure/runner/entrypoint.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# entrypoint.sh -# Docker entrypoint for the self-hosted GitHub Actions runner. -# -# Required environment variables: -# REPO_URL - Full GitHub repository URL (e.g. https://github.com/GoodOlClint/PSProxmoxVE) -# RUNNER_TOKEN - Registration token from GitHub Settings > Actions > Runners -# -# Optional environment variables: -# RUNNER_LABELS - Comma-separated labels (default: self-hosted,proxmox,integration) -# RUNNER_NAME - Runner name (default: hostname) -# RUNNER_GROUP - Runner group (default: Default) -# EPHEMERAL - Set to "true" for single-job ephemeral mode (default: false) -# ============================================================================= -set -euo pipefail - -RUNNER_LABELS="${RUNNER_LABELS:-self-hosted,proxmox,integration}" -RUNNER_NAME="${RUNNER_NAME:-$(hostname)}" -RUNNER_GROUP="${RUNNER_GROUP:-Default}" -EPHEMERAL="${EPHEMERAL:-false}" - -# --------------------------------------------------------------------------- -# Validate required environment variables -# --------------------------------------------------------------------------- -if [[ -z "${REPO_URL:-}" ]]; then - echo "ERROR: REPO_URL environment variable is required." - echo " Example: -e REPO_URL=https://github.com/GoodOlClint/PSProxmoxVE" - exit 1 -fi - -if [[ -z "${RUNNER_TOKEN:-}" ]]; then - echo "ERROR: RUNNER_TOKEN environment variable is required." - echo " Get one from: ${REPO_URL}/settings/actions/runners/new" - exit 1 -fi - -# --------------------------------------------------------------------------- -# Configure the runner -# --------------------------------------------------------------------------- -CONFIG_ARGS=( - --url "$REPO_URL" - --token "$RUNNER_TOKEN" - --labels "$RUNNER_LABELS" - --name "$RUNNER_NAME" - --runnergroup "$RUNNER_GROUP" - --work _work - --unattended - --replace -) - -if [[ "$EPHEMERAL" == "true" ]]; then - CONFIG_ARGS+=(--ephemeral) - echo "Ephemeral mode enabled -- runner will exit after one job." -fi - -echo "Configuring runner..." -echo " Repository: $REPO_URL" -echo " Name: $RUNNER_NAME" -echo " Labels: $RUNNER_LABELS" - -/opt/github-runner/config.sh "${CONFIG_ARGS[@]}" - -# --------------------------------------------------------------------------- -# Deregister on shutdown (best-effort) -# --------------------------------------------------------------------------- -cleanup() { - echo "" - echo "Caught signal -- removing runner registration..." - /opt/github-runner/config.sh remove --token "$RUNNER_TOKEN" || true -} -trap cleanup SIGTERM SIGINT - -# --------------------------------------------------------------------------- -# Start the runner -# --------------------------------------------------------------------------- -echo "Starting runner..." -/opt/github-runner/run.sh & -wait $! diff --git a/tests/infrastructure/runner/setup-runner.sh b/tests/infrastructure/runner/setup-runner.sh deleted file mode 100755 index fb46b15..0000000 --- a/tests/infrastructure/runner/setup-runner.sh +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env bash -# ============================================================================= -# setup-runner.sh -# Sets up a self-hosted GitHub Actions runner for PSProxmoxVE integration tests. -# -# Intended to run inside a Debian 12 or Ubuntu 24.04 environment (LXC, VM, or -# bare metal) that has network access to the Proxmox VE API under test. -# -# Prerequisites: root or sudo access, internet connectivity. -# -# Usage: -# sudo ./setup-runner.sh \ -# --repo GoodOlClint/PSProxmoxVE \ -# --token AXXXXXXXXXXXXXXXXXXXXXXXXXXXX \ -# --labels self-hosted,proxmox,integration -# -# Read through the script before running it -- no surprises. -# ============================================================================= -set -euo pipefail - -# --------------------------------------------------------------------------- -# Parse arguments -# --------------------------------------------------------------------------- -REPO="" -TOKEN="" -LABELS="self-hosted,proxmox,integration" -RUNNER_DIR="/opt/github-runner" -RUNNER_USER="github-runner" - -while [[ $# -gt 0 ]]; do - case $1 in - --repo) REPO="$2"; shift 2 ;; - --token) TOKEN="$2"; shift 2 ;; - --labels) LABELS="$2"; shift 2 ;; - --help|-h) - echo "Usage: $0 --repo OWNER/REPO --token TOKEN [--labels LABELS]" - echo "" - echo " --repo GitHub repository (e.g. GoodOlClint/PSProxmoxVE)" - echo " --token Runner registration token from GitHub Settings > Actions > Runners" - echo " --labels Comma-separated labels (default: self-hosted,proxmox,integration)" - exit 0 - ;; - *) echo "Unknown option: $1"; exit 1 ;; - esac -done - -# --------------------------------------------------------------------------- -# Validate required arguments -# --------------------------------------------------------------------------- -[[ -z "$REPO" ]] && { echo "ERROR: --repo required (e.g. GoodOlClint/PSProxmoxVE)"; exit 1; } -[[ -z "$TOKEN" ]] && { echo "ERROR: --token required (get from GitHub Settings > Actions > Runners)"; exit 1; } - -# --------------------------------------------------------------------------- -# Detect OS -# --------------------------------------------------------------------------- -if [[ -f /etc/os-release ]]; then - # shellcheck disable=SC1091 - source /etc/os-release - echo "Detected OS: $PRETTY_NAME" -else - echo "WARNING: Cannot detect OS. Proceeding assuming Debian/Ubuntu." -fi - -# --------------------------------------------------------------------------- -# Helper: retry a command up to N times -# --------------------------------------------------------------------------- -retry() { - local retries=$1; shift - local count=0 - until "$@"; do - count=$((count + 1)) - if [[ $count -ge $retries ]]; then - echo "ERROR: Command failed after $retries attempts: $*" - return 1 - fi - echo "Retry $count/$retries..." - sleep 3 - done -} - -# --------------------------------------------------------------------------- -# 1. System packages -# --------------------------------------------------------------------------- -echo "" -echo "=== Installing system prerequisites ===" -export DEBIAN_FRONTEND=noninteractive -apt-get update -qq -apt-get install -y -qq curl jq git wget apt-transport-https software-properties-common \ - lsb-release ca-certificates gnupg unzip - -# --------------------------------------------------------------------------- -# 2. .NET SDK 10.0 -# --------------------------------------------------------------------------- -echo "" -echo "=== Installing .NET SDK 10.0 ===" -# Microsoft package repository -wget -q "https://packages.microsoft.com/config/$(lsb_release -is | tr '[:upper:]' '[:lower:]')/$(lsb_release -rs)/packages-microsoft-prod.deb" \ - -O /tmp/packages-microsoft-prod.deb -dpkg -i /tmp/packages-microsoft-prod.deb -rm -f /tmp/packages-microsoft-prod.deb -apt-get update -qq -apt-get install -y -qq dotnet-sdk-10.0 - -echo " .NET version: $(dotnet --version)" - -# --------------------------------------------------------------------------- -# 3. PowerShell 7.x -# --------------------------------------------------------------------------- -echo "" -echo "=== Installing PowerShell 7.x ===" -# PowerShell is available from the Microsoft repository added above. -apt-get install -y -qq powershell - -echo " PowerShell version: $(pwsh -NoProfile -Command '$PSVersionTable.PSVersion.ToString()')" - -# --------------------------------------------------------------------------- -# 4. Terraform -# --------------------------------------------------------------------------- -echo "" -echo "=== Installing Terraform ===" -wget -qO- https://apt.releases.hashicorp.com/gpg | gpg --dearmor -o /usr/share/keyrings/hashicorp-archive-keyring.gpg -echo "deb [signed-by=/usr/share/keyrings/hashicorp-archive-keyring.gpg] https://apt.releases.hashicorp.com $(lsb_release -cs) main" \ - > /etc/apt/sources.list.d/hashicorp.list -apt-get update -qq -apt-get install -y -qq terraform - -echo " Terraform version: $(terraform version -json | jq -r '.terraform_version')" - -# --------------------------------------------------------------------------- -# 5. Create runner service account -# --------------------------------------------------------------------------- -echo "" -echo "=== Creating runner service account ===" -if ! id "$RUNNER_USER" &>/dev/null; then - useradd -m -s /bin/bash "$RUNNER_USER" - echo " Created user: $RUNNER_USER" -else - echo " User $RUNNER_USER already exists" -fi - -# --------------------------------------------------------------------------- -# 6. Download and configure GitHub Actions runner -# --------------------------------------------------------------------------- -echo "" -echo "=== Downloading GitHub Actions runner ===" -mkdir -p "$RUNNER_DIR" - -# Determine the latest runner version from the GitHub API. -RUNNER_VERSION=$(curl -fsSL https://api.github.com/repos/actions/runner/releases/latest | jq -r '.tag_name' | sed 's/^v//') -RUNNER_ARCH="x64" -RUNNER_TAR="actions-runner-linux-${RUNNER_ARCH}-${RUNNER_VERSION}.tar.gz" -RUNNER_URL="https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/${RUNNER_TAR}" - -echo " Runner version: $RUNNER_VERSION" -echo " Download URL: $RUNNER_URL" - -if [[ ! -f "$RUNNER_DIR/.runner" ]]; then - curl -fsSL "$RUNNER_URL" -o "/tmp/$RUNNER_TAR" - tar xzf "/tmp/$RUNNER_TAR" -C "$RUNNER_DIR" - rm -f "/tmp/$RUNNER_TAR" -else - echo " Runner already extracted -- skipping download" -fi - -chown -R "$RUNNER_USER":"$RUNNER_USER" "$RUNNER_DIR" - -# --------------------------------------------------------------------------- -# 7. Configure the runner -# --------------------------------------------------------------------------- -echo "" -echo "=== Configuring runner ===" -cd "$RUNNER_DIR" - -# Run config as the service account. -sudo -u "$RUNNER_USER" ./config.sh \ - --url "https://github.com/$REPO" \ - --token "$TOKEN" \ - --labels "$LABELS" \ - --name "$(hostname)-proxmox-runner" \ - --work _work \ - --unattended \ - --replace - -# --------------------------------------------------------------------------- -# 8. Install and start the systemd service -# --------------------------------------------------------------------------- -echo "" -echo "=== Installing as systemd service ===" -./svc.sh install "$RUNNER_USER" -./svc.sh start - -echo "" -echo "=== Runner setup complete ===" -echo " Runner directory: $RUNNER_DIR" -echo " Service user: $RUNNER_USER" -echo " Labels: $LABELS" -echo " Repository: https://github.com/$REPO" -echo "" -echo "Verify the runner appears in your repository under:" -echo " Settings > Actions > Runners" -echo "" -echo "To remove this runner later:" -echo " cd $RUNNER_DIR" -echo " ./svc.sh stop" -echo " ./svc.sh uninstall" -echo " ./config.sh remove --token " diff --git a/tests/infrastructure/scripts/preflight-cleanup.sh b/tests/infrastructure/scripts/preflight-cleanup.sh index 760a7b8..36b9e9d 100755 --- a/tests/infrastructure/scripts/preflight-cleanup.sh +++ b/tests/infrastructure/scripts/preflight-cleanup.sh @@ -19,7 +19,7 @@ if [ -z "$NODE" ]; then NODES_JSON=$(curl -sk -H "Authorization: PVEAPIToken=${API_TOKEN}" "${API_BASE}/nodes" 2>/dev/null) NODE=$(echo "$NODES_JSON" | python3 -c "import json,sys; print(json.load(sys.stdin)['data'][0]['node'])" 2>/dev/null || echo "pve") fi -ISO_STORAGE="${TF_VAR_iso_storage:-local}" +ISO_STORAGE="${TF_VAR_iso_storage:-}" echo "=== Pre-flight cleanup (node: ${NODE}, vmid: ${VM_ID}) ===" @@ -55,6 +55,13 @@ fi # --- Clean up orphaned ISO --- if [ -z "$ISO_FILENAME" ]; then echo "No ISO filename specified, skipping ISO cleanup" +elif [ -z "$ISO_STORAGE" ]; then + # force-cleanup is the only cleanup CI runs and it wipes Terraform state, so a + # skipped ISO delete here strands the upload with nothing left to reclaim it. + if [ "${GITHUB_ACTIONS:-}" = "true" ]; then + echo "::warning::TF_VAR_iso_storage is unset — ISO cleanup skipped; the uploaded auto-install ISO is stranded" + fi + echo "WARNING: TF_VAR_iso_storage is unset — skipping ISO cleanup rather than guessing a storage pool" else ISO_EXISTS=$(curl -sk -H "Authorization: PVEAPIToken=${API_TOKEN}" \ "${API_BASE}/nodes/${NODE}/storage/${ISO_STORAGE}/content" 2>/dev/null \ diff --git a/tests/infrastructure/scripts/run-integration.sh b/tests/infrastructure/scripts/run-integration.sh index 351cc9f..eeaae73 100644 --- a/tests/infrastructure/scripts/run-integration.sh +++ b/tests/infrastructure/scripts/run-integration.sh @@ -24,6 +24,12 @@ # PVE_ENDPOINT Parent PVE API URL (e.g. https://pve.example.com:8006) # PVE_API_TOKEN Parent PVE API token # PVE_PASSWORD Root password for nested PVE instances +# TF_VAR_disk_storage Storage pool for VM disks; must support raw format +# TF_VAR_iso_storage Storage pool for uploads; must accept the iso AND +# import content types +# +# The two TF_VAR_* values have no Terraform defaults, and this script removes +# terraform.tfvars before applying, so the environment is the only channel. # # Required env vars (test with pre-existing PVE): # PVETEST_HOST PVE host IP (node A) @@ -233,6 +239,8 @@ cmd_provision() { require_env PVE_ENDPOINT require_env PVE_API_TOKEN require_env PVE_PASSWORD + require_env TF_VAR_disk_storage + require_env TF_VAR_iso_storage resolve_target_node "$(wc -w <<<"$provision_nodes")" ci_mask "$PVE_PASSWORD" @@ -622,6 +630,8 @@ cmd_cleanup() { PVE_TARGET_NODE="$(cat "$TARGET_NODE_FILE" 2>/dev/null || true)" fi require_env PVE_TARGET_NODE + require_env TF_VAR_disk_storage + require_env TF_VAR_iso_storage # Build tfvars for all versions (Terraform needs the full variable map) local tfvars="$WORK_DIR/instances.tfvars.json" diff --git a/tests/infrastructure/terraform.tfvars.example b/tests/infrastructure/terraform.tfvars.example index e56fee4..1dfe8d7 100644 --- a/tests/infrastructure/terraform.tfvars.example +++ b/tests/infrastructure/terraform.tfvars.example @@ -3,13 +3,23 @@ proxmox_endpoint = "https://pve.example.com:8006" proxmox_api_token = "root@pam!terraform=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" target_node = "pve" -# Local path to the prepared auto-install ISO (output of prepare-auto-iso.sh) -iso_local_path = "/tmp/proxmox-ve_9.1-1-auto.iso" -disk_storage = "local-lvm" +test_vm_password = "root-password-for-the-nested-pve-nodes" + +# Storage pools. Both are required — there are no defaults. +# iso_storage must accept the "iso" AND "import" content types. +# +# NOTE: this file is only read when you invoke terraform by hand. +# run-integration.sh deletes terraform.tfvars before applying; for that path +# set TF_VAR_disk_storage / TF_VAR_iso_storage in tests/.env.test instead. +# +# pve_instances and pve_isos are required too, but are machine-generated — +# run-integration.sh builds them into $WORK_DIR/instances.tfvars.json and +# passes that with -var-file. A by-hand apply needs the same file. +disk_storage = "local-lvm" +iso_storage = "local" # Optional overrides # cores = 4 # memory = 8192 # disk_size = 64 # network_bridge = "vmbr0" -# iso_storage = "local" diff --git a/tests/infrastructure/variables.tf b/tests/infrastructure/variables.tf index 86cc09f..db3f9fd 100644 --- a/tests/infrastructure/variables.tf +++ b/tests/infrastructure/variables.tf @@ -55,15 +55,13 @@ variable "disk_size" { } variable "disk_storage" { - description = "Proxmox storage pool for VM disks (must support raw format)" + description = "Proxmox storage pool for VM disks (must support raw format). Required, no default. run-integration.sh removes terraform.tfvars before applying, so set TF_VAR_disk_storage in the environment (tests/.env.test)." type = string - default = "nas-iSCSI-lvm" } variable "iso_storage" { - description = "Proxmox storage pool for uploads (must accept the iso AND import content types — import is not enabled by default on most storages)" + description = "Proxmox storage pool for uploads (must accept the iso AND import content types — import is not enabled by default on most storages). Required, no default. run-integration.sh removes terraform.tfvars before applying, so set TF_VAR_iso_storage in the environment (tests/.env.test)." type = string - default = "local" } variable "network_bridge" {