Commit Graph

11 Commits

Author SHA1 Message Date
goodolclint-claude[bot] e942878aa2 fix: plumb skiplock/force parameters through Remove-PveVm/Container (#164)
* fix: plumb skiplock parameter through Remove-PveVm and Remove-PveContainer

When -Force is specified, both cmdlets now pass skiplock=1 to PVE, which bypasses locks.
PVE honours the skiplock parameter for root@pam only.

Also updated help text on both cmdlets to clarify the limitation and behaviour.

Fixes #136.

* fix: plumb skiplock parameter for VMs, force for containers

VM removal: add skiplock=1 parameter when -Force is specified. PVE honors it
for root@pam only; non-root callers receive 403 errors. Updated help text.

Container removal: map -Force to force=1 (LXC-specific parameter for forcing
removal of running containers). Containers do not support skiplock.

Also: clarified class and parameter documentation to remove false claims about
-Force suppressing confirmation (it does not).

Fixes #136. Addresses correctness reviewer findings.

* fix: revert unrelated formatting churn, add container force test

The em-dash-to-hyphen sweep in commit 75f4bbf touched VmService.cs and
VmServiceTests.cs outside the skiplock/force change; restore the
original text there, including the <vmid> XML-doc escape that
had been un-escaped and was malforming the generated documentation.

Add ContainerServiceTests.cs, covering the untested force=1
query-string plumbing in ContainerService.RemoveContainer.

---------

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
2026-09-02 13:20:04 -05:00
goodolclint-claude[bot] a7d2b877f5 fix: populate Privileges on PvePermission from access/permissions (#163)
UserService.GetPermissions unwrapped the path-keyed /access/permissions
response and discarded prop.Value, so every returned PvePermission had a
path but no privilege data. Add a Privileges dictionary populated from
the privilege map; a key's presence means the privilege is granted and
its value is whether the grant propagates to sub-paths, matching PVE's
documented "propagate boolean" contract for that endpoint.

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
2026-09-02 13:19:53 -05:00
goodolclint-claude[bot] 1bf7483a4e fix: escape dynamic path segments in three Remove-* cmdlets (#161)
* fix: escape dynamic path segments in three Remove-* cmdlets

Wrap Storage, Vnet, and Zone parameters with Uri.EscapeDataString() in
RemovePveStorageCmdlet, RemovePveSdnVnetCmdlet, and RemovePveSdnZoneCmdlet
to prevent path traversal attacks via the API path.

Add xUnit test demonstrating that escaped paths preserve percent-encoding
(preventing path collapse) while unescaped paths allow segment traversal.

Fixes #145

* fix: route Remove-Pve{Storage,SdnVnet,SdnZone} through their services

Delete the private PveHttpClient construction and inline
Uri.EscapeDataString call in RemovePveStorageCmdlet,
RemovePveSdnVnetCmdlet and RemovePveSdnZoneCmdlet; call
StorageService.RemoveStorage / NetworkService.RemoveSdnZone /
NetworkService.RemoveSdnVnet instead, which already escape the
identifier identically and are now the single place doing so.

Add ValidatePattern on the Storage/Vnet/Zone parameters as defense
in depth, anchored with \A/\z so a trailing newline cannot slip a
disallowed character past the gate.

Replace PveHttpClientPathEscapingTests with a version that actually
regression-tests the real Uri parser (asserts both that %2F survives
and that the unescaped form is absent), and add
StorageServiceTests/NetworkServiceTests cases that mock
IPveHttpClient and verify the exact escaped DELETE path for a
traversal-attempt name.

---------

Co-authored-by: goodolclint-claude[bot] <323206664+goodolclint-claude[bot]@users.noreply.github.com>
2026-09-02 16:59:57 +00:00
goodolclint-claude[bot] c5e73f4fc9 fix: Restart-PveVm uses PVE's native reboot endpoint (cmdlet + tests) 2026-09-01 17:46:21 +00:00
goodolclint-claude[bot] 2df9f47e2d test: cover ClusterConfigService.WaitForQuorum
Returns once quorate, throws TimeoutException when quorum never arrives,
and keeps polling through a PveApiException from the restarting API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-01 06:22:55 +00:00
Clint Branham 56dcf22cea fix: address PR #69 review feedback
- VmService.ExecuteGuestCommand: guard against null elements in -Args.
  The old JSON-serialization tolerated nulls (as "null"); the repeated-key
  path would NRE in EncodeFormValue. Throw a clear ArgumentException instead.
- findings.json: refresh the stale counters block (untouched since F085) to
  the actual ledger state — next_id 92, resolved 83 — and bump last_updated
  to 2026-05-22. last_scan_date stays 2026-03-26 (F086–F091 came from issue
  triage, not a formal review scan).
- VmServiceTests: add empty-array (single command entry) and null-element
  (throws) cases.

Note: F091 is the correct next ID — F086–F090 already exist from prior
merged PRs (#60/#61/#66/#67); only the counters were lagging.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:38:54 -05:00
Clint Branham bc71ed4a12 fix: deliver Invoke-PveVmGuestExec -Args to the guest as argv
ExecuteGuestCommand JSON-serialized the args array into the agent/exec
'input-data' field — which is the process's STDIN, not its arguments. So
guest commands ran with no argv: cmd.exe started interactively and the
JSON blob ['/c','echo',...] arrived at its prompt.

PVE's agent/exec 'command' parameter is itself an array (element 0 = the
executable, the rest = argv) sent as repeated form keys. The low-level
client couldn't express repeated keys (Dictionary<string,string> only),
so:

- Add PostAsync(string, IEnumerable<KeyValuePair<string,string>>) to
  IPveHttpClient/PveHttpClient; BuildFormContent now emits one key=value
  field per pair, so a key may repeat.
- ExecuteGuestCommand builds command = [exe] + args as repeated 'command'
  fields and no longer touches input-data.

Tests: form-encoder repeated-key + per-value encoding cases; VmService
tests asserting the command array, order, and absence of input-data; an
integration regression guard that echoes an arg and checks it round-trips
as stdout.

Tracked as F091. Closes #68.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-22 14:32:04 -05:00
Clint Branham 4e4bc11872 test: add xUnit, Pester, and integration tests for cluster config + HA
xUnit (47 new tests, 429 total):
- ClusterConfigServiceTests: 25 tests covering all 14 service methods
  including URL encoding, null guards, and error responses
- HaServiceTests: 22 tests covering resources, groups, status, and rules
  with URI encoding verification (vm:100 → vm%3A100)

Pester (187 new tests, 1525 total):
- ClusterConfigCmdlets.Tests.ps1: 11 cmdlets — parameter metadata,
  ValidateSet/ValidateRange/ValidateLength, ShouldProcess, ConfirmImpact
- HaCmdlets.Tests.ps1: 14 cmdlets — same pattern

Integration (ClusterConfig.Integration.Tests.ps1):
- Full 2-node cluster lifecycle: create → join → options → HA groups → cleanup
- Uses root@pam ticket auth for cluster create/join (API tokens lack permission)
- Reconnects with API token after privileged operations
- Skip helpers for standalone (no node B) and non-PVE-9 environments

Fixes:
- GetClusterConfig returns JToken (not JObject) to handle array responses
  from /cluster/config on standalone nodes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 17:40:00 -05:00
Clint Branham a241714125 fix: update CloudInit regenerate test for PUT endpoint
Test was still expecting GetAsync on cloudinit/dump but the service
now calls PutAsync on cloudinit (regenerate endpoint). Updated mock
setup and assertions to match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 15:32:25 -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
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