Commit Graph

228 Commits

Author SHA1 Message Date
Clint Branham a27863f4ef fix: prevent -Force -Cleanup with -Version filter
Force cleanup wipes all Terraform state, so filtering by version
would leave an inconsistent state where subsequent provisions fail.
Error early with a clear message instead.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:39:27 -05:00
Clint Branham 8c6790476b fix: always remove Docker containers in force cleanup
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>
2026-03-25 14:38:54 -05:00
Clint Branham b3005e1e72 feat: replace per-node baked ISOs with HTTP answer server
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>
2026-03-25 14:38:15 -05:00
Clint Branham 943a9e61dd feat: add -Force cleanup that bypasses Terraform
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>
2026-03-25 14:27:43 -05:00
Clint Branham eb0ffdfeba fix: include Docker storage resources in version-filtered Terraform targets
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>
2026-03-25 14:19:02 -05:00
Clint Branham 80871f750b feat: consolidate infrastructure into Terraform + terraform destroy cleanup
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>
2026-03-25 14:16:52 -05:00
Clint Branham 2e126b96a3 fix: CloudInit regenerate calls PUT endpoint instead of GET dump
RegenerateCloudInitImage was calling GET /cloudinit/dump?type=user
which returns the cloud-init YAML content, not a task UPID. The
cmdlet then passed this YAML string to WaitForTask, causing a 501
error trying to poll a URI like "GET nodes/.../tasks/%23cloud-config..."

Fixed to call PUT /nodes/{node}/qemu/{vmid}/cloudinit which is the
correct regeneration endpoint that returns a UPID.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:06:28 -05:00
Clint Branham f5c950f7fa fix: version-safe provisioning with Terraform -target
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>
2026-03-25 13:57:42 -05:00
Clint Branham db3629e705 feat: support -Version for -Provision and -Cleanup commands
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>
2026-03-25 13:52:44 -05:00
Clint Branham 117b10d222 refactor: make all integration test files self-contained
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>
2026-03-25 13:40:05 -05:00
Clint Branham 72280e3697 fix: don't clean up test VM in 06_VMs — later files depend on it
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>
2026-03-25 13:32:51 -05:00
Clint Branham df70e278ed fix: inject pwsh variables directly instead of using -- separator
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>
2026-03-25 13:25:49 -05:00
Clint Branham d3d73e5702 fix: move config.json from CACHE_DIR to WORK_DIR (/tmp/pve-integration)
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>
2026-03-25 13:23:33 -05:00
Clint Branham 858047c477 fix: check correct config path in dev.ps1 integration readiness check
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>
2026-03-25 13:21:53 -05:00
Clint Branham 7d74bc9750 feat: add test filter support to run-integration.sh
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>
2026-03-25 13:05:27 -05:00
Clint Branham f62e02e2a2 refactor: redesign dev.ps1 with switch-based CLI and -Tests filter
Replace positional $Command parameter with switches that can be combined:
  -Provision -Integration -Cleanup -DockerHost 172.16.40.113

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

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

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

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

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:53:24 -05:00
Clint Branham 12874d168d refactor: split monolithic integration tests into numbered per-area files
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>
2026-03-25 11:51:49 -05:00
GoodOlClint 59327a1329 Merge pull request #29 from GoodOlClint/fix/emit-native-types
feat: emit native types instead of Newtonsoft JObject/JArray in public APIs
2026-03-25 11:46:57 -05:00
GoodOlClint 298294b687 Merge pull request #30 from GoodOlClint/copilot/sub-pr-29
chore: remove stale D013 compliance report and fix JsonHelper XML doc nullability
2026-03-25 11:45:14 -05:00
copilot-swe-agent[bot] 4a63055c10 chore: remove pre-correction D013-compliance.md and fix JsonHelper XML doc comment nullability
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/2feecd30-2ba1-40bc-b1b5-c1fcc485a319
2026-03-25 16:42:29 +00:00
copilot-swe-agent[bot] 9e22036abd Initial plan 2026-03-25 16:38:23 +00:00
Clint Branham 10c277af10 fix: resolve F085 — PveClusterJoinInfo Nodelist/Totem D013 violations
D013 compliance scan found 2 remaining Newtonsoft type exposures:
- PveClusterJoinInfo.Nodelist: JArray → List<Dictionary<string, object?>>
- PveClusterJoinInfo.Totem: JObject → Dictionary<string, object?>

Both now use NativeListConverter/NativeDictionaryConverter for
deserialization. Full D013 compliance report added.

Updated findings.json: F085 status open with scan evidence.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:31:05 -05:00
Clint Branham 3ce19a721c feat: emit native types instead of Newtonsoft JObject/JArray in public APIs
Replace all Newtonsoft.Json.Linq types (JObject, JArray, JToken) exposed
in public service methods, model properties, and cmdlet OutputTypes with
native .NET types (Dictionary<string, object?>, List<Dictionary<string,
object?>>, PSObject).

