feat(a2): port leaf PS scripts to ci_orchestrator CLI
Implements Phase A2 of plans/implementation-plan-A-B.md: - commands/wait.py -> wait-ready (replaces Wait-VMReady.ps1) - commands/vm.py -> vm remove + vm cleanup (replaces Remove-BuildVM.ps1, Cleanup-OrphanedBuildVMs.ps1); cleanup accepts injected VmBackend (Phase C ESXi hook preserved) - commands/monitor.py -> monitor disk + monitor runner (replaces Watch-DiskSpace.ps1, Watch-RunnerHealth.ps1) - commands/report.py -> report job (replaces Get-CIJobSummary.ps1) Each PS script reduced to a 3-line shim that delegates to the Python CLI and preserves \0. Tests: 69 pytest cases across new test_commands_*.py modules; ruff clean, mypy --strict clean, coverage 74.5% (>=70 gate).
This commit is contained in:
+16
-123
@@ -1,130 +1,23 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Stops and permanently deletes an ephemeral build VM.
|
||||
|
||||
.DESCRIPTION
|
||||
Performs a graceful shutdown (soft stop) with a timeout fallback to
|
||||
a hard stop, then uses vmrun deleteVM to remove all VM files.
|
||||
Finally, removes the clone directory from disk.
|
||||
|
||||
This script is designed to be called from a try/finally block so
|
||||
it always runs, even on build failure.
|
||||
|
||||
.PARAMETER VMPath
|
||||
Full path to the clone VM's .vmx file.
|
||||
|
||||
.PARAMETER VmrunPath
|
||||
Full path to vmrun.exe.
|
||||
Default: C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe
|
||||
|
||||
.PARAMETER GracefulTimeoutSeconds
|
||||
Seconds to wait for graceful shutdown before forcing power-off.
|
||||
Default: 15
|
||||
|
||||
.EXAMPLE
|
||||
.\Remove-BuildVM.ps1 -VMPath "F:\CI\BuildVMs\Clone_run-42_20260508_143022\Clone_run-42_20260508_143022.vmx"
|
||||
#>
|
||||
[CmdletBinding(SupportsShouldProcess)]
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string] $VMPath,
|
||||
|
||||
[string] $VmrunPath = 'C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe',
|
||||
|
||||
[ValidateRange(1, 60)]
|
||||
[int] $GracefulTimeoutSeconds = 15
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Continue' # Don't stop on errors — always try next step
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
Import-Module (Join-Path $PSScriptRoot '_Common.psm1') -Force
|
||||
|
||||
$cloneDir = Split-Path $VMPath -Parent
|
||||
|
||||
Write-Host "[Remove-BuildVM] Destroying VM: $VMPath"
|
||||
|
||||
# ── Step 1: Check if VM exists at all ────────────────────────────────────────
|
||||
if (-not (Test-Path $VMPath -PathType Leaf)) {
|
||||
Write-Warning "[Remove-BuildVM] VMX file not found — nothing to remove: $VMPath"
|
||||
# Still attempt to remove the directory in case of partial clone
|
||||
if ((Test-Path $cloneDir) -and $PSCmdlet.ShouldProcess($cloneDir, 'Remove partial clone directory')) {
|
||||
Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
# Shim: delegates to the Python ci_orchestrator CLI.
|
||||
# Original PS implementation moved to git history; see plans/A2-closeout.md.
|
||||
$pyArgs = @()
|
||||
foreach ($a in $args) {
|
||||
if ($a -is [string] -and $a.Length -gt 1 -and $a.StartsWith('-') -and -not $a.StartsWith('--')) {
|
||||
$name = $a.Substring(1)
|
||||
$kebab = [Regex]::Replace($name, '(?<=[a-z0-9])([A-Z])', '-$1')
|
||||
$kebab = [Regex]::Replace($kebab, '([A-Z]+)([A-Z][a-z])', '$1-$2')
|
||||
$pyArgs += '--' + $kebab.ToLower()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (-not $PSCmdlet.ShouldProcess($VMPath, 'Stop and delete build VM')) { return }
|
||||
|
||||
# ── Step 2: Graceful stop ─────────────────────────────────────────────────────
|
||||
Write-Host "[Remove-BuildVM] Sending soft stop (timeout: ${GracefulTimeoutSeconds}s)..."
|
||||
$stopSoft = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||||
-Operation stop -Arguments @($VMPath, 'soft') -TimeoutSeconds $GracefulTimeoutSeconds
|
||||
if ($stopSoft.TimedOut) {
|
||||
Write-Warning "[Remove-BuildVM] Soft stop timed out after ${GracefulTimeoutSeconds}s."
|
||||
} elseif ($stopSoft.ExitCode -eq 0) {
|
||||
Write-Host "[Remove-BuildVM] VM stopped gracefully."
|
||||
}
|
||||
|
||||
# ── Step 3: Force stop if still running ──────────────────────────────────────
|
||||
if ($stopSoft.TimedOut -or $stopSoft.ExitCode -ne 0) {
|
||||
Write-Host "[Remove-BuildVM] Graceful stop did not succeed — forcing hard stop..."
|
||||
$stopHard = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||||
-Operation stop -Arguments @($VMPath, 'hard') -TimeoutSeconds 60
|
||||
if ($stopHard.TimedOut) {
|
||||
Write-Warning "[Remove-BuildVM] Hard stop timed out after 60s."
|
||||
else {
|
||||
$pyArgs += $a
|
||||
}
|
||||
}
|
||||
|
||||
# Poll vmrun list until VMX is gone — vmware-vmx.exe may hold file locks briefly
|
||||
# after stop. Waiting for the entry to disappear ensures deleteVM won't race the
|
||||
# process exit.
|
||||
$vmxNorm = $VMPath.Trim().ToLower()
|
||||
$releaseDeadline = (Get-Date).AddSeconds(20)
|
||||
while ((Get-Date) -lt $releaseDeadline) {
|
||||
$listedVMs = & $VmrunPath -T ws list 2>&1 |
|
||||
Where-Object { $_ -notmatch '^Total running' } |
|
||||
ForEach-Object { "$_".Trim().ToLower() }
|
||||
if ($listedVMs -notcontains $vmxNorm) { break }
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
|
||||
# ── Step 4: Delete VM via vmrun — retry up to 3× with backoff ────────────────
|
||||
Write-Host "[Remove-BuildVM] Deleting VM..."
|
||||
$deleteOutput = ''
|
||||
$deleteExit = -1
|
||||
foreach ($delay in @(0, 3, 6)) {
|
||||
if ($delay -gt 0) {
|
||||
Write-Host "[Remove-BuildVM] Retrying deleteVM in ${delay}s..."
|
||||
Start-Sleep -Seconds $delay
|
||||
}
|
||||
$delResult = Invoke-VmrunBounded -VmrunPath $VmrunPath `
|
||||
-Operation deleteVM -Arguments @($VMPath) -TimeoutSeconds 90
|
||||
$deleteOutput = $delResult.Output
|
||||
$deleteExit = $delResult.ExitCode
|
||||
if ($delResult.TimedOut) {
|
||||
Write-Warning "[Remove-BuildVM] deleteVM timed out after 90s."
|
||||
$deleteExit = -1
|
||||
}
|
||||
if ($deleteExit -eq 0) { break }
|
||||
}
|
||||
|
||||
if ($deleteExit -ne 0) {
|
||||
Write-Warning "[Remove-BuildVM] vmrun deleteVM failed after retries (exit $deleteExit): $deleteOutput"
|
||||
Write-Warning "[Remove-BuildVM] Will attempt manual directory removal."
|
||||
}
|
||||
|
||||
# ── Step 5: Remove clone directory (catches any leftover files) ───────────────
|
||||
if (Test-Path $cloneDir) {
|
||||
Write-Host "[Remove-BuildVM] Removing clone directory: $cloneDir"
|
||||
Remove-Item $cloneDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
if (Test-Path $cloneDir) {
|
||||
Write-Warning "[Remove-BuildVM] Clone directory could not be fully removed: $cloneDir"
|
||||
Write-Warning " Manual cleanup required."
|
||||
} else {
|
||||
Write-Host "[Remove-BuildVM] Clone directory removed."
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[Remove-BuildVM] VM destruction complete."
|
||||
$venvPython = $env:CI_VENV_PYTHON
|
||||
if (-not $venvPython) { $venvPython = 'F:\CI\python\venv\Scripts\python.exe' }
|
||||
& $venvPython -m ci_orchestrator vm remove @pyArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
Reference in New Issue
Block a user