Commit Graph

53 Commits

Author SHA1 Message Date
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
Clint Branham b12ffc485d feat: add AdditionalConfig parameter and guest agent cmdlets (closes #2, #3)
Issue #3 — Set-PveVmConfig / Set-PveContainerConfig:
- Add -AdditionalConfig [Hashtable] for arbitrary PVE config keys
  (scsi0, boot, agent, net0, ide2, cicustom, etc.)
- Add -Delete [string] for removing config keys
- Unit tests for both new parameters

Issue #2 — QEMU Guest Agent cmdlets:
- Test-PveVmGuestAgent: pings guest agent, returns bool
- Get-PveVmGuestNetwork: returns guest network interfaces with IPs
- Invoke-PveVmGuestExec: executes command in guest, returns output
- New models: PveGuestNetworkInterface, PveGuestIpAddress
- Service methods in VmService for all agent operations

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 14:06:55 -05:00
Clint Branham d995479c8e fix(test): add -Confirm:\$false to New-PveTemplate call in integration tests
ConfirmImpact.High causes an interactive prompt in CI without
-Confirm:\$false, leading to a null reference error.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:31:17 -05:00
Clint Branham 6b213265de fix(ci): skip Invoke-PveCloudInitRegenerate (bug: returns config as UPID)
The cmdlet calls the /cloudinit/dump endpoint which returns YAML
content, not a task UPID. This gets passed to WaitForTask causing a
501 error. PVE regenerates cloud-init automatically on VM start, so
the explicit regenerate call is not needed for test VM setup.

Bug to fix: RegenerateCloudInitImage should PUT to /cloudinit instead
of GET from /cloudinit/dump.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:21:47 -05:00
Clint Branham 6f8389800f fix(ci): switch to Ubuntu cloud image (.img extension) for test VM
The Debian .qcow2 extension was rejected by PVE's download-url API.
Ubuntu's noble-server-cloudimg-amd64.img has a .img extension that
PVE accepts, and cloud-init + qemu-guest-agent work the same way.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 13:14:06 -05:00
Clint Branham d98bd40476 refactor(ci): use PSProxmoxVE cmdlets in test VM preparation
Replace raw qm/SSH commands with module cmdlets where possible:
- Invoke-PveStorageDownload for cloud image download
- New-PveVm for VM creation
- Set-PveCloudInitConfig for cloud-init user/password/IP
- Invoke-PveCloudInitRegenerate to apply config
- Start-PveVm to boot the VM

SSH/SCP retained only for operations without API support:
- Snippet upload (no snippet API)
- pvesm set for snippets content type
- qm importdisk (no importdisk cmdlet)
- qm set for --scsi0, --cicustom, --agent, --boot (not in Set-PveVmConfig)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:52:44 -05:00
Clint Branham f1f5f3f180 refactor(ci): use cloud-init user-data instead of virt-customize
Replace virt-customize with a cloud-init custom user-data snippet that
installs qemu-guest-agent on first boot. The snippet is SCP'd to the
nested PVE's /var/lib/vz/snippets/ and referenced via --cicustom.

This eliminates the need for libguestfs-tools entirely — no install
on nested PVE, no extra Docker image, no large dependencies.

Timeout increased to 300s to allow for cloud-init package installation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:49:52 -05:00
Clint Branham c4a1abf5b1 fix(ci): install libguestfs-tools on nested PVE and mask test password
- prepare-test-vm.sh: install libguestfs-tools if not present on the
  fresh nested PVE before running virt-customize
- Mask PVE_PASSWORD in logs via ::add-mask:: to prevent leaking

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:46:21 -05:00
Clint Branham d6aa5274d4 refactor(ci): customize cloud image on nested PVE instead of container
Move virt-customize to run on the nested PVE host (which already has
libguestfs) instead of inside the CI container. This removes
libguestfs-tools, linux-image-generic, and qemu-utils from the
Dockerfile, shrinking the image from ~1.2GB to ~770MB.

- prepare-test-vm.sh: downloads Debian cloud image on nested PVE,
  runs virt-customize there to install qemu-guest-agent, then creates
  VM with imported disk + cloud-init
- Remove build-test-image.sh (no longer needed)
- Remove /opt/pve-images volume mount

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:37:35 -05:00
Clint Branham 3202bbccef fix(ci): add xorriso to container and expand tool verification
proxmox-auto-install-assistant requires xorriso for ISO manipulation.
Expand the verification step to check all tools the scripts depend on.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 12:09:36 -05:00
Clint Branham ff4853978d feat(ci): containerize integration tests and add Alpine guest-agent VM
Container infrastructure:
- Dockerfile with PowerShell, Pester, Terraform, sshpass,
  libguestfs-tools, and proxmox-auto-install-assistant
- build-test-container.yml workflow to build/push image to GHCR
- Image tagged ghcr.io/<repo>/integration-test:latest

Workflow restructure:
- New build job on GitHub-hosted ubuntu-latest (dotnet publish)
- Integration job runs inside Docker container on self-hosted runner
- Module artifact downloaded from build job (no .NET SDK in container)
- Host volumes mounted for PVE ISOs and cached images

Alpine guest-agent VM:
- build-test-image.sh: Downloads Alpine cloud image, installs
  qemu-guest-agent via virt-customize, caches on runner
- prepare-test-vm.sh: Deploys image to nested PVE via SCP + qm
  importdisk, configures cloud-init, waits for guest agent

Test fixes:
- Get-PveTask: use .Upid property instead of PveTask object ToString()

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:26:43 -05:00
Clint Branham a9f1c066e0 feat(test): add Alpine guest-agent VM for ACPI and template tests
Infrastructure:
- build-test-image.sh: Downloads Alpine cloud image, uses virt-customize
  to install qemu-guest-agent, caches at /opt/pve-images/
- prepare-test-vm.sh: SCPs image to nested PVE, creates VM via
  qm importdisk with cloud-init and agent enabled, waits for agent

Workflow:
- Build Alpine image step (cached on runner, only built once)
- Deploy test VM step after provisioning, passes LINUX_VMID to tests

Tests:
- Guest Agent VM lifecycle: verify running, ACPI restart, ACPI stop
- Templates: convert Linux VM to template, clone from template
  (New-PveTemplate, New-PveVmFromTemplate)
- Fix Get-PveTask: use .Upid property instead of whole PveTask object

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 11:01:05 -05:00
Clint Branham 84029d8871 fix(infra): detect Debian suite from os-release for PVE 8 compatibility
PVE 8 (Bookworm) may not have /etc/apt/sources.list.d/debian.sources,
causing the grep fallback to use 'trixie' which is invalid for PVE 8.
Use /etc/os-release VERSION_CODENAME which works on both PVE 8 and 9.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:46:44 -05:00
Clint Branham 8924d521b6 fix(test): correct integration test parameter mismatches
- New-PveRole: Privileges is a comma-separated string, not an array
- Set-PvePermission: uses UgId+Type, not Users
- Get-PveVmConfig: no Name property — test config get/set together
- Restart-PveVm: replace with Reset-PveVm (ACPI timeout on no-OS VM)
- Remove resize disk test (Set-PveVmConfig lacks Settings param)
- Copy-PveVm: must provide NewVmId explicitly
- Get-PveTask: requires Upid — test via Start-PveVm task chain

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:46:08 -05:00
Clint Branham 5e1864a0b0 feat(test): add PVE 8+9 matrix and expand integration test coverage
Workflow changes:
- Convert to matrix strategy testing PVE 8.4 and 9.1 sequentially
- Each version gets unique VMID (99908/99909) and VM name
- Pass PVETEST_PVE_VERSION env var for version-specific assertions
- Version-specific artifact names for test results

New integration tests:
- User CRUD: create, list, update (Set-PveUser), remove
- API Token CRUD: create, list, remove (on dedicated test user)
- Role CRUD: create with privileges, list, remove
- Permissions: list and set (Get/Set-PvePermission)
- VM config: get and set config, resize disk
- VM lifecycle: restart, explicit remove
- Snapshots: full lifecycle (create, list, restore, remove)
- Storage: list content (Get-PveStorageContent)
- Tasks: list recent tasks (Get-PveTask)
- Version assertion: verify PVE major version matches expected

Coverage improved from 15 to ~30 cmdlets tested against live API.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:33:07 -05:00
Clint Branham cc344a3070 fix(test): replace null-coalescing operator for PS 5.1 compatibility
PS 5.1 cannot parse the ?? operator (PS 7.0+), causing a discovery
error even though integration tests are excluded by tag. Replace with
if/else that works on all PS versions.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 10:00:18 -05:00
Clint Branham 6cab18779c fix(test): remove mandatory-param invocation tests that hang PS 5.1
PS 5.1 prompts interactively for mandatory parameters instead of
throwing, causing CI to hang indefinitely. Replace invocation-based
tests with attribute-based IsMandatory checks where duplicates exist,
or convert to attribute checks where they don't.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 09:52:43 -05:00
Clint Branham b8af36a7fc fix(build): add netstandard2.0 target to eliminate System.Runtime version mismatch
The net9.0 DLL references System.Runtime 9.0.0.0 which is unavailable
on GitHub runners where pwsh ships with .NET 8.x. Adding netstandard2.0
as a target produces a DLL compatible with any .NET version (PS 5.1 on
.NET Framework through PS 7.x on .NET 8/9).

- Add netstandard2.0 to TargetFrameworks in Core and module csproj
- Update #if NET48 conditionals to include NETSTANDARD2_0
- Switch CI workflows from net9.0 to netstandard2.0 for Pester tests
- Update _TestHelper.ps1 framework search order

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 09:34:40 -05:00
Clint Branham 35805959f4 fix(ci): remove all deps/runtimeconfig json and unset DOTNET_ROOT in-process
Previous approach of env: DOTNET_ROOT='' at the step level wasn't
sufficient — the .NET runtime may have already cached the original
DOTNET_ROOT before pwsh started. Now:

- Remove *.deps.json AND *.runtimeconfig.json from publish output
- Unset DOTNET_ROOT via $env:DOTNET_ROOT = $null inside the pwsh script
- _TestHelper.ps1 also unsets DOTNET_ROOT and cleans deps/runtimeconfig

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 09:06:24 -05:00
Clint Branham 558053dc74 chore(ci): move unit tests and build to Docker containers on self-hosted runner
All CI now runs on the self-hosted runner using Docker containers,
eliminating GitHub-hosted runner minute consumption:

- Unit tests: PS 7.5 container (mcr.microsoft.com/powershell:7.5-ubuntu-24.04)
  with .NET SDK 9.0 installed at build time
- Build/xUnit: .NET SDK 9.0 container (mcr.microsoft.com/dotnet/sdk:9.0)
- Integration tests: unchanged (runs directly on self-hosted runner)

Drops net48/PS 5.1 CI matrix (Windows-only, can't run in Linux
containers). Local Docker testing available via tests/Dockerfile.test.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:56:12 -05:00
Clint Branham 7962a549f0 fix(test): load module via .psd1 manifest instead of raw DLL
Loading via the module manifest (.psd1) ensures PowerShell sets up
proper assembly resolution context. Loading via raw DLL path bypasses
PS's module loading infrastructure and fails on CI runners where the
.NET SDK's assembly resolution conflicts with PS's bundled runtime.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:45:23 -05:00
Clint Branham 8d268aee61 fix(ci): remove PSProxmoxVE.deps.json to fix assembly loading in PS 7.x
The deps.json file causes .NET's AssemblyLoadContext to conflict with
PowerShell's own runtime assembly resolution, resulting in
FileNotFoundException for System.Runtime 9.0.0.0 on all PS 7.x
platforms. Binary PS modules should not include deps.json.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:36:49 -05:00
Clint Branham d187b0de52 fix: resolve net48-only nullable warnings (CS8601, CS8602, CS8604)
- CS8601: Add null-forgiving operator on guarded dictionary assignments
  in cmdlets where IsNullOrEmpty check precedes the assignment
- CS8602: Add Assert.NotNull after JObject["data"] in xUnit model tests
  (JToken indexer returns nullable on net48)
- CS8604: Add null-forgiving on guarded arguments in NodeService,
  WaitPveTaskCmdlet, GetPveTemplateCmdlet, and auth test parameters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 08:32:57 -05:00
Clint Branham 3075c12768 test: add unit tests for all previously untested cmdlets (274 new tests)
Coverage for 16 cmdlets that had no unit tests:
- Nodes: Get-PveNode, Get-PveNodeStatus
- VM config: Get-PveVmConfig, Set-PveVmConfig, Resize-PveVmDisk
- VM ops: Copy-PveVm, Move-PveVm
- Container config: Copy-PveContainer, Get-PveContainerConfig,
  Set-PveContainerConfig
- Storage: Invoke-PveStorageDownload
- Roles: New-PveRole, Remove-PveRole
- Tasks: Get-PveTask, Wait-PveTask

Each test covers: command existence, parameter metadata, pipeline
support, ShouldProcess/WhatIf, and no-session error handling.

Total Pester unit tests: 705 (was 431)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:15:17 -05:00
Clint Branham 0c70e11fe8 fix: resolve all C# compiler warnings (CS8604, CS8618, CS8603, CS1573, xUnit1012)
- CS8604: Use cmdlet's own Node property instead of nullable task.Node
  in WaitForTask calls across all VM cmdlets
- CS8618: Make PveSession.ServerVersion nullable (PveVersion?) since it
  is set post-construction by PveAuthenticator
- CS8603: Add null-forgiving operator where IsNullOrWhiteSpace guards
  guarantee non-null in PveHttpClient
- CS1573: Add XML param documentation to all public service methods
- xUnit1012: Make test parameters nullable (string?) for null test cases

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:11:49 -05:00
Clint Branham 0dc7f5f833 fix(test): fix assembly loading, remove mock server, resolve test warnings
- Create shared _TestHelper.ps1 for reliable module loading in both
  local dev and CI (fixes System.Runtime 9.0.0.0 FileNotFoundException
  on PS 7.x by trying Import-Module by name first, then local paths)
- Use (Get-Module).ModuleBase for manifest path resolution
- Remove PSProxmoxVE.MockServer project and MockIntegration tests
- Remove MockIntegration from ExcludeTag filters

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 18:11:33 -05:00
Clint Branham ec05e52e58 fix(ci): fix xunit test, net48 build isolation, mock server check, Pester config
- Update BaseUrl test to expect trailing slash (matches code change)
- Revert upload-artifact to v4 (v5 doesn't exist yet)
- Build net48 job restores only src/test projects (excludes MockServer)
- MockServer.Tests.ps1: fix net10.0 -> net9.0 DLL paths, fix build check
- Unit tests: use New-PesterConfiguration instead of -CI flag (fixes
  AmbiguousParameterSet on PS 5.1), exclude MockIntegration tag

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 17:20:39 -05:00
Clint Branham b64c34c8cd fix(ci): align all projects on net9.0, upgrade actions to v5, enable VM tests
- Change test projects from net10.0 to net9.0 to match source projects
- Update build workflow from net8.0 to net9.0
- Upgrade checkout to v5, setup-dotnet to v5 across all workflows
- Remove outdated PS 7.2 test matrix entries
- Restructure integration tests: create test keeps VM alive so start/stop,
  snapshot, and cloud-init tests can use it instead of skipping. AfterAll
  handles cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:59:43 -05:00
Clint Branham 8779dc5818 fix(ci): correct nested PVE node name and mask secrets in logs
- Change FQDN from pve-test.local to pve.test.local so the PVE node
  name is "pve" (matching what tests expect)
- Mask the dynamically created API token and VM IP with ::add-mask::
  before they appear in workflow logs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:46:55 -05:00
Clint Branham e8689f071c fix(ci): use RUNNER_TEMP for all temp files instead of /tmp
/tmp on the self-hosted runner has sticky bit — files created by
previous runs (or different users) can't be overwritten. RUNNER_TEMP
is per-run and owned by the runner user.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:33:05 -05:00
Clint Branham 26f5c0558c fix(ci): add --tmp /tmp to prepare-iso to avoid permission denied
The proxmox-auto-install-assistant defaults --tmp to the source ISO's
directory (/opt/pve-isos/), which the runner user can't write to.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:19:56 -05:00
Clint Branham 08286303ee fix(ci): first-boot repo fix, robust cleanup, and script fixes
- first-boot.sh: Remove .sources files (PVE 9 DEB822 format) not just
  .list, and detect suite (trixie vs bookworm) from debian.sources
- create-api-token.sh: Accept full endpoint URL (fixes missing port),
  use /access/domains for API check (no auth needed on PVE 9),
  avoid pipefail on curl|python3 pipe
- preflight-cleanup.sh: New script that runs at start and end of every
  provisioning run — cleans orphaned VMs, ISOs, and stale terraform state
- Workflow: Add pre-flight and final cleanup steps so failed runs can't
  leave the runner/host in a broken state

All changes validated manually on the self-hosted runner.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 16:14:35 -05:00
Clint Branham cda603d920 fix(ci): set boot order to disk-first to prevent install loop
OVMF was rebooting back into the installer CD after PVE install completed.
Setting boot_order to scsi0 first means the first boot falls through to
CD (empty disk), and after install OVMF boots from the EFI entry on disk.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:27:43 -05:00
Clint Branham 18265fa4cf fix(ci): correct case-sensitive storage pool name (nas-iSCSI-lvm)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:21:23 -05:00
Clint Branham 0dff14f4ba fix(ci): use Terraform provider for ISO upload, fix port/lockfile issues
- Use proxmox_virtual_environment_file resource to upload the auto-install
  ISO via the API. terraform destroy now cleans up both the VM and ISO.
- Remove manual upload-to-pve.sh and cleanup-pve-storage.sh scripts.
- Commit .terraform.lock.hcl for reproducible CI builds.
- Replace iso_file_id variable with iso_local_path and iso_storage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:19:24 -05:00
Clint Branham 910c686abc fix(ci): remove invalid on_first_boot field from answer.toml
The on_first_boot option is a CLI flag for prepare-iso, not a valid
field in the answer file's [global] section. The first-boot script
is already passed via --on-first-boot in prepare-auto-iso.sh.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:15:35 -05:00
Clint Branham ddecafee91 feat(ci): automated nested PVE provisioning for integration tests
Workflow now provisions a throwaway nested PVE VM via Terraform, runs
integration tests against it, then destroys it. Uses proxmox-auto-install-assistant
to bake the answer file and a first-boot script (installs qemu-guest-agent)
directly into the ISO. IP is discovered via the QEMU guest agent on the
parent PVE, eliminating the need for static IP configuration.

Supports both PVE 8.x and 9.x via workflow_dispatch version selector.
Skip provisioning with skip_provision=true to test against a pre-existing PVE.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-18 14:13:41 -05:00
Clint Branham 79fae0bb37 chore(infra): simplify Terraform config and update docs for auto-install ISO
- Remove ISO upload and answer-file resources (pre-provisioned manually)
- Remove null_resource wait/token steps (token created out-of-band)
- Switch iso_file to iso_file_id referencing an existing Proxmox file ID
- Add lifecycle ignore_changes for started and cdrom
- Update README with proxmox-auto-install-assistant ISO prep instructions
- Update default variables to match homelab environment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 12:57:32 -05:00
Clint Branham 8ea983c0fc fix: resolve all integration test failures against live PVE
- BaseUrl trailing slash and resource path cleanup: PveSession.BaseUrl now
  ends with '/', all cmdlet resource strings stripped of leading slashes and
  /api2/json/ prefixes so URLs compose correctly
- PveNodeStatus: rename Name -> Node to match JSON field and test expectations
- NewPveVmCmdlet: auto-allocate vmid via GET cluster/nextid when -VmId omitted
- UploadFileAsync (BZ 7389 workarounds):
  * Unquoted multipart boundary in Content-Type header
  * Quoted name= values in Content-Disposition (embedded double-quotes)
  * Content-Disposition set before Content-Type on file part — PVE's parser
    closes the connection if Content-Type appears first
- SendPveIsoCmdlet: run upload in Task.Run, track progress atomically with
  Interlocked, poll and call WriteProgress only from pipeline thread to avoid
  InvalidOperationException from PSCmdlet thread-affinity requirements
- Add debug/Capture-UploadDiff.ps1: mitmproxy capture script used to isolate
  the header-ordering bug by diffing raw multipart bytes from PS vs C# module

Integration test result: 11 passed, 0 failed, 4 skipped (expected — no
template/stopped VMs on bare nested PVE test node).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 12:57:21 -05:00
Clint Branham 94b0fcdad2 feat(users): add API token CRUD cmdlets (closes #1)
Implement Get-PveApiToken, New-PveApiToken, and Remove-PveApiToken to
enable automated token management without manual Proxmox VE UI interaction.
Adds PveApiToken model, UserService methods for the token endpoints, and
31 Pester unit tests covering parameters, ShouldProcess, and no-session behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 08:25:51 -05:00
Clint Branham dc9b253195 fix(tests): resolve all 400 unit test failures to reach green
Cmdlet fixes:
- GetPveSnapshotCmdlet: add -Name filter; fix s.SnapName → s.Name
- GetPveStorageCmdlet: add -Storage name filter
- GetPveNetworkCmdlet: add -Iface optional filter
- NewPveNetworkCmdlet: rename Interface → Iface (consistency with Set/Remove)
- SetPveNetworkCmdlet, RemovePveNetworkCmdlet: rename Interface → Iface
- RemovePveNetworkCmdlet, RemovePveSdnZoneCmdlet, RemovePveSdnVnetCmdlet,
  RemovePveUserCmdlet: add ConfirmImpact = ConfirmImpact.High
- SetPveCloudInitConfigCmdlet: rename User → CiUser; move GetSession() first;
  remove duplicate session variable
- SendPveIsoCmdlet: add sha512 to ChecksumAlgorithm ValidateSet
- GetPveUserCmdlet: add -Enabled switch; refactor into MatchesFilters(); fix
  int? comparison (Enabled != 1)
- GetPvePermissionCmdlet: rename UgId → UserId
- SetPvePermissionCmdlet: rename RoleId → Role
- NewPveTemplateCmdlet: add ConfirmImpact.High; move GetSession() before
  ShouldProcess so -WhatIf-less calls throw session error first
- NewPveVmFromTemplateCmdlet: rename Node → TemplateNode with [Alias("Node")]
- RemovePveSnapshotCmdlet, RestorePveSnapshotCmdlet: move GetSession() before
  ShouldProcess so session check precedes confirm prompt

Test fixes:
- All 18 Pester test files: update DLL candidates to net9.0
- Fix foreach+It closure capture using -TestCases pattern
- Remove-PveVm, Remove-PveContainer no-session tests: add -Confirm:$false to
  bypass ConfirmImpact.High prompt before GetSession() check
- Get-PveStorageContent test: add mandatory -Node/-Storage params
- Send-PveIso test: create temp file to satisfy FileExistsValidation
- New-PveVmFromTemplate test: add NewVmId to parameter splat

Tooling:
- Invoke-Tests.ps1: add -FromTerraform, explicit lab params, fix TFM
  auto-detection via Select-Xml, fix Pester import, fix variable scoping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-17 18:27:12 -05:00
Clint Branham 07b2e1ba57 feat(infra): add self-hosted GitHub Actions runner setup
Bash setup script for Debian/Ubuntu, Dockerfile for containerized
runners, and comprehensive documentation. Installs .NET SDK, PowerShell,
Terraform, and configures the GitHub Actions runner as a systemd service
with proxmox/integration labels.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:21:33 -05:00
Clint Branham 8c963fb684 feat(infra): add Terraform config for nested PVE test VM
Provisions a throwaway nested Proxmox VE instance on an existing host
using bpg/proxmox provider. Includes unattended install via answer file,
API wait script, and automatic API token creation. Designed for CI-driven
integration test environments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:15:31 -05:00
Clint Branham e2b471b9ac test(mock): add mock integration tests and CI workflow
Pester tests that start the mock PVE server, run real cmdlets against it,
and validate end-to-end behavior: auth flows, node/VM/storage/network/
user operations, pipeline support, and deserialization. Runs on every
push via GitHub-hosted runners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:15:20 -05:00
Clint Branham 239aee630f feat(test): add mock PVE API server for integration testing
ASP.NET Minimal API project that simulates the Proxmox VE API using
existing JSON fixtures. Supports ticket and API token auth, request
tracking for assertions, self-signed HTTPS, and all major API endpoints.
Designed for programmatic test startup via MockPveServer.Start().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 16:14:56 -05:00
Clint Branham a0ce887c1b test(stub): add integration test stubs and documentation
All integration tests skip unless PVETEST_* environment variables are
configured. README documents required API token permissions, environment
variables, and planned test coverage per domain. Warning that tests
create and destroy real VMs and modify network configuration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-17 15:48:34 -05:00