Commit Graph

98 Commits

Author SHA1 Message Date
Clint Branham 064cc372cf feat(storage): add -Target and -Portal parameters to New-PveStorage for iSCSI
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>
2026-03-24 14:43:33 -05:00
copilot-swe-agent[bot] 6a63dea923 fix: apply review feedback on Docker storage and security documentation
- 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
2026-03-24 18:53:30 +00:00
Clint Branham 9817d30a11 feat(ci): multi-node PVE provisioning and Docker-based shared storage
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>
2026-03-24 13:34:15 -05:00
Clint Branham f21c7f84b7 feat(ci): unify CI and local integration test infrastructure
- 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>
2026-03-24 10:41:28 -05:00
copilot-swe-agent[bot] 2291379040 fix: apply security and correctness fixes from PR #13 review
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/677fba6b-906d-4cf6-82fa-3b7ab6afc648
2026-03-24 14:36:51 +00:00
Clint Branham fc092ea5b3 feat: add dev container for local build, test, and provisioning
- 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>
2026-03-24 09:21:52 -05:00
Clint Branham 7f3430cd50 refactor(ci): extract integration test orchestration into run-integration.sh
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>
2026-03-24 09:21:08 -05:00
Clint Branham d5f4e13637 fix(security): move hardcoded test password to GitHub Actions secret
- 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>
2026-03-23 17:40:47 -05:00
Clint Branham 053fd4c06a fix(security): add Uri.EscapeDataString to all URL path parameters
20 instances across 5 service files were missing URL encoding on
dynamic path segments (node, iface, zone, vnet). Fixes D003 regression.

- NodeService: 7 methods
- NetworkService: 7 methods (5 node-network + 2 SDN remove)
- CloudInitService: 3 methods
- TaskService: 2 methods (GetTask, GetTaskLog)
- TemplateService: 1 method (CreateTemplate)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 17:40:36 -05:00
Clint Branham a424dd18ac refactor(ci): parallel provisioning, file caching, provision/test/cleanup split
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>
2026-03-23 16:05:20 -05:00
dependabot[bot] 6f9ffd9ad1 Bump xunit from 2.7.0 to 2.9.3
---
updated-dependencies:
- dependency-name: xunit
  dependency-version: 2.9.3
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 20:47:03 +00:00
dependabot[bot] 17b42b6a89 Bump Moq from 4.20.70 to 4.20.72
---
updated-dependencies:
- dependency-name: Moq
  dependency-version: 4.20.72
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 20:41:03 +00:00
GoodOlClint c5ad5bcca7 Merge pull request #11 from GoodOlClint/dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/xunit.runner.visualstudio-3.1.5
Bump xunit.runner.visualstudio from 2.5.7 to 3.1.5
2026-03-23 15:38:00 -05:00
GoodOlClint ac1fb4872d Merge pull request #6 from GoodOlClint/dependabot/nuget/tests/PSProxmoxVE.Core.Tests/main/Microsoft.NET.Test.Sdk-18.3.0
Bump Microsoft.NET.Test.Sdk from 17.9.0 to 18.3.0
2026-03-23 15:37:29 -05:00
dependabot[bot] fd8030415c Bump xunit.runner.visualstudio from 2.5.7 to 3.1.5
---
updated-dependencies:
- dependency-name: xunit.runner.visualstudio
  dependency-version: 3.1.5
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 20:32:20 +00:00
dependabot[bot] eef68ee96d Bump coverlet.collector from 6.0.1 to 8.0.1
---
updated-dependencies:
- dependency-name: coverlet.collector
  dependency-version: 8.0.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 20:32:19 +00:00
dependabot[bot] 2d3099aaef Bump Microsoft.NET.Test.Sdk from 17.9.0 to 18.3.0
---
updated-dependencies:
- dependency-name: Microsoft.NET.Test.Sdk
  dependency-version: 18.3.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-23 20:32:17 +00:00
Clint Branham d74f8c952a fix(test): add -Confirm:$false to Suspend/Restart container Pester tests
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>
2026-03-23 15:09:58 -05:00
Clint Branham 89bead7a0e fix: add -Confirm:$false to Restart-PveContainer integration test (F080, F081)
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>
2026-03-23 14:48:11 -05:00
Clint Branham 68dadbfdc0 fix: remediate findings F045, F047, F048, F064, F070, F071, F076-F079
Phase 1 — Trivial fixes:
- F071: Add Uri.EscapeDataString to GetPveTemplateCmdlet node path
- F077: Add ValidateRange(100, 999999999) to GetPveTaskListCmdlet.VmId
- F076: Create .github/dependabot.yml (nuget + github-actions, weekly)
- F079: Fix unit-tests.yml dotnet SDK from 9.0.x to 10.0.x
- F048: Mark wont_fix — sync-over-async accepted for PS 5.1 compat

