Public/Export-AzLocalSideloadStatusReport.ps1
|
function Export-AzLocalSideloadStatusReport { <# .SYNOPSIS Builds a sideload status report (markdown + JUnit XML) from the shared state records and optional plan error rows. .DESCRIPTION Public reporting helper for the v0.8.7 on-prem sideloading automation. Reads every per-cluster state JSON under StateRoot\state, summarises progress by state (Queued / Copying / Copied / Verified / Imported / Failed / Stale) with throughput + ETA, and appends any plan error rows (NotInAllowList, UnknownAuthAccountId, NoCatalogEntry). Optionally writes the markdown and a JUnit XML (one test case per cluster; Failed/error rows fail) to disk. .PARAMETER StateRoot Shared UNC root containing state\. .PARAMETER Plan Optional plan rows from Resolve-AzLocalSideloadPlan, used to surface configuration/selection error rows in the report. .PARAMETER OutputPath Optional directory to write sideload-status.md and sideload-junit.xml. .OUTPUTS [PSCustomObject] with Markdown, JUnitXml, Counts, HasFailures. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$StateRoot, [Parameter(Mandatory = $false)][PSCustomObject[]]$Plan, [Parameter(Mandatory = $false)][string]$OutputPath ) $stateDir = Join-Path -Path $StateRoot -ChildPath 'state' $states = @() if (Test-Path -LiteralPath $stateDir) { $states = @( Get-ChildItem -LiteralPath $stateDir -Filter '*.json' -File -ErrorAction SilentlyContinue | ForEach-Object { try { Get-Content -LiteralPath $_.FullName -Raw | ConvertFrom-Json } catch { $null } } | Where-Object { $null -ne $_ } ) } $lines = New-Object System.Collections.Generic.List[string] $lines.Add('## Sideload status') $lines.Add('') if ($states.Count -gt 0) { $lines.Add('| Cluster | Version | State | Progress | Mbps | ETA (UTC) | Owner | Retries | Message |') $lines.Add('| --- | --- | --- | --- | --- | --- | --- | --- | --- |') foreach ($s in ($states | Sort-Object ClusterName)) { $pct = if ([long]$s.TotalBytes -gt 0) { ('{0}%' -f [math]::Round(([double]$s.CopiedBytes / [double]$s.TotalBytes) * 100, 1)) } else { '-' } $rowText = ('| {0} | {1} | {2} | {3} | {4} | {5} | {6} | {7} | {8} |' -f ` $s.ClusterName, $s.Version, $s.State, $pct, $s.Mbps, $s.EtaUtc, $s.OwningMachine, $s.Retries, $s.Message) $lines.Add($rowText) } $lines.Add('') } else { $lines.Add('_No sideload state records found._') $lines.Add('') } $errorRows = @() if ($Plan) { $errorRows = @($Plan | Where-Object { @('NotInAllowList', 'UnknownAuthAccountId', 'NoCatalogEntry', 'NoneReady') -contains [string]$_.Status }) } if ($errorRows.Count -gt 0) { $lines.Add('### Plan warnings / errors') $lines.Add('') $lines.Add('| Cluster | Status | Message |') $lines.Add('| --- | --- | --- |') foreach ($e in $errorRows) { $lines.Add(('| {0} | {1} | {2} |' -f $e.ClusterName, $e.Status, $e.Message)) } $lines.Add('') } $markdown = ($lines -join [Environment]::NewLine) # JUnit: one case per cluster state; Failed (and plan errors) fail. $cases = New-Object System.Collections.Generic.List[object] foreach ($s in $states) { if ([string]$s.State -eq 'Failed') { $cases.Add(@{ Name = ('{0} ({1})' -f $s.ClusterName, $s.Version); Failure = @{ Message = [string]$s.Message; Type = 'SideloadFailed' } }) } else { $cases.Add(@{ Name = ('{0} ({1}) [{2}]' -f $s.ClusterName, $s.Version, $s.State) }) } } foreach ($e in $errorRows) { $cases.Add(@{ Name = ('{0} [{1}]' -f $e.ClusterName, $e.Status); Failure = @{ Message = [string]$e.Message; Type = [string]$e.Status } }) } $suites = @(@{ Name = 'Sideload'; TestCases = $cases.ToArray() }) $junit = New-AzLocalPipelineJUnitXml -TestSuitesName 'AzLocalSideload' -Suites $suites $counts = $states | Group-Object State | ForEach-Object { [PSCustomObject]@{ State = $_.Name; Count = $_.Count } } $hasFailures = (@($states | Where-Object { [string]$_.State -eq 'Failed' }).Count -gt 0) -or ($errorRows.Count -gt 0) if (-not [string]::IsNullOrWhiteSpace($OutputPath)) { if (-not (Test-Path -LiteralPath $OutputPath)) { New-Item -ItemType Directory -Path $OutputPath -Force | Out-Null } Write-Utf8NoBomFile -Path (Join-Path $OutputPath 'sideload-status.md') -Content $markdown Write-Utf8NoBomFile -Path (Join-Path $OutputPath 'sideload-junit.xml') -Content $junit } return [PSCustomObject]@{ Markdown = $markdown JUnitXml = $junit Counts = @($counts) HasFailures = $hasFailures } } function Add-AzLocalSideloadStepSummary { <# .SYNOPSIS Renders the sideload status report into the CI/CD step summary. .DESCRIPTION Thin wrapper that calls Export-AzLocalSideloadStatusReport and appends the markdown to the pipeline step summary (GitHub/ADO/Local handled internally by Add-AzLocalPipelineStepSummary). .OUTPUTS [PSCustomObject] the report object from Export-AzLocalSideloadStatusReport. #> [CmdletBinding()] [OutputType([PSCustomObject])] param( [Parameter(Mandatory = $true)][ValidateNotNullOrEmpty()][string]$StateRoot, [Parameter(Mandatory = $false)][PSCustomObject[]]$Plan, [Parameter(Mandatory = $false)][string]$OutputPath ) $report = Export-AzLocalSideloadStatusReport -StateRoot $StateRoot -Plan $Plan -OutputPath $OutputPath Add-AzLocalPipelineStepSummary -Markdown $report.Markdown | Out-Null return $report } |