feat(a3): port build pipeline (vm new, build run, artifacts collect)

Implements Phase A3 of plans/implementation-plan-A-B.md:

- commands/vm.py: vm new (replaces New-BuildVM.ps1)

- commands/build.py: build run (replaces Invoke-RemoteBuild.ps1) with WinRM/SSH dispatch and stdout streaming for act_runner

- commands/artifacts.py: artifacts collect (replaces Get-BuildArtifacts.ps1) using transport.fetch()

- 3 PS scripts reduced to shims preserving \0

- Pester tests/{New-BuildVM,Wait-VMReady,Remove-BuildVM}.Tests.ps1 removed; equivalent cases covered in pytest

- Phase C hook: build/artifacts accept opaque VmHandle/vmx; no Windows path assumptions

Tests: 91 pytest, ruff clean, mypy --strict clean, coverage 78.27% (>=70 gate).

Master checklist + step A3 attivita + 'definizione di fatto' updated; A3-closeout.md added.
This commit is contained in:
2026-05-14 17:20:34 +02:00
parent 096ba7fe16
commit 816a15503e
15 changed files with 1900 additions and 1061 deletions
+15 -82
View File
@@ -1,43 +1,10 @@
#Requires -Version 5.1
<#
.SYNOPSIS
Creates a linked clone of the template VM for an ephemeral CI build.
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
.DESCRIPTION
Uses vmrun.exe to create a linked clone from a frozen template snapshot.
Returns the full path to the new clone's .vmx file on success.
The template VM and its snapshot are never modified.
# Shim: delegates to the Python ci_orchestrator CLI (`vm new`).
# Original PS implementation moved to git history; see plans/A3-closeout.md.
.PARAMETER TemplatePath
Full path to the template VM's .vmx file.
Example: F:\CI\Templates\WinBuild\WinBuild.vmx
.PARAMETER SnapshotName
Name of the snapshot on the template VM to clone from.
Default: BaseClean
.PARAMETER CloneBaseDir
Directory under which the new clone folder will be created.
The clone folder is named after the job ID and timestamp.
Example: F:\CI\BuildVMs
.PARAMETER JobId
Unique identifier for the CI job (e.g. run ID from Gitea Actions).
Used to name the clone: Clone_{JobId}_{yyyyMMdd_HHmmss}
.PARAMETER VmrunPath
Full path to vmrun.exe.
Default: C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe
.OUTPUTS
[string] Full path to the new clone's .vmx file.
.EXAMPLE
$vmxPath = .\New-BuildVM.ps1 `
-TemplatePath "F:\CI\Templates\WinBuild\WinBuild.vmx" `
-CloneBaseDir "F:\CI\BuildVMs" `
-JobId "run-42"
#>
[CmdletBinding()]
param(
[Parameter(Mandatory)]
@@ -55,49 +22,15 @@ param(
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe'
)
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
$pyArgs = @(
'--template-path', $TemplatePath,
'--snapshot-name', $SnapshotName,
'--clone-base-dir', $CloneBaseDir,
'--job-id', $JobId,
'--vmrun-path', $VmrunPath
)
Import-Module (Join-Path $PSScriptRoot '_Common.psm1') -Force
Resolve-VmrunPath -VmrunPath $VmrunPath | Out-Null
# --- Build clone path ---
$timestamp = Get-Date -Format 'yyyyMMdd_HHmmss'
$cloneName = "Clone_${JobId}_${timestamp}"
$cloneDir = Join-Path $CloneBaseDir $cloneName
$cloneVmx = Join-Path $cloneDir "$cloneName.vmx"
# Ensure clone base dir exists
if (-not (Test-Path $CloneBaseDir)) {
New-Item -ItemType Directory -Path $CloneBaseDir -Force | Out-Null
}
Write-Host "[New-BuildVM] Creating linked clone..."
Write-Host " Template : $TemplatePath"
Write-Host " Snapshot : $SnapshotName"
Write-Host " Clone VMX : $cloneVmx"
$sw = [System.Diagnostics.Stopwatch]::StartNew()
$cloneResult = Invoke-Vmrun -VmrunPath $VmrunPath -Operation clone `
-Arguments @($TemplatePath, $cloneVmx, 'linked', '-snapshot', $SnapshotName)
$sw.Stop()
if ($cloneResult.ExitCode -ne 0) {
# Clean up partial clone directory if it was created
if (Test-Path $cloneDir) {
Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue
}
throw "vmrun clone failed (exit $($cloneResult.ExitCode)).`nOutput: $($cloneResult.Output)"
}
if (-not (Test-Path $cloneVmx -PathType Leaf)) {
throw "vmrun reported success but clone VMX not found at: $cloneVmx"
}
Write-Host "[New-BuildVM] Clone created in $($sw.Elapsed.TotalSeconds.ToString('F1'))s : $cloneVmx"
# Return the VMX path as output
return $cloneVmx
$venvPython = $env:CI_VENV_PYTHON
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
& $venvPython -m ci_orchestrator vm new @pyArgs
exit $LASTEXITCODE