New utilities:
- JsonHelper: ToNative, ToDictionary, ToListOfDictionaries for recursive
  JToken→native conversion
- NativeListConverter / NativeDictionaryConverter: JsonConverters for
  model properties that deserialize JArray/JObject to native types

Services updated (8 methods):
- NodeService: GetNodeConfig, GetNodeDns → Dictionary<string, object?>
- BackupService: GetNotBackedUp → List<Dictionary<string, object?>>
- ClusterConfigService: GetClusterConfig, GetTotem, GetQdevice → Dictionary
- HaService: GetManagerStatus → Dictionary<string, object?>
- VmService: GetGuestExecStatus → Dictionary<string, object?>

Models updated (2 properties):
- PvePool.Members: JArray → List<Dictionary<string, object?>>
- PveHaRule.Properties: JObject → Dictionary<string, object?>

Cmdlets updated (5):
- GetPveClusterConfigCmdlet: OutputType JToken → Dictionary<string, object>
- GetPveNodeConfigCmdlet: iteration updated for Dictionary
- GetPveNodeDnsCmdlet: iteration updated for Dictionary
- GetPveBackupInfoCmdlet: iteration updated for List<Dictionary>
- InvokePveVmGuestExecCmdlet: polling updated for Dictionary

Added D013 to DECISIONS.md: cmdlets must emit only native or module-defined types.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 11:12:57 -05:00
GoodOlClint 20ed5de69e Merge pull request #27 from GoodOlClint/feat/cluster-config-ha
feat: add cluster config and HA management cmdlets
2026-03-24 18:50:07 -05:00
Clint Branham 51b51354ef fix: address second round of Copilot review feedback
- Replace null-forgiving operator on Marshal.PtrToStringUni with
  null-coalescing fallback (AddPveClusterMemberCmdlet.cs:64)
- GetNextId now throws InvalidOperationException instead of silently
  returning 0 when API response cannot be parsed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:47:43 -05:00
GoodOlClint e54f9fb749 Merge branch 'main' into feat/cluster-config-ha 2026-03-24 18:41:20 -05:00
GoodOlClint 358e104db2 Merge pull request #26 from GoodOlClint/fix/f039-f084-quick-fixes
fix: resolve F039 bare catches and F084 session secret exposure
2026-03-24 18:41:01 -05:00
Clint Branham f1a3676723 fix: address Copilot review feedback on PR #27
- Extract ParseLinks helper to PveCmdletBase for shared link parsing
  with WriteWarning on malformed entries (was duplicated in 3 cmdlets)
- Fix GetClusterConfig to return data payload, not full API envelope
- Fix OutputType on GetPveClusterConfigCmdlet to JObject
- Fix link doc comments to use correct key format (link0..link7)
- Add null-safe Properties hashtable conversion in HA rule cmdlets
- Use case-insensitive Mode comparison in MovePveHaResourceCmdlet
- Remove unused using directives

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:40:10 -05:00
GoodOlClint b646a611e6 Merge pull request #28 from GoodOlClint/copilot/sub-pr-26
fix: narrow exception catches in PingGuestAgent and ImportPveOvaCmdlet
2026-03-24 18:37:22 -05:00
copilot-swe-agent[bot] f400336803 fix: narrow exception catches in PingGuestAgent and ImportPveOvaCmdlet
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/e7930807-5536-4c55-96f8-0712aee44e00
2026-03-24 23:35:27 +00:00
copilot-swe-agent[bot] d91c93aca2 Initial plan 2026-03-24 23:31:32 +00:00
Clint Branham da2037d0c5 feat: add cluster config and HA management cmdlets (F060, F053)
Cluster Config (F060) — 11 new cmdlets:
- Get-PveClusterStatus, Get-PveClusterNextId
- Get/Set-PveClusterOption (datacenter settings)
- Get-PveClusterConfig, Get-PveClusterConfigNode
- Add/Remove-PveClusterConfigNode (cluster membership)
- Get-PveClusterJoinInfo, Add-PveClusterMember (join workflow)
- New-PveCluster (create cluster)

HA Management (F053) — 14 new cmdlets:
- Get/New/Set/Remove-PveHaResource (HA managed VMs/CTs)
- Move-PveHaResource (migrate/relocate via HA manager)
- Get/New/Set/Remove-PveHaGroup (node groups + priorities)
- Get-PveHaStatus (HA manager status)
- Get/New/Set/Remove-PveHaRule (PVE 9.0+ version-gated)