Phase 2 — Framework targeting (D009 compliance):
- F047: Reduce publishable csproj to netstandard2.0 only, remove all
  #if NET48/NETSTANDARD2_0 conditionals from PveHttpClient.cs,
  restructure build.yml for netstandard2.0 publish + net10.0/net48 tests
- F064: Resolved by F047 — SMA 7.5.0 ItemGroup removed with net10.0 TFM
- F070: Add PS 5.1 smoke-test job to publish.yml (windows-latest)

Phase 3 — IPveHttpClient interface extraction (F045):
- Extract IPveHttpClient interface from PveHttpClient
- Add constructor injection to all 14 service classes
- Services use injected client when available, create+dispose when not

Phase 4 — Service unit tests (F078):
- 196 new xUnit tests across 10 service test files
- All services tested via Moq-mocked IPveHttpClient
- Total test count: 382 (was 186)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-23 13:51:37 -05:00
Clint Branham 94424367bf fix: remediate scan-4 findings F058, F062, F063, F071, F072, F073, F074, F075
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>
2026-03-23 09:54:28 -05:00
Clint Branham d99c3e5490 chore(test): update tests for parameter changes, target net10.0
- 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>
2026-03-22 08:16:45 -05:00
Clint Branham df4cae01bc feat: close API coverage gaps — 51 new cmdlets across 6 areas
Phase 1 — Missing Update/Set operations (10 cmdlets):
- SDN: Set-PveSdnZone/Vnet/Subnet/Controller/Ipam/Dns + Invoke-PveSdnApply
- Set-PveRole, Set-PveStorage, Set-PveApiToken

Phase 2 — Cluster operations (8 cmdlets):
- Get-PveClusterResource (cluster-wide inventory)
- Get-PveTaskList, Stop-PveTask
- Get/New/Set/Remove-PvePool
- Get-PveBackupInfo (unprotected VMs)

Phase 3 — VM & container gaps (14 cmdlets):
- Move-PveVmDisk, Remove-PveVmDisk
- Guest agent: Get-PveVmGuestOsInfo/FsInfo, Read/Write-PveVmGuestFile,
  Set-PveVmGuestPassword, Invoke-PveVmGuestFsTrim
- Suspend/Resume-PveContainer, Resize-PveContainerDisk,
  New-PveContainerTemplate, Move-PveContainerVolume,
  Get-PveContainerInterface

Phase 4 — Storage content management (4 cmdlets):
- Get-PveStorageStatus, Remove/Set-PveStorageContent, New-PveStorageDisk

Phase 5 — Node operations (6 cmdlets):
- Get/Set-PveNodeConfig, Get/Set-PveNodeDns, Start/Stop-PveNodeVms

Phase 6 — Access management (9 cmdlets):
- Get/New/Set/Remove-PveGroup, Get/New/Set/Remove-PveDomain, Set-PvePassword

