The Dockerfile.test stages only install packages (no COPY), but the
build context changed from tests/infrastructure/ to repo root. Without
a .dockerignore, Docker sends the entire repo (250MB) to the daemon
before building, causing CI to hang.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace tests/infrastructure/Dockerfile with tests/Dockerfile.test
(single multi-stage Dockerfile for both CI and local dev)
- CI container-image job now builds from Dockerfile.test target dev-infra
- Add ARM support: PowerShell installed via dotnet tool on arm64,
APT package on amd64
- Replace tests/dev.sh (bash) with tests/dev.ps1 (PowerShell) for
cross-platform support (Windows, macOS, Linux)
- Add -DockerHost parameter for running x86 containers on a remote
Docker host from ARM Macs (rsyncs repo, uses SSH Docker transport)
- Add -NoCleanup switch to keep nested PVE VMs after integration tests
- integration command now provisions nested PVE VMs instead of testing
against a pre-existing PVE directly
- Share /opt/pve-isos host path between CI and local dev (was separate
Docker named volume)
- Delete tools/Invoke-Tests.ps1 (unused, overlapped with run-integration.sh)
- Add .gitignore entries for Terraform state/artifacts
- Update all documentation references
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The node name "pve" was being masked in all CI logs because GitHub
Actions auto-masks secret values. Since the node name is not sensitive,
use vars.PVE_TARGET_NODE instead of secrets.PVE_TARGET_NODE.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
All changes now go through pull requests. Branch protection is enabled
on main (required build checks, required review, admin enforced).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Dockerfile.test: multi-stage build with dev (ARM+x86) and dev-infra
(x86 only, adds Terraform + PVE provisioning tools) targets
- docker-compose.test.yml: services for both targets, .env.test support
- dev.sh: helper script wrapping container lifecycle and run-integration.sh
- .env.test.example: template for integration test configuration
- .gitignore: exclude .env.test, whitelist .env.*.example
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Provision, test, and cleanup jobs now call run-integration.sh instead
of inlining ~170 lines of shell. Logic changes only need to be made
in one place, shared between CI and local dev.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Single source of truth for the provision → test → cleanup lifecycle,
callable from both CI workflows and local dev containers.
Subcommands: provision, test [8|9|all], cleanup, all
Detects CI vs local via $GITHUB_ACTIONS and adjusts output accordingly.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- generate-help.ps1: rewritten to use Update-MarkdownHelp (merge) by
default, preserving hand-written descriptions and examples. Only
New-MarkdownHelp on genuinely new cmdlets. -Force flag for full regen.
- docs/cmdlets: parameter metadata updated via platyPS merge, CRLF
normalized to LF
- Removed stale Send-PveIso.md (cmdlet renamed to Send-PveFile)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- CI workflow: PVE_PASSWORD now reads from secrets.PVE_TEST_PASSWORD
- variables.tf: removed default password, requires TF_VAR env var
- Integration README: examples use <your-test-password> placeholder
- create-api-token.sh: example IP changed to pve.example.com
- .gitignore: added .env/.env.* exclusion
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Runner VM now has 2 runner instances registered, so both test matrix
entries can execute simultaneously against their respective pre-
provisioned nested PVE VMs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
GitHub Actions redacts any value that matches a secret or masked string
in job outputs, making them empty in downstream jobs. This affected
host IPs, tokens, and even port 8006 (substring of PVE_ENDPOINT).
The provision job now writes connection details to
/opt/pve-isos/test-config.json on the shared volume. Test jobs read
this file directly with jq — no job outputs needed for sensitive values.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
::add-mask:: on the VM IP addresses caused GitHub Actions to redact
them in job outputs, making needs.provision.outputs.pve*_host empty
in the test jobs. The IPs are internal lab addresses, not sensitive.
Token masking is retained.
Also removes terraform debug output from prior commit.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
${{ runner.temp }} may resolve differently inside containers. Use
${RUNNER_TEMP} shell variable with unquoted heredoc instead. Added
debug output to diagnose ISO path issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restructures the integration test workflow from a monolithic sequential
job into separate provision → test → cleanup stages:
- provision: creates ALL nested PVE VMs in a single terraform apply
(parallel), waits for APIs, creates tokens, passes outputs to tests
- test: matrix [pve9, pve8] consumes provision outputs, no provisioning
- cleanup: always runs, API-only teardown for all VMs
Terraform refactored to for_each with pve_instances map variable,
enabling parallel ISO upload and VM creation.
New caching scripts reduce redundant downloads:
- ensure-base-iso.sh: downloads PVE ISOs to /opt/pve-isos if missing
- ensure-cloud-images.sh: caches cloud image + OVA with 7-day TTL
- prepare-auto-iso.sh: --cache-dir flag with hash-based skip
Runner no longer needs manual ISO provisioning (zero-touch setup).
cleanup-images bumped to min-versions-to-keep: 3 to survive overlapping
runs. All jobs gated against dependabot.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SharpCompress 0.47.3 renamed ReaderFactory.Open() to OpenReader() and
now requires an explicit ReaderOptions parameter. The synchronous API
is still available — no async conversion needed.
Closes#12
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The dependabot actor check was only on the build job. The container-image
and cleanup-images jobs still ran — cleanup-images with if: always()
deleted GHCR container images while the main branch integration run was
still using them, causing PVE 8 tests to fail with "image not found".
Now all four integration jobs (build, container-image, integration,
cleanup-images) are gated with github.actor != 'dependabot[bot]'.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Suspend-PveContainer and Restart-PveContainer gained ConfirmImpact.High
in F062/F063 fixes. The "Should throw when no session is active" Pester
tests invoke these cmdlets without -Confirm:$false, causing the
confirmation prompt to fire before the session check — failing in
non-interactive CI on all platforms.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
unit-tests.yml: The publishable project targets netstandard2.0 only
(D009/F047), so the net48 build matrix entry fails. PS 5.1 loads
netstandard2.0 assemblies on .NET Framework 4.8, so the PS 5.1 Pester
tests now use the netstandard2.0 artifact.
integration-tests.yml: Skip entire workflow for dependabot PRs since
integration tests on the self-hosted runner are expensive and dependency
bumps don't warrant full end-to-end testing. Also updates dotnet SDK
from 9.0.x to 10.0.x for consistency.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Restart-PveContainer gained ConfirmImpact.High in the F062 fix, which
correctly prompts for confirmation on destructive operations. The
integration test was missing -Confirm:$false, causing a
NullReferenceException in non-interactive CI. The cascading failure also
broke the Copy-PveContainer test (F081).
Also updates CONTRIBUTING.md to reference .NET SDK 10.0+ (was 9.0+) to
match all CI workflows and test project TFM (F083).
Includes scan-6 review report and findings database update (F001-F083).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
F058 (critical): Replace while(true) infinite-loop task polling with
TaskService.WaitForTask in 5 container snapshot and storage cmdlets.
F073+F047 (high): Migrate net9.0 → net10.0 across both source .csproj
files, build.yml, publish.yml, and test helper.
F071 (medium): Add Uri.EscapeDataString() to all inline URL path
segments in ~16 cmdlets that bypass service classes (D003).
F062+F063 (medium): Add ConfirmImpact.High to Restart-PveContainer
and Suspend-PveContainer (D006).
F075 (medium): Generate markdown help docs for 89 cmdlets that were
missing documentation (170 total, up from 81).
F072 (low): Remove unused System.Text.Json dependency from Core.csproj.
F074 (low): Raise publish smoke-test threshold from 60 to 150.
F065 (low): Add .github/ISSUE_TEMPLATE/config.yml.
F066 (low): Add CODEOWNERS.
Also fix _TestHelper.ps1 net9.0 → net10.0 framework reference.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add scan-4 full review: 1 resolved (F040), 6 new findings (F071-F076).
27 open findings, 49 resolved. No regressions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- xUnit test project now targets net10.0 instead of net9.0 (EOL Nov 2026)
- Updated GuestAgentExtCmdlets tests for SecureString Password parameter
- Added Timeout parameter tests to GuestAgentCmdlets
- Added ConfirmImpact.High assertions for Suspend/Restart-PveVm
- Added -Confirm:$false to Suspend-PveVm integration test call
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced hardcoded PVE host IP and API token in Capture-UploadDiff.ps1
with placeholder values that must be filled in before running.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sealed 105 cmdlet classes that were not already sealed — cmdlets are leaf
classes and sealing enables compiler optimizations.
Added [OutputType(typeof(void))] to 53 cmdlets that were missing the
attribute. All 169 cmdlet files now declare OutputType.
Added ConfirmImpact.High to Suspend-PveVm and Restart-PveVm to match
the convention used by Stop-PveVm and Reset-PveVm.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The codebase uses Newtonsoft.Json (JObject.Parse + ToObject<T>) exclusively.
Removed all [JsonPropertyName] attributes and [System.Text.Json.Serialization.
JsonIgnore] attributes from 41 model files, along with the now-unused
using System.Text.Json.Serialization imports. [JsonProperty] (Newtonsoft)
attributes remain as the sole serialization annotations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Changed VmId from int to int? across all 18 firewall cmdlets. The parameter
is conditionally required (only when Level is Vm or Container) so using a
non-nullable int with 0 as sentinel was fragile. Now checks !VmId.HasValue
instead of VmId == 0, and passes the nullable directly to service methods.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VmService, ContainerService, StorageService, and SnapshotService now wrap
user-controlled string parameters (node names, storage names) with
Uri.EscapeDataString() in URL path construction, preventing path traversal
or encoding issues. Integer parameters (vmid) are left unwrapped.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replaced 5 bare catch blocks with specific exception types:
- PveHttpClient: catch JsonException for JSON parse fallback
- PveCmdletBase: catch Exception with filter excluding OOM/SOE for polling
- VmService/ContainerService: catch PveApiException|HttpRequestException
for offline node handling
- GetPveVmCmdlet: catch PveApiException for VM enrichment failures
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Set-PveVmGuestPassword now accepts SecureString instead of plain string for
the -Password parameter, matching the established pattern in Set-PvePassword.
The password is converted via Marshal and zeroed from memory after use.
New-PveStorage now validates that -Pool and -CephPool cannot both be specified,
since both mapped to the same API key and CephPool silently overwrote Pool.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Four cmdlets (Invoke-PveNetworkApply, New/Restore/Remove-PveSnapshot) had
private WaitForTask methods with bare while(true) loops and no timeout or
failure detection. Replaced with TaskService.WaitForTask which has a 10-minute
default timeout, exit status validation, and throws PveTaskTimeoutException
or PveTaskFailedException on failure.
Invoke-PveVmGuestExec had a similar unbounded do/while polling loop. Added a
-Timeout parameter (default 300s, range 1-3600) with Stopwatch enforcement.
Also fixed bare catch in DecodeBase64 to catch FormatException specifically.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>