131 lines
6.2 KiB
PowerShell
131 lines
6.2 KiB
PowerShell
[CmdletBinding()]
|
|
param(
|
|
[Parameter(Mandatory = $true, Position = 0)]
|
|
[ValidateSet('Prepare', 'Status', 'Run', 'Collect', 'Stop', 'Reset')]
|
|
[string] $Action,
|
|
[Parameter(Mandatory = $false)]
|
|
[ValidatePattern('^[a-z0-9][a-z0-9-]{0,63}$')]
|
|
[string] $RunId = '',
|
|
[string] $VmName = $(if ($env:RVBOX_WINDOWS_VM) { $env:RVBOX_WINDOWS_VM } elseif ($env:RVBOX_TEST_VBOX_VM) { $env:RVBOX_TEST_VBOX_VM } else { 'rvbox-win10-test' }),
|
|
[string] $Snapshot = $(if ($env:RVBOX_WINDOWS_BASELINE_SNAPSHOT) { $env:RVBOX_WINDOWS_BASELINE_SNAPSHOT } elseif ($env:RVBOX_TEST_VBOX_SNAPSHOT) { $env:RVBOX_TEST_VBOX_SNAPSHOT } else { 'baseline-disk-first' })
|
|
)
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
$VBoxManage = if ($env:VBOXMANAGE) { $env:VBOXMANAGE } else { 'VBoxManage' }
|
|
$GuestUser = if ($env:RVBOX_WINDOWS_GUEST_USER) { $env:RVBOX_WINDOWS_GUEST_USER } else { $env:RVBOX_TEST_GUEST_USER }
|
|
$GuestPasswordFile = if ($env:RVBOX_WINDOWS_GUEST_PASSWORD_FILE) {
|
|
$env:RVBOX_WINDOWS_GUEST_PASSWORD_FILE
|
|
} else {
|
|
$env:RVBOX_TEST_GUEST_PASSWORD_FILE
|
|
}
|
|
$LeaseRoot = if ($env:RVBOX_WINDOWS_LEASE_DIR) { $env:RVBOX_WINDOWS_LEASE_DIR } else { Join-Path $PSScriptRoot '..\..\.test-runs\windows' }
|
|
|
|
function Invoke-VBox([string[]] $Arguments) {
|
|
$output = & $VBoxManage @Arguments 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "VBoxManage failed ($LASTEXITCODE): $($output -join ' ')"
|
|
}
|
|
return $output
|
|
}
|
|
|
|
function Require-RunId {
|
|
if ([string]::IsNullOrWhiteSpace($RunId)) { throw "$Action requires -RunId" }
|
|
}
|
|
|
|
function Acquire-Lease {
|
|
New-Item -ItemType Directory -Force -Path $LeaseRoot | Out-Null
|
|
$path = Join-Path $LeaseRoot 'lease.lock'
|
|
try {
|
|
$script:Lease = [System.IO.File]::Open($path, [System.IO.FileMode]::OpenOrCreate, [System.IO.FileAccess]::ReadWrite, [System.IO.FileShare]::None)
|
|
$bytes = [Text.Encoding]::UTF8.GetBytes("$VmName`n$RunId`n$([DateTime]::UtcNow.ToString('o'))`n")
|
|
$Lease.SetLength(0); $Lease.Write($bytes, 0, $bytes.Length); $Lease.Flush($true)
|
|
} catch {
|
|
throw "Windows test VM is already leased: $path"
|
|
}
|
|
}
|
|
|
|
function Release-Lease {
|
|
if ($script:Lease) { $script:Lease.Dispose(); $script:Lease = $null }
|
|
}
|
|
|
|
function Guest-Args([string[]] $Arguments) {
|
|
if ([string]::IsNullOrWhiteSpace($GuestUser) -or [string]::IsNullOrWhiteSpace($GuestPasswordFile)) {
|
|
throw 'Set the guest user and password-file environment variables in the host environment; secrets are never read from repository files or printed.'
|
|
}
|
|
if (-not (Test-Path -LiteralPath $GuestPasswordFile -PathType Leaf)) {
|
|
throw "Guest password file does not exist: $GuestPasswordFile"
|
|
}
|
|
return @('guestcontrol', $VmName, '--username', $GuestUser, '--passwordfile', $GuestPasswordFile) + $Arguments
|
|
}
|
|
|
|
function Wait-PowerOff {
|
|
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
|
$current = (Invoke-VBox @('showvminfo', $VmName, '--machinereadable') | Where-Object { $_ -like 'VMState=*' }) -replace '^VMState="?([^"\r\n]+)"?$', '$1'
|
|
if ($current -eq 'poweroff') { return }
|
|
Start-Sleep -Seconds 1
|
|
}
|
|
throw "VM did not power off within the bounded stop deadline: $VmName"
|
|
}
|
|
|
|
function Wait-GuestReady {
|
|
for ($attempt = 0; $attempt -lt 60; $attempt++) {
|
|
$properties = Invoke-VBox @('guestproperty', 'enumerate', $VmName)
|
|
if (($properties -match '/GuestAdd/Version') -and ($properties -match '/GuestInfo/OS/Release')) {
|
|
return
|
|
}
|
|
Start-Sleep -Seconds 1
|
|
}
|
|
throw "Guest Additions did not become ready within the bounded boot deadline: $VmName"
|
|
}
|
|
|
|
try {
|
|
if ($Action -eq 'Status') {
|
|
Invoke-VBox @('showvminfo', $VmName, '--machinereadable') | Where-Object { $_ -match '^(VMState|name|SnapshotName)=' }
|
|
exit 0
|
|
}
|
|
Require-RunId
|
|
Acquire-Lease
|
|
try {
|
|
$state = (Invoke-VBox @('showvminfo', $VmName, '--machinereadable') | Where-Object { $_ -like 'VMState=*' }) -replace '^VMState="?([^"\r\n]+)"?$', '$1'
|
|
switch ($Action) {
|
|
'Prepare' {
|
|
if ($state -eq 'running') { Wait-GuestReady }
|
|
else {
|
|
Invoke-VBox @('startvm', $VmName, '--type', 'headless') | Out-Null
|
|
Wait-GuestReady
|
|
}
|
|
Invoke-VBox (Guest-Args @('run', '--exe', 'C:\Windows\System32\whoami.exe', '--wait-stdout', '--wait-stderr', '--unquoted-args', '--', '/groups')) | Out-Null
|
|
Invoke-VBox (Guest-Args @('run', '--exe', 'C:\Windows\System32\query.exe', '--wait-stdout', '--wait-stderr', '--unquoted-args', '--', 'user')) | Out-Null
|
|
Write-Output "prepared VM=$VmName run=$RunId"
|
|
}
|
|
'Run' {
|
|
$guestBinary = if ($env:RVBOX_WINDOWS_GUEST_BINARY) { $env:RVBOX_WINDOWS_GUEST_BINARY } else { 'C:\ProgramData\RVBox\test\rvbox.exe' }
|
|
Invoke-VBox (Guest-Args @('run', '--exe', $guestBinary, '--wait-stdout', '--wait-stderr', '--unquoted-args', '--', '--check-config', '--config', 'C:\ProgramData\RVBox\client.toml')) | Out-Null
|
|
Write-Output "ran native Windows smoke run=$RunId"
|
|
}
|
|
'Collect' {
|
|
$destination = Join-Path $LeaseRoot $RunId
|
|
New-Item -ItemType Directory -Force -Path $destination | Out-Null
|
|
$guestArtifacts = if ($env:RVBOX_WINDOWS_GUEST_ARTIFACTS) { $env:RVBOX_WINDOWS_GUEST_ARTIFACTS } else { 'C:\ProgramData\RVBox\test-artifacts' }
|
|
Invoke-VBox (Guest-Args @('copyfrom', $guestArtifacts, $destination, '--recursive')) | Out-Null
|
|
Write-Output "collected native artifacts under $destination"
|
|
}
|
|
'Stop' {
|
|
if ($state -eq 'running') { Invoke-VBox @('controlvm', $VmName, 'acpipowerbutton') | Out-Null; Wait-PowerOff }
|
|
Write-Output "requested graceful stop VM=$VmName"
|
|
}
|
|
'Reset' {
|
|
if ($state -eq 'running') { Invoke-VBox @('controlvm', $VmName, 'acpipowerbutton') | Out-Null; Wait-PowerOff }
|
|
Invoke-VBox @('snapshot', $VmName, 'restore', $Snapshot) | Out-Null
|
|
Write-Output "restored baseline snapshot=$Snapshot VM=$VmName run=$RunId"
|
|
}
|
|
}
|
|
} finally {
|
|
Release-Lease
|
|
}
|
|
} catch {
|
|
Release-Lease
|
|
Write-Error $_
|
|
exit 1
|
|
}
|