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>
Locally verified against PVE 9.1 with Ubuntu 24.04 cloud OVA:
- Use scsi bus (not sata) for imported disks, matching PVE UI behavior
- Set boot=order=scsi0 for first disk
- Parse NIC ResourceSubType from OVF (VmxNet3, E1000, E1000e, etc.)
and map to PVE network model instead of hardcoding virtio
- Add MapNicModel() to OvfMetadata with fallback to e1000 for unknown types
- Integration test verifies name, cores, memory, ostype, scsi0, net0, boot
qmpstatus fix (from previous local testing):
- WaitForStatusTransition polls status/current endpoint for qmpstatus
- PVE reports status=running but qmpstatus=paused for suspended VMs
- SDN subnet ID uses zone prefix: {zone}-{ip}-{prefix}
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Suspend/Resume:
- Poll status/current endpoint (not VM list) to get qmpstatus
- PVE reports status=running but qmpstatus=paused for suspended VMs
- Verified locally: Suspend-PveVm -Wait -Timeout 30 now works
SDN:
- Subnet IDs use zone prefix, not vnet: {zone}-{ip}-{prefix}
- Add 5s propagation delay before VNet deletion after subnet removal
- Switch to real Ubuntu 24.04 OVA for Import-PveOva testing
Integration tests:
- All VM/container lifecycle calls use -Wait -Timeout
- Remove manual Start-Sleep polling loops
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All VM and container lifecycle cmdlets (Start, Stop, Restart, Suspend,
Resume, Reset) now poll VM/container status when -Wait is specified,
blocking until the expected state is reached or -Timeout (default 60s)
is exceeded.
Implementation:
- PveCmdletBase.WaitForStatusTransition() — shared helper that waits
for PVE task completion then polls status via API
- 9 cmdlets updated: 6 VM (Start, Stop, Restart, Suspend, Resume,
Reset) + 3 container (Start, Stop, Restart)
- -Timeout parameter with [ValidateRange(1, 3600)] on each
Integration tests:
- Replace all manual Start-Sleep + polling loops with -Wait -Timeout
- Switch to real Ubuntu cloud OVA (571 MB) for Import-PveOva testing
- OVA test verifies full import + VM start
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Suspend/Resume: replace fixed sleep with polling loop (up to 20s)
- Restart: ensure VM is running (not paused) before attempting restart
- SDN: add propagation delays between subnet/vnet/zone removal
- OVA: split into OVF parsing test (always works) and import test
(tolerates synthetic VMDK failures gracefully)
- Dockerfile: add qemu-utils for proper VMDK generation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Suspend/Resume: move to Linux VM lifecycle context (empty VMs may
not reliably transition to paused state)
- SDN subnet model: dhcp-range is an array, not string
- SDN subnet delete: use PVE-format ID (vnet-ip-prefix) not CIDR
- Container config: trim trailing newline from PVE description
- OVA: use qemu-img to create valid sparse VMDK (add qemu-utils to
test container Dockerfile)
- Enable 'images' content type on local storage for OVA import
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- SDN: zone ID cannot contain hyphens — rename 'pester-zone' to 'pesterz'
- SDN: subnet removal uses original CIDR, not transformed ID
- Containers: New-PveContainer now auto-assigns VM ID via /cluster/nextid
when -VmId is omitted (was missing, unlike New-PveVm)
- OVA: enable 'images' content type on local storage for import-from
- Suspend: add delay before status check, use Should -Not -Throw pattern
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PVE upload API validates file extensions per content type. The
content=import type does not accept .img extension. The Ubuntu cloud
image is qcow2 format, so rename from .img to .qcow2 during download.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Send-PveFile replaces Send-PveIso with a -ContentType parameter
(iso, vztmpl, import). This is a breaking rename — the module has
not been released yet.
Test infrastructure refactored:
- prepare-test-vm.sh replaced with prepare-test-environment.sh
(only SSH ops: enable content types, upload cloud-init snippet,
curl download cloud image)
- VM provisioning moved into integration tests as "Linux VM —
Provisioning" context: Send-PveFile, New-PveVm, Import-PveVmDisk,
Set-PveVmConfig, Set-PveCloudInitConfig, Start-PveVm,
Test-PveVmGuestAgent — all tested as part of the test suite
- PVETEST_LINUX_VMID env var removed (VM created by tests)
- PVETEST_CLOUD_IMAGE_PATH + PVETEST_PASSWORD added
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Absolute filesystem paths are rejected by PVE API when using API token
auth (only root ticket auth allows arbitrary paths). Instead:
- Enable 'import' content type on local storage alongside snippets
- Download cloud image with content=import (not iso)
- Reference as local:import/filename in Import-PveVmDisk
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The cloud image is downloaded as content=iso, but import-from requires
content type 'images' or 'import'. Use the absolute node path
(/var/lib/vz/template/iso/) instead of the storage volume reference
(local:iso/) to work around this constraint.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Parses OVF metadata client-side from the OVA TAR archive to extract
VM name, CPU, memory, disks, and network adapters. Then uploads the
OVA with content=import, creates the VM, and imports each disk.
- OvfMetadata parser with multi-target TAR support (System.Formats.Tar
on net9.0, manual 512-byte header parsing on netstandard2.0/net48)
- VmService.UploadOva() for content=import uploads
- Import-PveOva cmdlet with -Name/-Memory/-Cores overrides and -Wait
- Pester unit tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Imports disk images (qcow2, raw, vmdk) and OVA archives into VMs
via the PVE API using POST config with import-from syntax.
- VmService.ImportDisk() method using POST /nodes/{node}/qemu/{vmid}/config
- Import-PveVmDisk cmdlet with -Source, -Disk, -TargetStorage, -Format, -Wait
- Pester unit tests
- prepare-test-vm.sh updated to use Import-PveVmDisk instead of SSH qm importdisk
- Set-PveNetwork: add required Type parameter (fixes PVE API 400 error)
- Integration test fix: pass -Type bridge to Set-PveNetwork
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ConfirmImpact.High causes interactive confirmation prompts in
non-interactive CI, blocking before ProcessRecord runs. Add
-Confirm:$false to all Stop-PveVm, Reset-PveVm, Remove-*,
Restore-*, and New-PveTemplate calls in both unit and integration tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- New Move-PveContainer cmdlet with -Online and -Wait support
- Uses existing ContainerService.MigrateContainer
- Pester unit tests added to ContainerLifecycle.Tests.ps1
- README cmdlet reference updated
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add bridge create/update/remove/apply test cases
- Add interface type filter test
- Tests exercise New-PveNetwork, Set-PveNetwork, Remove-PveNetwork,
and Invoke-PveNetworkApply against live PVE
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Container snapshots (4 cmdlets):
- Get-PveContainerSnapshot, New-PveContainerSnapshot
- Remove-PveContainerSnapshot, Restore-PveContainerSnapshot
- Service methods in ContainerService
- Pester unit tests
SDN subnet management (3 cmdlets):
- Get-PveSdnSubnet, New-PveSdnSubnet, Remove-PveSdnSubnet
- PveSdnSubnet model and NetworkService methods
- Pester unit tests
Also updates manifest (73 cmdlets), README cmdlet reference, and CHANGELOG.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PVE 9 may not immediately reflect the template flag in the VM list
after New-PveTemplate returns. Add a 3-second delay before verifying.
PVE 8 passed without the delay.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>