Files
AutopilotMonitor/.github/scripts/Assert-HostsFromRegistry.ps1
Oliver Kieselbach 8d24c5680b CI: one endpoint registry for every host the workflows use
.github/scripts/Get-Endpoints.ps1 defines the storage account (blob and table
derived from its name), the containers, the AdminConfiguration row, the legacy
mirror, the download alias, API, MCP, portal, www, docs and the timestamp
server. Workflows load it once per job; the script publisher and the local
release build call it; the dev-render anchors are built from the alias entry.

deploy-web takes NEXT_PUBLIC_API_BASE_URL and NEXT_PUBLIC_PLATFORM_STATS_MANIFEST_URL
from the registry instead of repository variables and refuses an empty value.
A new Workflow gates job fails on any host spelled out in a workflow or CI
helper; the enforced list is derived from the registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:32:36 +02:00

85 lines
3.1 KiB
PowerShell

<#
.SYNOPSIS
Fails when a workflow or a CI helper spells out a host instead of reading Get-Endpoints.ps1.
.DESCRIPTION
The CI counterpart of the URL-registry guard tests (HardcodedUrlGuardTests,
hardcodedUrls.guard.test.ts, hardcoded-urls.guard.test.ts). Enforced: every host the registry
defines -- derived from it, so the list cannot fall behind -- plus any Azure storage, App Service
or autopilotmonitor.com host, so a new or misspelled account is caught as well.
Scans .github/workflows/*.yml and .github/scripts/*.ps1 except the registry itself. Comment lines
and PowerShell block comments do not count: comments may cite URLs.
.PARAMETER Root
Repository root to scan. Defaults to the parent of .github/scripts.
#>
[CmdletBinding()]
param(
[string]$Root
)
$ErrorActionPreference = 'Stop'
if (-not $Root) {
$Root = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
}
$endpoints = & (Join-Path $PSScriptRoot 'Get-Endpoints.ps1')
$registryHosts = $endpoints.Values | ForEach-Object { ([uri]$_).Host } | Sort-Object -Unique
$patterns = @($registryHosts | ForEach-Object { [regex]::Escape($_) }) + @(
'[a-z0-9-]+\.(blob|table|queue|file)\.core\.windows\.net'
'[a-z0-9-]+\.azurewebsites\.net'
'([a-z0-9-]+\.)*autopilotmonitor\.com'
)
$hostPattern = [regex]::new('(' + ($patterns -join '|') + ')', 'IgnoreCase')
$files = @(
Get-ChildItem -Path (Join-Path $Root '.github/workflows') -File -Filter '*.yml'
Get-ChildItem -Path (Join-Path $Root '.github/workflows') -File -Filter '*.yaml'
Get-ChildItem -Path (Join-Path $Root '.github/scripts') -File -Filter '*.ps1' |
Where-Object Name -ne 'Get-Endpoints.ps1'
)
$findings = 0
foreach ($file in $files) {
$relative = [System.IO.Path]::GetRelativePath($Root, $file.FullName) -replace '\\', '/'
$isPowerShell = $file.Extension -eq '.ps1'
$inBlock = $false
$lineNumber = 0
foreach ($line in [System.IO.File]::ReadAllLines($file.FullName)) {
$lineNumber++
$code = $line
if ($isPowerShell) {
if ($inBlock) {
$end = $code.IndexOf('#>')
if ($end -lt 0) { continue }
$code = $code.Substring($end + 2)
$inBlock = $false
}
$start = $code.IndexOf('<#')
if ($start -ge 0) {
$end = $code.IndexOf('#>', $start + 2)
if ($end -lt 0) {
$code = $code.Substring(0, $start)
$inBlock = $true
} else {
$code = $code.Remove($start, $end + 2 - $start)
}
}
}
if ($code.TrimStart().StartsWith('#')) { continue }
$match = $hostPattern.Match($code)
if ($match.Success) {
Write-Host "::error file=$relative,line=$lineNumber::$($match.Value) is spelled out here -- take it from .github/scripts/Get-Endpoints.ps1."
$findings++
}
}
}
if ($findings -gt 0) {
throw "$findings host literal(s) outside the endpoint registry."
}
Write-Host "No host literal outside .github/scripts/Get-Endpoints.ps1 ($($files.Count) files scanned)."