Total: 169 cmdlets (was 118). Includes models, services, format views,
Pester unit tests, manifest updates, README, CHANGELOG, API coverage docs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 09:55:19 -05:00
Clint Branham ca4585f134 fix(test): add missing Url and Key params to New-PveSdnDns no-session test
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>
2026-03-21 09:31:40 -05:00
Clint Branham 4b503b08db fix: backup job creation requires vmid or -All, fix Enabled default
- 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>
2026-03-21 09:07:39 -05:00
Clint Branham b11f41a5cc fix(test): fix 16 Pester unit test failures in firewall cmdlets
- 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>
2026-03-21 08:48:36 -05:00
Clint Branham 27ecca6251 feat: add two-tier version gating (introduced vs default) for cmdlets
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>
2026-03-21 08:40:23 -05:00
Clint Branham c6fd0d36fc fix(test): remove duplicate OVA context, use systemd calendar format for backup schedule
- 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>
2026-03-21 08:03:38 -05:00
Clint Branham a72642d203 feat: add firewall, backup, and SDN IPAM/DNS/controller cmdlets
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>
2026-03-21 07:51:49 -05:00
Clint Branham 8d5f33f592 feat: add -Detailed switch to Get-PveVm for accurate pause detection
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>
2026-03-20 16:18:38 -05:00
Clint Branham 5e37737bc4 fix(test): remove Get-PveVm status assertions after suspend/resume
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>
2026-03-20 16:08:20 -05:00
Clint Branham 0fc15d74ee fix: OVA import — scsi bus, boot order, NIC model from OVF, metadata verification
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>
2026-03-20 15:56:03 -05:00
Clint Branham a06cd3f2e5 fix: use qmpstatus for suspend state detection, fix SDN subnet ID format
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>
2026-03-20 15:37:38 -05:00
Clint Branham 2b772ebc65 feat: add -Timeout parameter with state polling to lifecycle cmdlets
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>
2026-03-20 15:15:30 -05:00
Clint Branham d1eb674c92 fix(test): fix suspend polling, SDN propagation delays, OVA resilience
- 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>
2026-03-20 15:05:40 -05:00
Clint Branham c5396707eb fix(test): fix suspend, SDN, container, and OVA integration test failures
- 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>
2026-03-20 14:49:06 -05:00
Clint Branham 531deafdb7 fix(test): fix integration test failures for SDN, containers, OVA, suspend
- 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>
2026-03-20 14:35:53 -05:00
Clint Branham 1c4c112565 test: expand integration and unit test coverage to 84 test cases
Integration tests (27 contexts, 84 test cases, up from 48 cmdlets to 76):
- VM: Suspend/Resume/Resize
- Containers: full lifecycle (create, config, start, stop, restart, clone)
- Container Snapshots: create, list, restore, remove
- SDN: zone/vnet/subnet CRUD (PVE 8+ version-gated)
- Storage: create/remove directory storage, Invoke-PveStorageDownload
- OVA Import: Import-PveOva with generated test OVA
- Cloud-Init: Invoke-PveCloudInitRegenerate
- Templates: Remove-PveTemplate
- Disconnect-PveServer: verify disconnect + post-disconnect failure

Unit tests:
- Guest agent cmdlets (Test-PveVmGuestAgent, Get-PveVmGuestNetwork,
  Invoke-PveVmGuestExec) — parameter metadata, ShouldProcess, session

Test infrastructure:
- prepare-test-environment.sh generates minimal test OVA (OVF + 1MB VMDK)
- CI workflow passes PVETEST_OVA_PATH to integration tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 14:20:52 -05:00
Clint Branham 6a2b1df804 fix(test): rename cloud image to .qcow2 for PVE upload compatibility
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>
2026-03-20 13:52:11 -05:00
Clint Branham f2280c1c88 refactor: rename Send-PveIso to Send-PveFile and move VM provisioning into integration tests
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>
2026-03-20 13:39:06 -05:00
Clint Branham 16be330834 fix(test): download cloud image as content=import for disk import
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>
2026-03-20 13:17:14 -05:00
Clint Branham b19911ef06 fix(test): use absolute path for disk import in prepare-test-vm.sh
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>
2026-03-20 13:08:47 -05:00
Clint Branham ca0cd945a0 feat: add Import-PveOva cmdlet for OVA appliance import
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>
2026-03-20 13:08:31 -05:00
Clint Branham 088221a5f1 feat: add Import-PveVmDisk cmdlet for disk image import
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>
2026-03-20 12:55:36 -05:00
Clint Branham ae55dbf0d7 fix(test): add -Confirm:$false to all ConfirmImpact.High cmdlet calls in tests
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>
2026-03-20 12:40:54 -05:00
Clint Branham de400e159c feat: add Move-PveContainer cmdlet for LXC migration
- 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>
2026-03-20 12:31:46 -05:00
Clint Branham fac796ee43 test: add network CRUD integration tests
- 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>
2026-03-20 12:22:03 -05:00
Clint Branham ffec75b461 feat: add container snapshot and SDN subnet cmdlets
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>
2026-03-20 12:21:40 -05:00
Clint Branham b77f08533c fix(test): add delay after template conversion for PVE 9 propagation
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>
2026-03-19 15:58:11 -05:00
Clint Branham 518e5c838a feat(test): use new cmdlets in test VM setup and add guest agent tests
prepare-test-vm.sh now uses module cmdlets for 7 of 8 steps:
- Set-PveVmConfig -AdditionalConfig replaces SSH qm set (8 config keys)
- Test-PveVmGuestAgent replaces SSH qm agent ping
- Only SSH remaining: pvesm set, SCP snippet, qm importdisk

New integration tests for guest agent cmdlets:
- Test-PveVmGuestAgent: verify agent responds
- Get-PveVmGuestNetwork: verify interfaces with IPv4 addresses
- Invoke-PveVmGuestExec: run hostname command, verify exit code + output

Also force Node.js 24 for delete-package-versions (no v6 available yet).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:08:52 -05:00