All cmdlets follow established conventions:
- sealed classes with [OutputType]
- ConfirmImpact.High on destructive operations (D006)
- SecureString for password parameter in Add-PveClusterMember (D002)
- Uri.EscapeDataString on all path segments (D003)
- Verb class constants (D011)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:28:11 -05:00
GoodOlClint c33ef930c8 Merge branch 'main' into fix/f039-f084-quick-fixes 2026-03-24 18:25:06 -05:00
GoodOlClint 6f74db10ed Merge pull request #25 from GoodOlClint/docs/review-scan-8
docs: review scan-8 findings update
2026-03-24 18:24:52 -05:00
Clint Branham c0a37d5ef3 fix: resolve F039 bare catches and F084 session secret exposure
F039 (regressed): Replace bare catch blocks with filtered catches that
exclude OutOfMemoryException and StackOverflowException, per D004.
- VmService.PingGuestAgent: catch now filters fatal exceptions
- ImportPveOvaCmdlet: catch now filters fatal exceptions + WriteVerbose

F084 (new): Add PveSession format view to PSProxmoxVE.format.ps1xml
that shows only Hostname, Port, AuthMode, IsExpired, and ServerVersion
in default table output. Sensitive properties (Ticket, ApiToken,
CsrfToken) are hidden from default display but remain accessible
via Select-Object * or direct property access.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:18:56 -05:00
Clint Branham f311c738b4 docs: review scan-8 findings update
Scan-8 full re-scan of PSProxmoxVE module (2026-03-24).

New findings:
- F084 [medium/security]: PveSession exposes auth secrets in pipeline output

Regressed:
- F039 [medium]: Bare catch blocks in VmService.PingGuestAgent and
  ImportPveOvaCmdlet (D004 violation)

Resolved:
- F059: VM/CT-level firewall was already supported via Level parameter

Findings DB: F001-F084, 10 open + 1 regressed + 72 resolved + 1 wont_fix

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 18:15:46 -05:00
GoodOlClint 0789601e33 Merge pull request #24 from GoodOlClint/fix/ci-concurrency
Add concurrency group to integration tests workflow
2026-03-24 15:57:25 -05:00
Clint Branham fb5d00aad0 fix(ci): add concurrency group to queue integration test runs
Prevents overlapping integration test runs on the self-hosted runner.
Uses cancel-in-progress: false so each run completes its full
lifecycle (including cleanup) before the next one starts.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-24 15:53:11 -05:00
GoodOlClint 0f3e2c16d9 Merge pull request #23 from GoodOlClint/copilot/set-up-copilot-instructions
chore: add .github/copilot-instructions.md
2026-03-24 15:42:54 -05:00
GoodOlClint 5a27baa96a Merge branch 'main' into copilot/set-up-copilot-instructions 2026-03-24 15:41:29 -05:00
GoodOlClint 80652cbe92 Merge pull request #20 from GoodOlClint/feat/storage-iscsi-nfs-params
Add iSCSI/NFS parameters to New-PveStorage and shared storage integration tests
2026-03-24 15:41:09 -05:00
GoodOlClint a0c0b84323 Merge pull request #21 from GoodOlClint/copilot/sub-pr-20
fix(storage): iSCSI portal defaulting, Pool/CephPool selection, NFS format validation
2026-03-24 15:39:47 -05:00
copilot-swe-agent[bot] efc3e3a8e0 chore: add .github/copilot-instructions.md
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/45c5e8bb-c3e5-461c-9064-6cdafab4daa0
2026-03-24 20:38:49 +00:00
copilot-swe-agent[bot] 3cd5400695 Initial plan 2026-03-24 20:37:18 +00:00
copilot-swe-agent[bot] f76ff513e9 fix(storage): implement Portal defaulting for iSCSI, fix Pool/CephPool selection, add NFS format validation
Co-authored-by: GoodOlClint <151449+GoodOlClint@users.noreply.github.com>
Agent-Logs-Url: https://github.com/GoodOlClint/PSProxmoxVE/sessions/d84722e4-409e-4ced-bfbc-f323715b8bdc
2026-03-24 20:36:46 +00:00
copilot-swe-agent[bot] 79c227f131 Initial plan 2026-03-24 20:30:56 +00:00
Clint Branham 2bf7f45ebf fix(ci): mount /lib/modules into NFS container for automatic kernel module loading
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>
2026-03-24 15:24:06 -05:00
Clint Branham c385f4a620 fix(tests): correct NFS and iSCSI storage test parameters
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>
2026-03-24 14:57:45 -05:00
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
GoodOlClint ab768e114a Merge pull request #18 from GoodOlClint/feat/multi-node-storage-vm
Multi-node PVE provisioning and Docker-based shared storage
2026-03-24 14:09:31 -05:00