mirror of
https://github.com/jhd3197/ServerKit.git
synced 2026-10-29 08:07:37 +09:00
Introduces a full-stack Test Sandbox feature to run distro matrix tests in Docker from the UI: new backend API/service, sandbox_runs model + migration, run/log endpoints, and a new frontend page with mode selection, run history, live polling, cancel, and per-distro logs. Also adds ARM64 scripts CI coverage (ubuntu-24.04-arm) and strengthens install/test automation for minimal images and overlay rebuilds: early curl/bootstrap index refresh, stricter venv validation, release-venv glibc fallback, Node reprovisioning on source fallback, rsync --delete overlay behavior, npm bootstrap in VM install, Windows tar pinning in PowerShell harness, plus updated test docs and distro coverage notes.
450 lines
22 KiB
PowerShell
450 lines
22 KiB
PowerShell
#requires -Version 5.1
|
|
<#
|
|
.SYNOPSIS
|
|
One-click full-stack E2E test for ServerKit on Windows. Sequential edition.
|
|
|
|
.DESCRIPTION
|
|
For each distro, in turn: launch VM via Multipass, upload local working
|
|
tree, run installer, run pytest harness, collect results. Aggregates into
|
|
a single HTML report.
|
|
|
|
Sequential by design — Start-Job's separate runspace makes parameter
|
|
passing and command resolution fragile enough that parallel is not worth
|
|
the debugging cost.
|
|
|
|
.PARAMETER Distros
|
|
Distros to test. Default: ubuntu22, ubuntu24, debian12.
|
|
|
|
.PARAMETER Only
|
|
Comma-separated subset (e.g. -Only "ubuntu24").
|
|
|
|
.PARAMETER Keep
|
|
Don't delete VMs at end.
|
|
|
|
.PARAMETER ReuseVm
|
|
Skip launch + install; use an already-running sk-test-* VM. Useful for
|
|
re-running just the harness after an install succeeded.
|
|
#>
|
|
[CmdletBinding()]
|
|
param(
|
|
# Default suite: Ubuntu LTSes via Multipass + Debian 12 / Rocky 9
|
|
# via Vagrant + Hyper-V. Fedora is *not* in the default — Vagrant
|
|
# Cloud's generic/fedora boxes stop at fedora39 (EOL Nov 2024) and no
|
|
# newer pre-built Hyper-V box exists. Use `-Only fedora` to opt in
|
|
# once you've pinned a box you trust. Extra opt-in homelab targets
|
|
# (not in the default either): alma9, debian11, opensuse15.
|
|
[string[]] $Distros = @('ubuntu22','ubuntu24','debian12','rocky9'),
|
|
[string] $Only,
|
|
[switch] $Keep,
|
|
[string] $ReuseVm,
|
|
[int] $Cpus = 2,
|
|
[int] $MemoryGB = 4,
|
|
[int] $DiskGB = 15
|
|
)
|
|
|
|
# NB: keep ErrorActionPreference at Continue. Multipass writes harmless
|
|
# warnings to stderr (e.g. "cannot set permissions" when transferring to
|
|
# NTFS) and 'Stop' turns those into fatal script errors.
|
|
$ErrorActionPreference = 'Continue'
|
|
$ProgressPreference = 'SilentlyContinue'
|
|
|
|
# DistroMap drives both dispatch and image/box selection.
|
|
# backend: 'multipass' uses the Canonical Ubuntu images; 'vagrant' uses
|
|
# the Hyper-V provider with generic/* boxes. The login user differs by
|
|
# backend (ubuntu vs vagrant), which matters for chown on the harness dir.
|
|
$DistroMap = @{
|
|
'ubuntu22' = @{ backend = 'multipass'; image = '22.04'; user = 'ubuntu' }
|
|
'ubuntu24' = @{ backend = 'multipass'; image = '24.04'; user = 'ubuntu' }
|
|
'debian12' = @{ backend = 'vagrant'; image = 'generic/debian12'; user = 'vagrant' }
|
|
# fedora39 went EOL Nov 2024; use fedora40 which is supported.
|
|
'fedora' = @{ backend = 'vagrant'; image = 'generic/fedora40'; user = 'vagrant' }
|
|
'rocky9' = @{ backend = 'vagrant'; image = 'generic/rocky9'; user = 'vagrant' }
|
|
# Opt-in extras (homelab-ish coverage; all have generic/* Hyper-V boxes):
|
|
'alma9' = @{ backend = 'vagrant'; image = 'generic/alma9'; user = 'vagrant' }
|
|
'debian11' = @{ backend = 'vagrant'; image = 'generic/debian11'; user = 'vagrant' }
|
|
'opensuse15' = @{ backend = 'vagrant'; image = 'generic/opensuse15'; user = 'vagrant' }
|
|
}
|
|
|
|
if ($Only) {
|
|
# Select from the full DistroMap (not just the default suite) so opt-in
|
|
# distros like fedora/alma9 work, in the order the user listed them.
|
|
$requested = $Only -split ','
|
|
$Distros = @($requested | Where-Object { $DistroMap.ContainsKey($_) })
|
|
}
|
|
|
|
$RepoRoot = (Resolve-Path "$PSScriptRoot\..\..").Path
|
|
$RunId = Get-Date -Format 'yyyyMMdd-HHmmss'
|
|
$OutDir = Join-Path $PSScriptRoot "output\$RunId"
|
|
$null = New-Item -ItemType Directory -Force -Path $OutDir
|
|
|
|
# Seed state.json immediately so any early refresh of the report shows
|
|
# "running" rather than treating an empty dir as a failed run.
|
|
Set-Content -Path (Join-Path $OutDir 'state.json') -Value '{"running":true,"vms":{}}' -Encoding utf8
|
|
|
|
# --- locate backends -----------------------------------------------------
|
|
function Find-Exe {
|
|
param([string[]] $Paths, [string] $Cmd)
|
|
foreach ($p in $Paths) { if (Test-Path $p) { return $p } }
|
|
return (Get-Command $Cmd -ErrorAction SilentlyContinue).Source
|
|
}
|
|
|
|
$MpExe = Find-Exe -Cmd 'multipass' -Paths @(
|
|
'C:\Program Files\Multipass\bin\multipass.exe',
|
|
"$env:LOCALAPPDATA\Programs\Multipass\bin\multipass.exe"
|
|
)
|
|
$VgExe = Find-Exe -Cmd 'vagrant' -Paths @(
|
|
'C:\Program Files\Vagrant\bin\vagrant.exe',
|
|
"$env:LOCALAPPDATA\Programs\Vagrant\bin\vagrant.exe"
|
|
)
|
|
|
|
# Filter distros down to whichever backends are actually installed.
|
|
$missing = @()
|
|
$Distros = $Distros | Where-Object {
|
|
$entry = $DistroMap[$_]
|
|
if (-not $entry) { return $false }
|
|
if ($entry.backend -eq 'multipass' -and -not $MpExe) { $missing += "$_ (needs Multipass)"; return $false }
|
|
if ($entry.backend -eq 'vagrant' -and -not $VgExe) { $missing += "$_ (needs Vagrant)"; return $false }
|
|
$true
|
|
}
|
|
|
|
if (-not $Distros -or $Distros.Count -eq 0) {
|
|
Write-Host "No runnable distros. Install Multipass and/or Vagrant first." -ForegroundColor Red
|
|
if ($missing) { $missing | ForEach-Object { Write-Host " skipped: $_" -ForegroundColor Yellow } }
|
|
exit 2
|
|
}
|
|
|
|
# Vagrant + Hyper-V requires the process itself to run elevated. Being in
|
|
# the Hyper-V Administrators group is necessary but not sufficient.
|
|
$needsAdmin = $Distros | Where-Object { $DistroMap[$_].backend -eq 'vagrant' }
|
|
if ($needsAdmin) {
|
|
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole(
|
|
[Security.Principal.WindowsBuiltInRole]::Administrator)
|
|
if (-not $isAdmin) {
|
|
Write-Host "" -ForegroundColor Red
|
|
Write-Host "Vagrant + Hyper-V requires an elevated PowerShell." -ForegroundColor Red
|
|
Write-Host "Requested distros that need it: $($needsAdmin -join ', ')" -ForegroundColor Red
|
|
Write-Host "" -ForegroundColor Red
|
|
Write-Host "Either:" -ForegroundColor Yellow
|
|
Write-Host " 1. Right-click PowerShell -> Run as Administrator, then re-run this script." -ForegroundColor Yellow
|
|
Write-Host " 2. Or run with -Only ubuntu22,ubuntu24 to skip Vagrant distros." -ForegroundColor Yellow
|
|
exit 2
|
|
}
|
|
}
|
|
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
Write-Host " ServerKit E2E Test - run $RunId" -ForegroundColor Cyan
|
|
if ($MpExe) { Write-Host " Multipass: $MpExe" -ForegroundColor Cyan }
|
|
if ($VgExe) { Write-Host " Vagrant: $VgExe" -ForegroundColor Cyan }
|
|
Write-Host " Distros: $($Distros -join ', ')" -ForegroundColor Cyan
|
|
if ($missing) { $missing | ForEach-Object { Write-Host " skipped: $_" -ForegroundColor Yellow } }
|
|
Write-Host " Output: $OutDir" -ForegroundColor Cyan
|
|
Write-Host "========================================" -ForegroundColor Cyan
|
|
|
|
# NB: 'mp' is a built-in PS alias for Move-ItemProperty, so we must NOT
|
|
# define a helper function named Mp. Use $MpExe directly everywhere.
|
|
|
|
# --- live state + report regen ------------------------------------------
|
|
$script:State = @{ running = $true; vms = @{} }
|
|
function Save-State {
|
|
$json = $script:State | ConvertTo-Json -Depth 5 -Compress
|
|
Set-Content -Path (Join-Path $OutDir 'state.json') -Value $json -Encoding utf8
|
|
if (Get-Command python -ErrorAction SilentlyContinue) {
|
|
& python (Join-Path $PSScriptRoot 'report.py') $OutDir 2>&1 | Out-Null
|
|
}
|
|
}
|
|
function Set-VmStage {
|
|
param([string]$Vm, [string]$Stage)
|
|
if (-not $script:State.vms.ContainsKey($Vm)) { $script:State.vms[$Vm] = @{} }
|
|
$script:State.vms[$Vm].stage = $Stage
|
|
Save-State
|
|
}
|
|
|
|
function Test-VmInstall {
|
|
param([string]$Vm, [string]$VmOut, [string]$Tarball, [string]$VmInstallScript, [string]$HarnessDir)
|
|
|
|
New-Item -ItemType Directory -Force -Path $VmOut | Out-Null
|
|
$statusFile = Join-Path $VmOut 'install-status'
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'transferring source'
|
|
Write-Host " [$Vm] transferring source ($([math]::Round((Get-Item $Tarball).Length/1MB,1)) MB)..." -ForegroundColor DarkCyan
|
|
& $MpExe transfer $Tarball "${Vm}:/tmp/serverkit-src.tar.gz" 2>&1 | Out-Null
|
|
& $MpExe transfer $VmInstallScript "${Vm}:/tmp/vm-install.sh" 2>&1 | Out-Null
|
|
& $MpExe exec $Vm -- sudo mkdir -p /opt/serverkit-src 2>&1 | Out-Null
|
|
& $MpExe exec $Vm -- sudo tar -xzf /tmp/serverkit-src.tar.gz -C /opt/serverkit-src 2>&1 | Out-Null
|
|
& $MpExe exec $Vm -- sudo chmod +x /tmp/vm-install.sh 2>&1 | Out-Null
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'installing (npm ci + build, ~10 min)'
|
|
Write-Host " [$Vm] running install (this is the long part)..." -ForegroundColor DarkCyan
|
|
& $MpExe exec $Vm -- sh -c "sudo bash /tmp/vm-install.sh > /tmp/vm-install-stdout.log 2>&1; rc=`$?; echo EXIT_RC=`$rc; exit `$rc" `
|
|
| Out-File (Join-Path $VmOut 'install.log') -Encoding utf8
|
|
$installRC = $LASTEXITCODE
|
|
|
|
& $MpExe transfer "${Vm}:/var/log/serverkit-test-install.log" "$VmOut\vm-install.log" 2>&1 | Out-Null
|
|
& $MpExe transfer "${Vm}:/tmp/serverkit-install-status" $statusFile 2>&1 | Out-Null
|
|
$rawStatus = if (Test-Path $statusFile) { Get-Content $statusFile -Raw -ErrorAction SilentlyContinue } else { $null }
|
|
if (-not $rawStatus -or "$rawStatus".Trim() -eq '') {
|
|
$fallback = if ($installRC -eq 0) { 'OK' } else { 'FAIL' }
|
|
Set-Content -Path $statusFile -Value $fallback -Encoding ascii
|
|
}
|
|
& $MpExe exec $Vm -- sudo journalctl -u serverkit --no-pager -n 500 2>&1 `
|
|
| Out-File (Join-Path $VmOut 'journalctl.log') -Encoding utf8
|
|
|
|
$rawStatus = Get-Content $statusFile -Raw -ErrorAction SilentlyContinue
|
|
$status = if ($rawStatus) { "$rawStatus".Trim() } else { 'FAIL' }
|
|
Write-Host " [$Vm] install status: $status" -ForegroundColor $(if ($status -eq 'OK') { 'Green' } else { 'Red' })
|
|
Save-State # regenerate so install log + journalctl show up
|
|
|
|
if ($status -ne 'OK') {
|
|
Set-VmStage -Vm $Vm -Stage 'install failed'
|
|
return $status
|
|
}
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'running pytest'
|
|
Write-Host " [$Vm] running pytest harness..." -ForegroundColor DarkCyan
|
|
& $MpExe exec $Vm -- sudo mkdir -p /opt/serverkit-test 2>&1 | Out-Null
|
|
& $MpExe exec $Vm -- sudo chown ubuntu:ubuntu /opt/serverkit-test 2>&1 | Out-Null
|
|
Get-ChildItem $HarnessDir -File | ForEach-Object {
|
|
& $MpExe transfer $_.FullName "${Vm}:/opt/serverkit-test/$($_.Name)" 2>&1 | Out-Null
|
|
}
|
|
& $MpExe exec $Vm -- sudo /opt/serverkit/venv/bin/pip install -r /opt/serverkit-test/requirements.txt 2>&1 `
|
|
| Out-File (Join-Path $VmOut 'harness-deps.log') -Encoding utf8
|
|
& $MpExe exec $Vm -- sh -c "sudo /opt/serverkit/venv/bin/python -m pytest /opt/serverkit-test --json-report --json-report-file=/opt/serverkit-test/pytest-report.json -v > /tmp/pytest-stdout.log 2>&1; rc=`$?; echo EXIT_RC=`$rc; exit `$rc" `
|
|
| Out-File (Join-Path $VmOut 'pytest.log') -Encoding utf8
|
|
& $MpExe transfer "${Vm}:/tmp/pytest-stdout.log" (Join-Path $VmOut 'pytest-stdout.log') 2>&1 | Out-Null
|
|
& $MpExe transfer "${Vm}:/opt/serverkit-test/pytest-report.json" (Join-Path $VmOut 'pytest-report.json') 2>&1 | Out-Null
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'done'
|
|
return $status
|
|
}
|
|
|
|
function Test-VagrantInstall {
|
|
# Vagrant equivalent of Test-VmInstall. Uses 'vagrant upload' for file
|
|
# transfer (added in 2.2.7) and 'vagrant ssh -c' for exec. Each VM
|
|
# gets its own VAGRANT_CWD so multiple distros can coexist.
|
|
param([string]$Vm, [string]$VmOut, [string]$Tarball, [string]$VmInstallScript, [string]$HarnessDir, [string]$Box, [string]$User)
|
|
|
|
New-Item -ItemType Directory -Force -Path $VmOut | Out-Null
|
|
$statusFile = Join-Path $VmOut 'install-status'
|
|
|
|
# Per-VM Vagrant working dir holding the Vagrantfile + .vagrant state.
|
|
$vagrantSrc = Join-Path $PSScriptRoot 'vagrant\Vagrantfile'
|
|
$vagrantCwd = Join-Path $OutDir "vagrant-state\$Vm"
|
|
New-Item -ItemType Directory -Force -Path $vagrantCwd | Out-Null
|
|
Copy-Item $vagrantSrc (Join-Path $vagrantCwd 'Vagrantfile') -Force
|
|
|
|
$env:VAGRANT_CWD = $vagrantCwd
|
|
$env:SK_BOX = $Box
|
|
$env:SK_VM_NAME = $Vm
|
|
$env:SK_CPUS = "$Cpus"
|
|
$env:SK_MEMORY_MB = "$($MemoryGB * 1024)"
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'launching VM (vagrant up, ~5-10 min first time)'
|
|
Write-Host " [$Vm] vagrant up... (full output below; also saved to vagrant-up.log)" -ForegroundColor DarkCyan
|
|
# Tee so the operator can see download progress + any prompts live,
|
|
# and the log file still gets a copy for later inspection. Pipe to
|
|
# Out-Host at the end so the pipeline output is CONSUMED — otherwise
|
|
# it leaks into this function's return stream and corrupts $status.
|
|
& $VgExe up --provider=hyperv 2>&1 | Tee-Object -FilePath (Join-Path $VmOut 'vagrant-up.log') | Out-Host
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Set-Content -Path $statusFile -Value 'LAUNCH_FAILED' -Encoding ascii
|
|
Set-VmStage -Vm $Vm -Stage 'launch failed'
|
|
return 'LAUNCH_FAILED'
|
|
}
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'transferring source'
|
|
Write-Host " [$Vm] transferring source..." -ForegroundColor DarkCyan
|
|
& $VgExe upload $Tarball /tmp/serverkit-src.tar.gz 2>&1 | Out-Null
|
|
& $VgExe upload $VmInstallScript /tmp/vm-install.sh 2>&1 | Out-Null
|
|
& $VgExe ssh -c "sudo mkdir -p /opt/serverkit-src && sudo tar -xzf /tmp/serverkit-src.tar.gz -C /opt/serverkit-src && sudo chmod +x /tmp/vm-install.sh" 2>&1 | Out-Null
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'installing (npm ci + build, ~10 min)'
|
|
Write-Host " [$Vm] running install..." -ForegroundColor DarkCyan
|
|
& $VgExe ssh -c "sudo bash /tmp/vm-install.sh > /tmp/vm-install-stdout.log 2>&1; rc=`$?; echo EXIT_RC=`$rc; exit `$rc" `
|
|
2>&1 | Out-File (Join-Path $VmOut 'install.log') -Encoding utf8
|
|
$installRC = $LASTEXITCODE
|
|
|
|
# Pull canonical logs back. vagrant scp would need a plugin; we
|
|
# cat-then-redirect via vagrant ssh, which is universal.
|
|
& $VgExe ssh -c "sudo cat /var/log/serverkit-test-install.log" 2>$null `
|
|
| Out-File (Join-Path $VmOut 'vm-install.log') -Encoding utf8
|
|
& $VgExe ssh -c "sudo cat /tmp/serverkit-install-status" 2>$null `
|
|
| Out-File $statusFile -Encoding ascii
|
|
# Get-Content -Raw returns $null for empty/missing files; guard before
|
|
# calling .Trim() so a failed status capture doesn't crash the run.
|
|
$rawStatus = if (Test-Path $statusFile) { Get-Content $statusFile -Raw -ErrorAction SilentlyContinue } else { $null }
|
|
if (-not $rawStatus -or "$rawStatus".Trim() -eq '') {
|
|
$fallback = if ($installRC -eq 0) { 'OK' } else { 'FAIL' }
|
|
Set-Content -Path $statusFile -Value $fallback -Encoding ascii
|
|
}
|
|
& $VgExe ssh -c "sudo journalctl -u serverkit --no-pager -n 500" 2>$null `
|
|
| Out-File (Join-Path $VmOut 'journalctl.log') -Encoding utf8
|
|
|
|
$rawStatus = Get-Content $statusFile -Raw -ErrorAction SilentlyContinue
|
|
$status = if ($rawStatus) { "$rawStatus".Trim() } else { 'FAIL' }
|
|
Write-Host " [$Vm] install status: $status" -ForegroundColor $(if ($status -eq 'OK') { 'Green' } else { 'Red' })
|
|
Save-State
|
|
|
|
if ($status -ne 'OK') {
|
|
Set-VmStage -Vm $Vm -Stage 'install failed'
|
|
return $status
|
|
}
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'running pytest'
|
|
Write-Host " [$Vm] running pytest harness..." -ForegroundColor DarkCyan
|
|
& $VgExe ssh -c "sudo mkdir -p /opt/serverkit-test && sudo chown ${User}:${User} /opt/serverkit-test" 2>&1 | Out-Null
|
|
Get-ChildItem $HarnessDir -File | ForEach-Object {
|
|
& $VgExe upload $_.FullName "/opt/serverkit-test/$($_.Name)" 2>&1 | Out-Null
|
|
}
|
|
& $VgExe ssh -c "sudo /opt/serverkit/venv/bin/pip install -r /opt/serverkit-test/requirements.txt" 2>&1 `
|
|
| Out-File (Join-Path $VmOut 'harness-deps.log') -Encoding utf8
|
|
& $VgExe ssh -c "sudo /opt/serverkit/venv/bin/python -m pytest /opt/serverkit-test --json-report --json-report-file=/opt/serverkit-test/pytest-report.json -v > /tmp/pytest-stdout.log 2>&1; rc=`$?; echo EXIT_RC=`$rc; exit `$rc" `
|
|
2>&1 | Out-File (Join-Path $VmOut 'pytest.log') -Encoding utf8
|
|
& $VgExe ssh -c "sudo cat /tmp/pytest-stdout.log" 2>$null | Out-File (Join-Path $VmOut 'pytest-stdout.log') -Encoding utf8
|
|
& $VgExe ssh -c "sudo cat /opt/serverkit-test/pytest-report.json" 2>$null | Out-File (Join-Path $VmOut 'pytest-report.json') -Encoding utf8
|
|
|
|
Set-VmStage -Vm $Vm -Stage 'done'
|
|
return $status
|
|
}
|
|
|
|
function Remove-VagrantVm {
|
|
param([string]$Vm)
|
|
$vagrantCwd = Join-Path $OutDir "vagrant-state\$Vm"
|
|
if (-not (Test-Path $vagrantCwd)) { return }
|
|
$env:VAGRANT_CWD = $vagrantCwd
|
|
& $VgExe destroy -f 2>&1 | Out-Null
|
|
}
|
|
|
|
# --- pack source --------------------------------------------------------
|
|
# Pin to Windows' bsdtar when present: if this script is launched from a
|
|
# Git Bash session, /usr/bin (GNU tar) shadows System32 on PATH, and GNU
|
|
# tar reads "C:\..." as a remote host:path and fails with
|
|
# "Cannot connect to C: resolve failed".
|
|
$TarExe = "$env:SystemRoot\System32\tar.exe"
|
|
if (-not (Test-Path $TarExe)) { $TarExe = 'tar' }
|
|
$Tarball = Join-Path $OutDir 'serverkit-src.tar.gz'
|
|
Write-Host "`n[1/4] Packing local working tree -> $Tarball" -ForegroundColor Yellow
|
|
Push-Location $RepoRoot
|
|
try {
|
|
& $TarExe --exclude='.git' `
|
|
--exclude='venv' `
|
|
--exclude='.venv' `
|
|
--exclude='.venv-wsl' `
|
|
--exclude='node_modules' `
|
|
--exclude='dist' `
|
|
--exclude='__pycache__' `
|
|
--exclude='instance' `
|
|
--exclude='target' `
|
|
--exclude='build' `
|
|
--exclude='*.exe' `
|
|
--exclude='*.msi' `
|
|
--exclude='scripts/test/output' `
|
|
-czf $Tarball -C $RepoRoot .
|
|
if ($LASTEXITCODE -ne 0) { throw "tar failed (exit $LASTEXITCODE)" }
|
|
} finally {
|
|
Pop-Location
|
|
}
|
|
Write-Host (" Tarball: {0:N1} MB" -f ((Get-Item $Tarball).Length / 1MB))
|
|
|
|
# --- main loop ----------------------------------------------------------
|
|
$results = @()
|
|
$liveVMs = @()
|
|
$vmInstallScript = Join-Path $PSScriptRoot 'vm-install.sh'
|
|
$cloudInit = Join-Path $PSScriptRoot 'cloud-init\base.yaml'
|
|
$harnessDir = Join-Path $PSScriptRoot 'harness'
|
|
|
|
# Pre-populate stages and seed the live report, then open it in the browser.
|
|
foreach ($d in $Distros) {
|
|
$vmName = if ($ReuseVm) { $ReuseVm } else { "sk-test-$d-$RunId" }
|
|
$script:State.vms[$vmName] = @{ stage = 'pending' }
|
|
if ($ReuseVm) { break }
|
|
}
|
|
Save-State
|
|
$reportPath = Join-Path $OutDir 'report.html'
|
|
if (Test-Path $reportPath) {
|
|
Write-Host "`nLive report: $reportPath (opening in browser)" -ForegroundColor Cyan
|
|
Start-Process $reportPath
|
|
}
|
|
|
|
if ($ReuseVm) {
|
|
Write-Host "`n[2-3/4] Reusing VM: $ReuseVm" -ForegroundColor Yellow
|
|
$vmOut = Join-Path $OutDir $ReuseVm
|
|
# Reuse only supports Multipass for now (Vagrant VMs are tied to a
|
|
# specific VAGRANT_CWD that doesn't exist outside the original run).
|
|
$status = Test-VmInstall -Vm $ReuseVm -VmOut $vmOut -Tarball $Tarball `
|
|
-VmInstallScript $vmInstallScript -HarnessDir $harnessDir
|
|
$results += @{ Name = $ReuseVm; Status = $status }
|
|
$liveVMs += @{ Name = $ReuseVm; Backend = 'multipass' }
|
|
} else {
|
|
Write-Host "`n[2/4] Launching + testing $($Distros.Count) VM(s) sequentially..." -ForegroundColor Yellow
|
|
foreach ($d in $Distros) {
|
|
$vmName = "sk-test-$d-$RunId"
|
|
$entry = $DistroMap[$d]
|
|
$vmOut = Join-Path $OutDir $vmName
|
|
|
|
Write-Host "`n--- $d ($($entry.backend)) ---" -ForegroundColor Yellow
|
|
|
|
if ($entry.backend -eq 'multipass') {
|
|
Write-Host " Launching $vmName from image $($entry.image)..." -ForegroundColor DarkCyan
|
|
& $MpExe launch $entry.image --name $vmName --cpus $Cpus --memory "${MemoryGB}G" --disk "${DiskGB}G" --cloud-init $cloudInit
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Write-Host " [FAIL] $vmName launch failed" -ForegroundColor Red
|
|
$results += @{ Name = $vmName; Status = 'LAUNCH_FAILED' }
|
|
continue
|
|
}
|
|
Write-Host " [OK] $vmName launched" -ForegroundColor Green
|
|
$liveVMs += @{ Name = $vmName; Backend = 'multipass' }
|
|
$status = Test-VmInstall -Vm $vmName -VmOut $vmOut -Tarball $Tarball `
|
|
-VmInstallScript $vmInstallScript -HarnessDir $harnessDir
|
|
$results += @{ Name = $vmName; Status = $status }
|
|
}
|
|
elseif ($entry.backend -eq 'vagrant') {
|
|
Write-Host " Bringing up $vmName via Vagrant (box: $($entry.image))..." -ForegroundColor DarkCyan
|
|
$liveVMs += @{ Name = $vmName; Backend = 'vagrant' }
|
|
$status = Test-VagrantInstall -Vm $vmName -VmOut $vmOut -Tarball $Tarball `
|
|
-VmInstallScript $vmInstallScript -HarnessDir $harnessDir `
|
|
-Box $entry.image -User $entry.user
|
|
$results += @{ Name = $vmName; Status = $status }
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- report -------------------------------------------------------------
|
|
Write-Host "`n[4/4] Finalizing HTML report..." -ForegroundColor Yellow
|
|
$script:State.running = $false
|
|
Save-State
|
|
$reportHtml = Join-Path $OutDir 'report.html'
|
|
if (Test-Path $reportHtml) {
|
|
Write-Host " Report: $reportHtml" -ForegroundColor Green
|
|
} else {
|
|
Write-Host " python not on PATH. Raw output: $OutDir" -ForegroundColor Yellow
|
|
$reportHtml = $null
|
|
}
|
|
|
|
# --- teardown -----------------------------------------------------------
|
|
if ($Keep -or $ReuseVm) {
|
|
Write-Host "`nVMs left running:" -ForegroundColor Yellow
|
|
$liveVMs | ForEach-Object { Write-Host " - $($_.Name) ($($_.Backend))" }
|
|
} else {
|
|
Write-Host "`nTearing down VMs..." -ForegroundColor Yellow
|
|
foreach ($vm in $liveVMs) {
|
|
if ($vm.Backend -eq 'multipass') {
|
|
& $MpExe delete $vm.Name 2>&1 | Out-Null
|
|
} elseif ($vm.Backend -eq 'vagrant') {
|
|
Remove-VagrantVm -Vm $vm.Name
|
|
}
|
|
}
|
|
if ($MpExe) { & $MpExe purge 2>&1 | Out-Null }
|
|
}
|
|
|
|
# --- summary ------------------------------------------------------------
|
|
$failed = @($results | Where-Object { $_.Status -ne 'OK' })
|
|
Write-Host ""
|
|
if ($failed.Count -eq 0 -and $results.Count -gt 0) {
|
|
Write-Host "ALL GREEN - $($results.Count) VM(s) passed." -ForegroundColor Green
|
|
exit 0
|
|
} else {
|
|
Write-Host "FAILURES on $($failed.Count) VM(s):" -ForegroundColor Red
|
|
$failed | ForEach-Object { Write-Host " - $($_.Name): $($_.Status)" -ForegroundColor Red }
|
|
exit 1
|
|
}
|