Force cleanup must remove pvetest-* Docker containers unconditionally,
not just when cleaning all versions. Stale containers cause Terraform
to fail on next provision ("container already exists") since the state
was also wiped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace 4 per-node auto-install ISOs (~4GB) with 2 generic ISOs
(~2GB) served by an HTTP answer server that routes per-node configs
by MAC address.
New flow:
1. Deterministic MAC addresses assigned per node (AA:BB:CC:00:VV:NN)
2. Per-MAC answer.toml files generated in answers/ directory
3. HTTP answer server (slothcroissant/proxmox-auto-installer-server)
managed by Terraform, serves answer files on port 8000
4. Generic ISOs prepared with --fetch-from http --url pointing to
the answer server
5. PVE installer POSTs system info, server matches MAC to answer file
Benefits:
- 50% reduction in ISO disk/tmp usage (2 ISOs instead of 4)
- Faster ISO preparation (2 builds instead of 4)
- Answer files can be updated without rebuilding ISOs
- First-boot script embedded in generic ISO via --on-first-boot
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When Terraform state is corrupted (e.g. interrupted provision),
-Cleanup -Force bypasses Terraform and:
1. Destroys VMs via direct PVE API calls (preflight-cleanup.sh)
2. Force-removes Docker storage containers and volumes
3. Deletes Terraform state files so next provision starts clean
Usage: dev.ps1 -Cleanup -Force -DockerHost 172.16.40.113
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When provisioning a subset of versions (-Version 9), the -target flags
only included PVE VM resources. Docker storage containers (iSCSI, NFS)
were skipped because they weren't targeted. Now always includes all
Docker resources in the target list since storage is shared across
all PVE versions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Major provisioning pipeline changes:
Terraform:
- Add kreuzwerker/docker provider to manage iSCSI and NFS storage
containers alongside PVE VMs in a single Terraform config
- New storage.tf with Docker container, image, and volume resources
- docker_host_ip variable for PVE nodes to reach storage services
Provisioning:
- Replace docker-compose storage management with Terraform
- Replace create-api-token.sh with wait-for-pve.sh (IP discovery +
API readiness + auth verification only — no token creation)
- Tests use root@pam credentials, not API tokens
Cleanup:
- Replace preflight-cleanup.sh loop with terraform destroy
- Supports version filtering: cleanup 9 destroys only PVE 9 resources
- Full cleanup also removes config.json and tfvars
New commands:
- taint [8|9|all]: marks VMs for recreation on next provision
- dev.ps1 -Reprovision: runs taint before provision to force VM rebuild
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When provisioning a subset of versions (e.g. -Version 9), use
terraform -target to apply only the requested nodes. This prevents
Terraform from destroying VMs for other versions that exist in state.
Key changes:
- Provision: ISOs, answer files, and API token creation only run for
requested version nodes. Tfvars always include ALL versions for
state consistency. -target limits what Terraform applies.
- Cleanup: only destroys VMs for the requested version. Storage
containers only stopped when cleaning all versions.
- Config: merges with existing config.json to preserve entries from
previously provisioned versions.
Based on -target pattern from ~/Source/homelab Makefile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provision and cleanup now accept a version argument (8, 9, or all)
to operate on a subset of PVE nodes:
dev.ps1 -Provision -Version 9 -DockerHost ... # only PVE 9 nodes
dev.ps1 -Cleanup -Version 8 -DockerHost ... # only PVE 8 nodes
run-integration.sh provision/cleanup also accept the version arg,
overriding PVE_VERSIONS and ALL_NODES for that invocation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Each test file now creates and cleans up its own resources:
- 06_VMs: restores own AfterAll cleanup for pester-test-vm
- 07_Snapshots: creates pester-snap-vm, tests snapshots, cleans up
- 09_CloudInit: creates pester-ci-vm with cloud-init drive, cleans up
- 15_Tasks: creates pester-task-vm, tests task CRUD, cleans up
Removed cross-file dependency helpers (Find-TestVm, Register-TestResource)
from _IntegrationHelper.ps1 — no longer needed.
Each file can now run independently via -Tests filter. If a Setup
context fails, subsequent tests in the same file skip gracefully.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
06_VMs AfterAll was deleting pester-test-vm, causing 07_Snapshots,
09_CloudInit, and 15_Tasks to fail with VMID 100 (wrong VM or
nonexistent). Now 99_Cleanup handles all pester-* resource removal.
Also fixed cleanup timing:
- Use -Wait on Stop-PveVm/Container before Remove
- Add sleep after removal for API propagation
- 10_Containers AfterAll uses -Wait on stop
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PowerShell's -Command doesn't support the -- argument separator for
param() blocks. Inject $PveVersion and $TestFilter as variable
assignments at the top of the script string instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config file now defaults to $WORK_DIR/config.json instead of
$CACHE_DIR/test-config.json. This keeps test artifacts in
/tmp/pve-integration alongside other build/work files, separate
from the ISO cache in /opt/pve-isos.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Config file is at /opt/pve-isos/test-config.json (CACHE_DIR default),
not /tmp/pve-integration/config.json.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
cmd_test() now accepts an optional second argument for filtering
integration tests by area name. Comma-separated names are matched
against test filenames via glob (e.g. *Connection*.Tests.ps1).
Usage: run-integration.sh test 9 Connection,VMs
The Pester invocation builds a path array from matched files when
a filter is specified, otherwise runs the entire Integration directory.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Split the 1544-line Integration.Tests.ps1 into 18 focused test files
with numeric prefixes for execution ordering.
New shared helper (_IntegrationHelper.ps1):
- Credential-based auth (root@pam) instead of API tokens
- Skip helpers for env var checks
- Resource discovery (Find-TestVm) for cross-file dependencies
- Register-TestResource for env-var state passing between files
Files created:
00_Connection, 01_Nodes, 02_Users, 03_Storage, 03a_SharedStorage,
04_Network, 05_SDN, 06_VMs, 07_Snapshots, 08_Templates,
09_CloudInit, 10_Containers, 11_LinuxVM, 12_OVA, 13_Firewall,
14_Backup, 15_Tasks, 99_Cleanup
Key changes:
- All tests use root@pam credentials (not API tokens)
- Each file self-contained with own BeforeAll/AfterAll cleanup
- Token CRUD tests now idempotent (remove-before-create)
- 99_Cleanup is safety-net for any leftover pester-* resources
- SharedStorage renamed to 03a_SharedStorage
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The NFS server image already has modprobe logic built in but needs
access to host kernel modules. Avoids requiring manual modprobe on
the runner host.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
NFS and iSCSI are implicitly shared in PVE — the API rejects an
explicit 'shared' parameter. iSCSI uses 'portal' (not 'server') for
the target address.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New-PveStorage now supports configuring iSCSI storage backends natively:
-Target: iSCSI target IQN (e.g. iqn.2024-01.com.example:storage)
-Portal: iSCSI portal address (host:port, defaults to server:3260)
Also refactored ProcessRecord to use AddIfNotEmpty helper, reducing
cognitive complexity.
Added unit tests for all iSCSI/NFS parameter metadata and a new
SharedStorage.Tests.ps1 integration test file that tests NFS and iSCSI
storage create/verify/status/delete lifecycle against the Docker-based
storage containers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix storage_ip derivation to use default route first, not Swarm RemoteManagers
- Make iSCSI container entrypoint idempotent (check before create for target/LUN/bind)
- Pin erichough/nfs-server from 'latest' to '2.2.1' for reproducibility
- Replace curl|sh Docker install with apt repo in Dockerfile.test
- Add docker.sock security warnings in docker-compose.test.yml and CI workflow
- Update stale 'storage VM' references in comments and .env.test.example"
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/534c0a63-2988-496c-b0ae-e291fcfa050b
Provision two PVE nodes per version (a/b) for future cluster testing,
plus Docker-based iSCSI target and NFS server for shared storage tests.
Multi-node changes:
- Each PVE version gets two nodes: 9a/9b and 8a/8b (4 VMs total)
- Parameterized answer.toml FQDN for unique hostnames per node
- Per-node auto-install ISOs with unique answer files
- Node name discovered from FQDN and included in test config
- API token creation handles pre-existing tokens (delete + recreate)
- New test env vars: PVETEST_HOST_B, PVETEST_APITOKEN_B
- Removed preflight cleanup from provision (use explicit cleanup instead)
Docker storage services:
- New docker-compose.storage.yml with iSCSI (tgt) and NFS containers
- Host networking so PVE nodes can reach storage services
- Docker socket mounted into dev-infra container for host Docker access
- Docker CLI added to dev-infra Dockerfile stage
- New test env vars: PVETEST_STORAGE_VM_IP, PVETEST_ISCSI_IQN, PVETEST_NFS_EXPORT
Other fixes:
- first-boot.sh installs open-iscsi on PVE nodes
- preflight-cleanup.sh handles empty ISO filename gracefully
- TMPDIR set to work dir to avoid /tmp overflow during ISO uploads
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace tests/infrastructure/Dockerfile with tests/Dockerfile.test
(single multi-stage Dockerfile for both CI and local dev)
- CI container-image job now builds from Dockerfile.test target dev-infra
- Add ARM support: PowerShell installed via dotnet tool on arm64,
APT package on amd64
- Replace tests/dev.sh (bash) with tests/dev.ps1 (PowerShell) for
cross-platform support (Windows, macOS, Linux)
- Add -DockerHost parameter for running x86 containers on a remote
Docker host from ARM Macs (rsyncs repo, uses SSH Docker transport)
- Add -NoCleanup switch to keep nested PVE VMs after integration tests
- integration command now provisions nested PVE VMs instead of testing
against a pre-existing PVE directly
- Share /opt/pve-isos host path between CI and local dev (was separate
Docker named volume)
- Delete tools/Invoke-Tests.ps1 (unused, overlapped with run-integration.sh)
- Add .gitignore entries for Terraform state/artifacts
- Update all documentation references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dockerfile.test: multi-stage build with dev (ARM+x86) and dev-infra
(x86 only, adds Terraform + PVE provisioning tools) targets
- docker-compose.test.yml: services for both targets, .env.test support
- dev.sh: helper script wrapping container lifecycle and run-integration.sh
- .env.test.example: template for integration test configuration
- .gitignore: exclude .env.test, whitelist .env.*.example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single source of truth for the provision → test → cleanup lifecycle,
callable from both CI workflows and local dev containers.
Subcommands: provision, test [8|9|all], cleanup, all
Detects CI vs local via $GITHUB_ACTIONS and adjusts output accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CI workflow: PVE_PASSWORD now reads from secrets.PVE_TEST_PASSWORD
- variables.tf: removed default password, requires TF_VAR env var
- Integration README: examples use <your-test-password> placeholder
- create-api-token.sh: example IP changed to pve.example.com
- .gitignore: added .env/.env.* exclusion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
Suspend-PveContainer and Restart-PveContainer gained ConfirmImpact.High
in F062/F063 fixes. The "Should throw when no session is active" Pester
tests invoke these cmdlets without -Confirm:$false, causing the
confirmation prompt to fire before the session check — failing in
non-interactive CI on all platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restart-PveContainer gained ConfirmImpact.High in the F062 fix, which
correctly prompts for confirmation on destructive operations. The
integration test was missing -Confirm:$false, causing a
NullReferenceException in non-interactive CI. The cascading failure also
broke the Copy-PveContainer test (F081).
Also updates CONTRIBUTING.md to reference .NET SDK 10.0+ (was 9.0+) to
match all CI workflows and test project TFM (F083).
Includes scan-6 review report and findings database update (F001-F083).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F058 (critical): Replace while(true) infinite-loop task polling with
TaskService.WaitForTask in 5 container snapshot and storage cmdlets.
F073+F047 (high): Migrate net9.0 → net10.0 across both source .csproj
files, build.yml, publish.yml, and test helper.
F071 (medium): Add Uri.EscapeDataString() to all inline URL path
segments in ~16 cmdlets that bypass service classes (D003).
F062+F063 (medium): Add ConfirmImpact.High to Restart-PveContainer
and Suspend-PveContainer (D006).
F075 (medium): Generate markdown help docs for 89 cmdlets that were
missing documentation (170 total, up from 81).
F072 (low): Remove unused System.Text.Json dependency from Core.csproj.
F074 (low): Raise publish smoke-test threshold from 60 to 150.
F065 (low): Add .github/ISSUE_TEMPLATE/config.yml.
F066 (low): Add CODEOWNERS.
Also fix _TestHelper.ps1 net9.0 → net10.0 framework reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- xUnit test project now targets net10.0 instead of net9.0 (EOL Nov 2026)
- Updated GuestAgentExtCmdlets tests for SecureString Password parameter
- Added Timeout parameter tests to GuestAgentCmdlets
- Added ConfirmImpact.High assertions for Suspend/Restart-PveVm
- Added -Confirm:$false to Suspend-PveVm integration test call
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New-PveSdnDns requires mandatory Url and Key parameters. The no-session
test was failing with "missing mandatory parameters" instead of reaching
the session check.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Integration test: add -All to New-PveBackupJob call — PVE API
requires either vmid or all when creating a backup job
- Fix New-PveBackupJob Enabled logic: jobs are enabled by default
unless user explicitly passes -Enabled:$false (was broken because
SwitchParameter default=true doesn't set IsPresent)
- Fix XML doc cref warning in NetworkService after RequireSdn removal
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add -Level Cluster to "no session" tests for cmdlets that require
the mandatory Level parameter (13 failures)
- Fix parameter name Pos → Position in Set/Remove-PveFirewallRule
tests (2 failures)
- Change Set-PveFirewallAlias Cidr test from mandatory to optional
assertion — Cidr is optional on update (1 failure)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace the service-layer RequireSdn hard block with a cmdlet-level
RequireVersion helper in PveCmdletBase that supports two tiers:
- Introduced version: hard fail — the API endpoint doesn't exist
- Default version: warning only — feature exists but may not be
enabled; users who manually enabled it can proceed
Applied to:
- SDN Zone/VNet/Subnet: introduced 6.2, default 8.0
- SDN IPAM/DNS/Controller: introduced 6.2, default 8.1
- Import-PveVmDisk, Import-PveOva: introduced 8.1
Also fixes integration tests:
- Remove duplicate OVA Import context
- Use systemd calendar format for backup schedule (PVE 9)
- Add SDN IPAM/DNS/Controller read-only integration tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove duplicate 'OVA Import' context that used wrong parameter name
(NewVmId vs VmId) — original OVA test context at line 1227 is correct
- Fix backup job schedule from cron syntax ('0 3 * * 6') to systemd
calendar event format ('sat 03:00') which PVE 9 requires
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 35 new cmdlets bringing the total to 118:
- Firewall (21): rules, groups, aliases, IP sets, options at
cluster/node/VM/container levels
- Backup (5): ad-hoc vzdump and scheduled backup job CRUD
- SDN IPAM/DNS/Controller (9): plugin management for SDN subsystem
Also includes:
- Fix: Remove-PveRole now has ConfirmImpact.High
- Fix: URL-encode snapshot names in API paths
- Refactor: extract auth header strings to constants in PveHttpClient
- Add PSGallery version badge to README
- Full test coverage: 11 JSON fixtures, xUnit model tests, Pester
unit tests, and integration tests for firewall/backup/OVA import
- Updated manifest, format file, CHANGELOG, README, API coverage docs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PVE list endpoint reports Status=running for paused VMs. The new
-Detailed switch queries status/current per VM to populate QmpStatus.
- PveVm.EffectiveStatus computed property: returns QmpStatus ?? Status
- PveVm.Status doc comment explains the limitation
- VmService.EnrichVmStatus() queries status/current endpoint
- Get-PveVm -Detailed enriches each VM with qmpstatus, pid, uptime
- format.ps1xml table view uses EffectiveStatus for the Status column
- Integration test asserts EffectiveStatus after suspend/resume
Verified locally: Get-PveVm -Detailed correctly shows 'paused' for
suspended VMs where Get-PveVm (without -Detailed) shows 'running'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PVE list endpoint (GET /nodes/{node}/qemu) always reports Status=running
for paused VMs — qmpstatus is only available from the status/current
endpoint. The -Wait -Timeout polling uses status/current internally, so
if it returns without throwing, the state transition is confirmed.
Also fix restart recovery: use Resume-PveVm -Wait instead of checking
Status from list endpoint (which never shows 'paused').
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>