From a5f6b99aa6e4ad50c9a5281d1df05bb2fa7bc35e Mon Sep 17 00:00:00 2001 From: svelderrainruiz Date: Fri, 27 Feb 2026 14:38:36 -0800 Subject: [PATCH] Harden host-release 2020 VIPM drill diagnostics and account preflight --- ...host-release-2020-vipm-lifecycle-drill.yml | 1 + ...ssert-InstallerHarnessMachinePreflight.ps1 | 26 +++++++ scripts/Install-WorkspaceFromManifest.ps1 | 27 +++++++- ...voke-HostRelease2020VipmLifecycleDrill.ps1 | 69 ++++++++++++++++--- ...pmLifecycleDrillWorkflowContract.Tests.ps1 | 1 + 5 files changed, 112 insertions(+), 12 deletions(-) diff --git a/.github/workflows/host-release-2020-vipm-lifecycle-drill.yml b/.github/workflows/host-release-2020-vipm-lifecycle-drill.yml index ad2e0bb..2b15408 100644 --- a/.github/workflows/host-release-2020-vipm-lifecycle-drill.yml +++ b/.github/workflows/host-release-2020-vipm-lifecycle-drill.yml @@ -63,6 +63,7 @@ jobs: -ExpectedLabviewYear '2020' ` -DockerContext 'desktop-linux' ` -DockerCheckSeverity warning ` + -RequireNonSystemAccount ` -OutputPath $reportPath if ($LASTEXITCODE -ne 0) { if (Test-Path -LiteralPath $reportPath -PathType Leaf) { diff --git a/scripts/Assert-InstallerHarnessMachinePreflight.ps1 b/scripts/Assert-InstallerHarnessMachinePreflight.ps1 index f934868..d1c1bdb 100644 --- a/scripts/Assert-InstallerHarnessMachinePreflight.ps1 +++ b/scripts/Assert-InstallerHarnessMachinePreflight.ps1 @@ -37,6 +37,10 @@ param( [Parameter()] [int]$NativeCommandTimeoutSeconds = 15 + + , + [Parameter()] + [switch]$RequireNonSystemAccount ) Set-StrictMode -Version Latest @@ -405,6 +409,28 @@ Add-Check ` -Detail $headlessDetail ` -Severity 'error' +$runnerIdentity = '' +try { + $runnerIdentity = [string][System.Security.Principal.WindowsIdentity]::GetCurrent().Name +} catch { + $runnerIdentity = '' +} +if ([string]::IsNullOrWhiteSpace($runnerIdentity)) { + $runnerIdentity = 'unknown' +} +$runnerIsSystem = ($runnerIdentity -ieq 'NT AUTHORITY\SYSTEM') +$serviceAccountPass = (-not $RequireNonSystemAccount) -or (-not $runnerIsSystem) +$serviceAccountDetail = if ($serviceAccountPass) { + ("identity='{0}'; require_non_system={1}" -f $runnerIdentity, [bool]$RequireNonSystemAccount) +} else { + ("runner_account_unsupported: identity='{0}' is not allowed for this lane; reconfigure runner service to a non-system account with LabVIEW licensing." -f $runnerIdentity) +} +Add-Check ` + -Name 'runner:service_account' ` + -Passed $serviceAccountPass ` + -Detail $serviceAccountDetail ` + -Severity 'error' + $nsisResolved = $NsisPath if (-not (Test-Path -LiteralPath $nsisResolved -PathType Leaf)) { $makensis = Get-Command makensis -ErrorAction SilentlyContinue diff --git a/scripts/Install-WorkspaceFromManifest.ps1 b/scripts/Install-WorkspaceFromManifest.ps1 index 8d78e48..36a1eaf 100644 --- a/scripts/Install-WorkspaceFromManifest.ps1 +++ b/scripts/Install-WorkspaceFromManifest.ps1 @@ -689,6 +689,8 @@ function Invoke-RunnerCliPplCapabilityCheck { buildspec_log_path = '' detected_labview_executable = '' detected_labview_year = '' + runner_cli_log_path = Join-Path $statusRoot ("workspace-installer-ppl-{0}-runner-cli.log" -f $RequiredBitness) + runner_cli_output_tail = @() } try { @@ -756,7 +758,20 @@ function Invoke-RunnerCliPplCapabilityCheck { ) $result.command = @($commandArgs) - & $RunnerCliPath @commandArgs | ForEach-Object { Write-Host $_ } + $runnerCliOutputLines = @( + & $RunnerCliPath @commandArgs 2>&1 | ForEach-Object { [string]$_ } + ) + foreach ($line in @($runnerCliOutputLines)) { + Write-Host $line + } + if (-not [string]::IsNullOrWhiteSpace([string]$result.runner_cli_log_path)) { + $runnerCliLogParent = Split-Path -Path ([string]$result.runner_cli_log_path) -Parent + Ensure-Directory -Path $runnerCliLogParent + Set-Content -LiteralPath ([string]$result.runner_cli_log_path) -Value @($runnerCliOutputLines) -Encoding utf8 + } + if (@($runnerCliOutputLines).Count -gt 0) { + $result.runner_cli_output_tail = @($runnerCliOutputLines | Select-Object -Last 40) + } $result.exit_code = $LASTEXITCODE if ($result.exit_code -ne 0) { throw "runner-cli ppl build failed with exit code $($result.exit_code)." @@ -1146,9 +1161,13 @@ $pplCapabilityChecks = [ordered]@{ command = @() exit_code = $null labview_install_root = '' + labview_ini_path = '' + expected_labview_cli_port = 0 buildspec_log_path = '' detected_labview_executable = '' detected_labview_year = '' + runner_cli_log_path = '' + runner_cli_output_tail = @() } '64' = [ordered]@{ status = 'not_run' @@ -1163,9 +1182,13 @@ $pplCapabilityChecks = [ordered]@{ command = @() exit_code = $null labview_install_root = '' + labview_ini_path = '' + expected_labview_cli_port = 0 buildspec_log_path = '' detected_labview_executable = '' detected_labview_year = '' + runner_cli_log_path = '' + runner_cli_output_tail = @() } } $vipPackageBuildCheck = [ordered]@{ @@ -2053,6 +2076,8 @@ try { buildspec_log_path = [string]$capabilityResult.buildspec_log_path detected_labview_executable = [string]$capabilityResult.detected_labview_executable detected_labview_year = [string]$capabilityResult.detected_labview_year + runner_cli_log_path = [string]$capabilityResult.runner_cli_log_path + runner_cli_output_tail = @($capabilityResult.runner_cli_output_tail) } Add-PostActionSequenceEntry -Sequence $postActionSequence -Phase 'ppl-build' -Bitness $bitness -Status ([string]$capabilityResult.status) -Message ([string]$capabilityResult.message) diff --git a/scripts/Invoke-HostRelease2020VipmLifecycleDrill.ps1 b/scripts/Invoke-HostRelease2020VipmLifecycleDrill.ps1 index 3467028..2a7267c 100644 --- a/scripts/Invoke-HostRelease2020VipmLifecycleDrill.ps1 +++ b/scripts/Invoke-HostRelease2020VipmLifecycleDrill.ps1 @@ -146,7 +146,7 @@ $resolvedIterationOutputRoot = [System.IO.Path]::GetFullPath($IterationOutputRoo $resolvedSmokeWorkspaceRoot = [System.IO.Path]::GetFullPath($SmokeWorkspaceRoot) $phaseResults = [System.Collections.Generic.List[object]]::new() -$report = [ordered]@{ + $report = [ordered]@{ schema_version = '1.0' generated_at_utc = (Get-Date).ToUniversalTime().ToString('o') status = 'fail' @@ -174,6 +174,7 @@ $report = [ordered]@{ output_root = $resolvedIterationOutputRoot smoke_workspace_root = $resolvedSmokeWorkspaceRoot script_path = $iterationScriptPath + iteration_exit_code = $null } smoke = [ordered]@{} vipm_lifecycle = [ordered]@{ @@ -223,14 +224,17 @@ try { & pwsh @iterationArgs | Out-Host $iterationExitCode = if ($null -eq $LASTEXITCODE) { 0 } else { [int]$LASTEXITCODE } - if ($iterationExitCode -ne 0) { - Throw-DrillError -ReasonCode 'iteration_failed' -Message ("Invoke-WorkspaceInstallerIteration.ps1 exited with code {0}." -f $iterationExitCode) + $report.details.iteration.iteration_exit_code = $iterationExitCode + if ($iterationExitCode -eq 0) { + Add-PhaseResult -Target $phaseResults -Phase 'iteration' -Status 'pass' -ReasonCode 'ok' + } else { + Add-PhaseResult -Target $phaseResults -Phase 'iteration' -Status 'fail' -ReasonCode 'iteration_failed' -Message ("Invoke-WorkspaceInstallerIteration.ps1 exited with code {0}." -f $iterationExitCode) } - Add-PhaseResult -Target $phaseResults -Phase 'iteration' -Status 'pass' -ReasonCode 'ok' $iterationSummaryPath = [string]$report.artifacts.iteration_summary if (-not (Test-Path -LiteralPath $iterationSummaryPath -PathType Leaf)) { - Throw-DrillError -ReasonCode 'iteration_summary_missing' -Message ("Iteration summary is missing: {0}" -f $iterationSummaryPath) + $summaryReason = if ($iterationExitCode -ne 0) { 'iteration_failed' } else { 'iteration_summary_missing' } + Throw-DrillError -ReasonCode $summaryReason -Message ("Iteration summary is missing: {0}" -f $iterationSummaryPath) } $summary = Get-Content -LiteralPath $iterationSummaryPath -Raw | ConvertFrom-Json -Depth 50 @@ -242,7 +246,8 @@ try { $exerciseReportPath = Join-Path $runOutputRoot 'exercise-report.json' $report.artifacts.exercise_report = $exerciseReportPath if (-not (Test-Path -LiteralPath $exerciseReportPath -PathType Leaf)) { - Throw-DrillError -ReasonCode 'exercise_report_missing' -Message ("Exercise report is missing: {0}" -f $exerciseReportPath) + $exerciseReason = if ($iterationExitCode -ne 0) { 'iteration_failed' } else { 'exercise_report_missing' } + Throw-DrillError -ReasonCode $exerciseReason -Message ("Exercise report is missing: {0}" -f $exerciseReportPath) } $exerciseReport = Get-Content -LiteralPath $exerciseReportPath -Raw | ConvertFrom-Json -Depth 50 @@ -259,28 +264,57 @@ try { $smokeStatus = [string]$smokeReport.status $executionProfile = [string]$smokeReport.execution_profile $selectedPplStatus = '' + $selectedPplMessage = '' + $selectedPplBuildspecLogPath = '' + $selectedPplRunnerCliLogPath = '' if ($null -ne $smokeReport.ppl_capability_checks -and $null -ne $smokeReport.ppl_capability_checks.PSObject.Properties[$SelectedPplBitness]) { - $selectedPplStatus = [string]$smokeReport.ppl_capability_checks.PSObject.Properties[$SelectedPplBitness].Value.status + $selectedPplNode = $smokeReport.ppl_capability_checks.PSObject.Properties[$SelectedPplBitness].Value + $selectedPplStatus = [string]$selectedPplNode.status + $selectedPplMessage = [string]$selectedPplNode.message + $selectedPplBuildspecLogPath = [string]$selectedPplNode.buildspec_log_path + if ($selectedPplNode.PSObject.Properties.Name -contains 'runner_cli_log_path') { + $selectedPplRunnerCliLogPath = [string]$selectedPplNode.runner_cli_log_path + } } $vipBuildStatus = [string]$smokeReport.vip_package_build_check.status $vipOutputPath = [string]$smokeReport.vip_package_build_check.output_vip_path + $smokeErrors = @($smokeReport.errors | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) + $smokeWarnings = @($smokeReport.warnings | ForEach-Object { [string]$_ } | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) $report.details.smoke = [ordered]@{ status = $smokeStatus execution_profile = $executionProfile selected_ppl_status = $selectedPplStatus + selected_ppl_message = $selectedPplMessage + selected_ppl_buildspec_log_path = $selectedPplBuildspecLogPath + selected_ppl_runner_cli_log_path = $selectedPplRunnerCliLogPath vip_build_status = $vipBuildStatus vip_output_path = $vipOutputPath + errors = @($smokeErrors) + warnings = @($smokeWarnings) } - if ($smokeStatus -ne 'succeeded') { - Throw-DrillError -ReasonCode 'smoke_status_failed' -Message ("Smoke installer report status is not succeeded: {0}" -f $smokeStatus) - } if ($executionProfile -ne 'host-release') { Throw-DrillError -ReasonCode 'smoke_execution_profile_mismatch' -Message ("Smoke execution profile is not host-release: {0}" -f $executionProfile) } if ($selectedPplStatus -ne 'pass') { - Throw-DrillError -ReasonCode 'ppl_gate_failed' -Message ("PPL gate status for bitness {0} is not pass: {1}" -f $SelectedPplBitness, $selectedPplStatus) + $pplFailureDetails = @() + if (-not [string]::IsNullOrWhiteSpace($selectedPplStatus)) { + $pplFailureDetails += ("status={0}" -f $selectedPplStatus) + } + if (-not [string]::IsNullOrWhiteSpace($selectedPplMessage)) { + $pplFailureDetails += ("message={0}" -f $selectedPplMessage) + } + if (-not [string]::IsNullOrWhiteSpace($selectedPplBuildspecLogPath)) { + $pplFailureDetails += ("buildspec_log={0}" -f $selectedPplBuildspecLogPath) + } + if (-not [string]::IsNullOrWhiteSpace($selectedPplRunnerCliLogPath)) { + $pplFailureDetails += ("runner_cli_log={0}" -f $selectedPplRunnerCliLogPath) + } + if (@($smokeErrors).Count -gt 0) { + $pplFailureDetails += ("smoke_errors={0}" -f (@($smokeErrors) -join ' | ')) + } + Throw-DrillError -ReasonCode 'ppl_gate_failed' -Message ("PPL gate status for bitness {0} is not pass. {1}" -f $SelectedPplBitness, (@($pplFailureDetails) -join '; ')) } if ($vipBuildStatus -ne 'pass') { Throw-DrillError -ReasonCode 'vip_build_not_pass' -Message ("VIP package build status is not pass: {0}" -f $vipBuildStatus) @@ -288,6 +322,19 @@ try { if ([string]::IsNullOrWhiteSpace($vipOutputPath) -or -not (Test-Path -LiteralPath $vipOutputPath -PathType Leaf)) { Throw-DrillError -ReasonCode 'vip_output_missing' -Message ("VIP output package is missing: {0}" -f $vipOutputPath) } + if ($smokeStatus -ne 'succeeded') { + $smokeFailureDetails = @() + if (@($smokeErrors).Count -gt 0) { + $smokeFailureDetails += ("errors={0}" -f (@($smokeErrors) -join ' | ')) + } + if (@($smokeWarnings).Count -gt 0) { + $smokeFailureDetails += ("warnings={0}" -f (@($smokeWarnings) -join ' | ')) + } + Throw-DrillError -ReasonCode 'smoke_status_failed' -Message ("Smoke installer report status is not succeeded: {0}. {1}" -f $smokeStatus, (@($smokeFailureDetails) -join '; ')) + } + if ($iterationExitCode -ne 0) { + Throw-DrillError -ReasonCode 'iteration_failed' -Message ("Invoke-WorkspaceInstallerIteration.ps1 exited with code {0}, but smoke contract checks unexpectedly passed." -f $iterationExitCode) + } Add-PhaseResult -Target $phaseResults -Phase 'smoke_contract' -Status 'pass' -ReasonCode 'ok' $vipmReportPath = [string]$report.artifacts.vipm_lifecycle_report diff --git a/tests/HostRelease2020VipmLifecycleDrillWorkflowContract.Tests.ps1 b/tests/HostRelease2020VipmLifecycleDrillWorkflowContract.Tests.ps1 index 4bf0757..0041e58 100644 --- a/tests/HostRelease2020VipmLifecycleDrillWorkflowContract.Tests.ps1 +++ b/tests/HostRelease2020VipmLifecycleDrillWorkflowContract.Tests.ps1 @@ -33,6 +33,7 @@ Describe 'Host release 2020 VIPM lifecycle drill workflow contract' { $script:workflowContent | Should -Match 'runs-on:\s*\[self-hosted,\s*windows,\s*self-hosted-windows-lv,\s*installer-harness\]' $script:workflowContent | Should -Match 'Assert-InstallerHarnessMachinePreflight\.ps1' $script:workflowContent | Should -Match "ExpectedLabviewYear '2020'" + $script:workflowContent | Should -Match '-RequireNonSystemAccount' $script:workflowContent | Should -Match 'Invoke-HostRelease2020VipmLifecycleDrill\.ps1' $script:workflowContent | Should -Match "TargetLabviewYear', '2020'" $script:workflowContent | Should -Match 'host-release-2020-vipm-lifecycle-drill-report-\$\{\{\s*github\.run_id\s*\}\}'