refactor: redesign dev.ps1 with switch-based CLI and -Tests filter

Replace positional $Command parameter with switches that can be combined:
  -Provision -Integration -Cleanup -DockerHost 172.16.40.113

New -Tests parameter filters integration tests by area name:
  -Tests Connection,VMs,Snapshots

New -Version parameter (alias for old PveVersion):
  -Version 9

Integration without Provision checks for config.json and errors if
environment is not ready.

Actions execute in logical order: Stop → Rebuild → Shell → Build →
Test → Provision → Integration → Cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Clint Branham
2026-03-25 11:53:24 -05:00
parent 12874d168d
commit f62e02e2a2
+108 -96
View File
@@ -7,87 +7,111 @@
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 Command
The action to perform:
shell - Open pwsh in the dev container (default)
build - Build the module inside the container
test - Run unit tests (ARM + x86)
integration - Provision nested PVE VMs, run integration tests, cleanup (x86 only)
provision - Provision nested PVE VMs only, no tests (x86 only)
cleanup - Destroy provisioned VMs (x86 only)
stop - Stop all containers
rebuild - Rebuild container image(s)
.PARAMETER Shell
Open an interactive pwsh shell in the dev container.
.PARAMETER PveVersion
.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 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.
Only used with 'integration' and 'provision' commands.
.PARAMETER NoCleanup
When used with 'integration', skips cleanup after tests complete.
Nested PVE VMs are left running for inspection or re-testing.
Run './tests/dev.ps1 cleanup' to destroy them later.
.PARAMETER DockerHost
SSH destination for a remote Docker host (e.g. user@runner-vm).
When set, the repo is rsynced to the remote host and all Docker
commands run against the remote daemon. Useful for running x86
containers from an ARM Mac.
SSH destination for a remote Docker host (e.g. 172.16.40.113).
Prerequisites on the remote host:
- Docker installed
- SSH user in the 'docker' group (sudo usermod -aG docker $USER)
- SSH key-based auth from the local machine
.PARAMETER NoCleanup
When used with -Integration, skips cleanup after tests complete.
.EXAMPLE
./tests/dev.ps1
./tests/dev.ps1 -Shell
# Opens a pwsh shell in the dev container
.EXAMPLE
./tests/dev.ps1 test
./tests/dev.ps1 -Build -Test
# Builds the module and runs unit tests
.EXAMPLE
./tests/dev.ps1 integration -DockerHost user@runner-vm
# Syncs repo to runner-vm, provisions nested PVE, runs tests, cleans up
./tests/dev.ps1 -Provision -Integration -Cleanup -DockerHost 172.16.40.113
# Full lifecycle: provision, test, cleanup on remote host
.EXAMPLE
./tests/dev.ps1 integration 9 -DockerHost user@runner-vm
# Same but only for PVE 9
./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(
[Parameter(Position = 0)]
[ValidateSet('shell', 'build', 'test', 'integration', 'provision', 'cleanup', 'stop', 'rebuild')]
[string] $Command = 'shell',
[switch] $Shell,
[switch] $Build,
[switch] $Test,
[switch] $Provision,
[switch] $Integration,
[switch] $Cleanup,
[switch] $Stop,
[switch] $Rebuild,
[Parameter(Position = 1)]
[string] $PveVersion = 'all',
[string[]] $Tests,
[Alias('PveVersion')]
[string] $Version = 'all',
[Parameter()]
[string] $DockerHost,
[Parameter()]
[Alias('k')]
[switch] $NoCleanup
)
$ErrorActionPreference = 'Stop'
# If no switches specified, default to -Shell
$anySwitchSet = $Shell -or $Build -or $Test -or $Provision -or $Integration -or $Cleanup -or $Stop -or $Rebuild
if (-not $anySwitchSet) {
$Shell = $true
}
# Resolve repo root (parent of tests/)
$RepoRoot = Split-Path -Parent $PSScriptRoot
Push-Location $RepoRoot
try {
# ── Remote Docker host support ────────────────────────────────────────
# When -DockerHost is specified, we rsync the repo (including .env.test)
# to the remote host and set DOCKER_HOST so all docker/compose commands
# execute on the remote daemon. The compose file's volume mount (..:/repo)
# and build context reference the remote copy.
$RemoteRepoPath = $null
if ($DockerHost) {
@@ -95,11 +119,9 @@ if ($DockerHost) {
$env:DOCKER_HOST = "ssh://$DockerHost"
Write-Host "Syncing repo to ${DockerHost}:${RemoteRepoPath}..."
# Create remote directory
ssh $DockerHost "mkdir -p $RemoteRepoPath"
if ($LASTEXITCODE -ne 0) { throw "Failed to create remote directory" }
# Rsync repo to remote host, excluding build artifacts
rsync -az --delete `
--exclude 'bin/' `
--exclude 'obj/' `
@@ -114,19 +136,12 @@ if ($DockerHost) {
Write-Host "Using remote Docker host: $DockerHost"
}
# When running remotely, docker compose reads the compose file locally but
# volume mounts resolve on the remote host. We generate a temporary override
# file that remaps volume mounts to the rsynced remote path.
$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'
# Override volume mounts to point at the rsynced remote repo path.
# env_file is NOT overridden — compose reads it locally and injects
# the values as container env vars, which is what we want.
@"
services:
dev:
@@ -146,7 +161,6 @@ $InfraContainer = 'psproxmoxve-dev-infra'
$RunIntegration = 'tests/infrastructure/scripts/run-integration.sh'
function Start-DevContainer {
# 'up -d' is idempotent: starts if stopped, recreates if config/env changed, no-ops if current
docker compose @ComposeArgs up -d dev
if ($LASTEXITCODE -ne 0) { throw 'Failed to start dev container' }
}
@@ -166,22 +180,40 @@ echo 'Module installed to /usr/local/share/powershell/Modules/PSProxmoxVE'
if ($LASTEXITCODE -ne 0) { throw "Module build failed (exit code $LASTEXITCODE)" }
}
switch ($Command) {
'shell' {
# 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'"
Write-Warning "Interactive shell over remote Docker is not supported. Use: ssh $DockerHost 'docker exec -it $InfraContainer pwsh -NoProfile'"
return
}
Start-DevContainer
docker exec -it $DevContainer pwsh -NoProfile
}
}
'build' {
if ($Build) {
Start-DevContainer
Invoke-BuildModule $DevContainer
}
}
'test' {
if ($Test) {
Start-DevContainer
Invoke-BuildModule $DevContainer
docker exec $DevContainer pwsh -NoProfile -Command @'
@@ -193,51 +225,31 @@ switch ($Command) {
Invoke-Pester -Configuration $config
'@
if ($LASTEXITCODE -ne 0) { throw "Unit tests failed (exit code $LASTEXITCODE)" }
}
}
'integration' {
# Full lifecycle: provision nested PVE VMs -> test -> cleanup (x86 only)
# Requires PVE_ENDPOINT, PVE_API_TOKEN, PVE_TARGET_NODE, PVE_PASSWORD in .env.test
# run-integration.sh handles module build if no pre-built artifact exists
Start-InfraContainer
if ($NoCleanup) {
# Provision and test separately — leave VMs running for inspection
docker exec $InfraContainer bash $RunIntegration provision
if ($LASTEXITCODE -ne 0) { throw "Provisioning failed (exit code $LASTEXITCODE)" }
docker exec $InfraContainer bash $RunIntegration test $PveVersion
if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" }
Write-Host 'VMs left running (-NoCleanup). Run ./tests/dev.ps1 cleanup to destroy them.'
} else {
docker exec $InfraContainer bash $RunIntegration all $PveVersion
if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" }
}
}
'provision' {
# Provision nested PVE VMs only, without running tests (x86 only)
# Useful for iterating: provision once, then shell in and run tests manually
if ($Provision) {
Start-InfraContainer
docker exec $InfraContainer bash $RunIntegration provision
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 /tmp/pve-integration/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."
}
'cleanup' {
# Destroy provisioned VMs
docker exec $InfraContainer bash $RunIntegration test $Version $TestFilter
if ($LASTEXITCODE -ne 0) { throw "Integration tests failed (exit code $LASTEXITCODE)" }
}
if ($Cleanup) {
Start-InfraContainer
docker exec $InfraContainer bash $RunIntegration cleanup
if ($LASTEXITCODE -ne 0) { throw "Cleanup failed (exit code $LASTEXITCODE)" }
}
'stop' {
docker compose @ComposeArgs --profile infra down
}
'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
}
}
} finally {