mirror of
https://github.com/okieselbach/AutopilotMonitor.git
synced 2026-10-24 12:42:38 +00:00
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>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
<#
|
||||
.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)."
|
||||
@@ -0,0 +1,71 @@
|
||||
<#
|
||||
.SYNOPSIS
|
||||
The one place the workflows and the release scripts take their hosts from.
|
||||
|
||||
.DESCRIPTION
|
||||
CI's member of the URL registry (Shared Constants.cs, web lib/config.ts, MCP src/config.ts):
|
||||
every own host the workflows, the .github/scripts helpers and the local release build talk to,
|
||||
plus the Authenticode timestamp server, is defined here once. Assert-HostsFromRegistry.ps1
|
||||
(Workflow gates) fails on any of these hosts spelled out elsewhere under .github/.
|
||||
|
||||
The storage URLs derive from the account name, so blob and table can never point at two
|
||||
different accounts.
|
||||
|
||||
A file in the repository rather than a GitHub variable: the local release build reads it too,
|
||||
and bearer tokens are sent to these URLs -- where a token goes stays a reviewed line in git.
|
||||
|
||||
.PARAMETER ExportToGitHubEnv
|
||||
Write every entry to $GITHUB_ENV instead of returning it, so the following steps of the job
|
||||
read them as environment variables ($env:AGENT_CONTAINER_URL, ${{ env.AGENT_CONTAINER_URL }}).
|
||||
|
||||
.OUTPUTS
|
||||
[ordered] hashtable, keyed by the names the environment variables carry.
|
||||
|
||||
.EXAMPLE
|
||||
$endpoints = & "$PSScriptRoot/Get-Endpoints.ps1"
|
||||
Invoke-WebRequest -Uri "$($endpoints.AGENT_CONTAINER_URL)/version.json"
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[switch]$ExportToGitHubEnv
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$storageAccount = 'autopilotmonitoreu'
|
||||
# Pre-migration account in another tenant; only the fail-soft legacy mirror still writes there.
|
||||
$legacyStorageAccount = 'autopilotmonitor'
|
||||
|
||||
$blob = "https://$storageAccount.blob.core.windows.net"
|
||||
$table = "https://$storageAccount.table.core.windows.net"
|
||||
|
||||
$endpoints = [ordered]@{
|
||||
STORAGE_BLOB_URL = $blob
|
||||
STORAGE_TABLE_URL = $table
|
||||
AGENT_CONTAINER_URL = "$blob/agent"
|
||||
VERSIONS_CONTAINER_URL = "$blob/versions"
|
||||
STATS_CONTAINER_URL = "$blob/stats"
|
||||
# Platform settings row; CI writes only its agent and bootstrap version columns.
|
||||
ADMIN_CONFIG_URL = "$table/AdminConfiguration(PartitionKey='GlobalConfig',RowKey='config')"
|
||||
LEGACY_AGENT_CONTAINER_URL = "https://$legacyStorageAccount.blob.core.windows.net/agent"
|
||||
# Front Door in front of the agent container: what customer scripts download from.
|
||||
DOWNLOAD_ALIAS_URL = 'https://download.autopilotmonitor.com/agent'
|
||||
API_BASE_URL = 'https://autopilotmonitor-api-eu.azurewebsites.net'
|
||||
MCP_BASE_URL = 'https://mcp.autopilotmonitor.com'
|
||||
PORTAL_BASE_URL = 'https://portal.autopilotmonitor.com'
|
||||
WWW_BASE_URL = 'https://www.autopilotmonitor.com'
|
||||
DOCS_BASE_URL = 'https://docs.autopilotmonitor.com'
|
||||
CODE_SIGN_TIMESTAMP_URL = 'http://timestamp.acs.microsoft.com'
|
||||
}
|
||||
|
||||
if ($ExportToGitHubEnv) {
|
||||
if ([string]::IsNullOrWhiteSpace($env:GITHUB_ENV)) {
|
||||
throw '-ExportToGitHubEnv only works inside a GitHub Actions step.'
|
||||
}
|
||||
$endpoints.GetEnumerator() | ForEach-Object { "$($_.Key)=$($_.Value)" } |
|
||||
Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
||||
$endpoints.GetEnumerator() | ForEach-Object { Write-Host "$($_.Key) = $($_.Value)" }
|
||||
return
|
||||
}
|
||||
|
||||
return $endpoints
|
||||
@@ -81,7 +81,6 @@ param(
|
||||
[string]$SigningToken,
|
||||
[string]$KeyVaultUrl,
|
||||
[string]$CertificateName,
|
||||
[string]$TimestampUrl = 'http://timestamp.acs.microsoft.com',
|
||||
[string]$ExpectedPublisher,
|
||||
[string]$SignedOutputDir,
|
||||
[switch]$DryRun
|
||||
@@ -93,10 +92,13 @@ if (-not $RepoRoot) {
|
||||
$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot '../..')).Path
|
||||
}
|
||||
|
||||
$ContainerUrl = 'https://autopilotmonitoreu.blob.core.windows.net/agent'
|
||||
$LegacyContainerUrl = 'https://autopilotmonitor.blob.core.windows.net/agent'
|
||||
$AliasUrl = 'https://download.autopilotmonitor.com/agent'
|
||||
$TableUrl = "https://autopilotmonitoreu.table.core.windows.net/AdminConfiguration(PartitionKey='GlobalConfig',RowKey='config')"
|
||||
# Every host, the timestamp server included, from the one endpoint registry.
|
||||
$Endpoints = & (Join-Path $PSScriptRoot 'Get-Endpoints.ps1')
|
||||
$ContainerUrl = $Endpoints.AGENT_CONTAINER_URL
|
||||
$LegacyContainerUrl = $Endpoints.LEGACY_AGENT_CONTAINER_URL
|
||||
$AliasUrl = $Endpoints.DOWNLOAD_ALIAS_URL
|
||||
$TableUrl = $Endpoints.ADMIN_CONFIG_URL
|
||||
$TimestampUrl = $Endpoints.CODE_SIGN_TIMESTAMP_URL
|
||||
|
||||
$BootstrapSource = Join-Path $RepoRoot 'scripts/Bootstrap/Install-AutopilotMonitor.ps1'
|
||||
$BootstrapBlob = 'Install-AutopilotMonitor.ps1'
|
||||
@@ -210,7 +212,7 @@ function Set-PublishSetSignature {
|
||||
--timestamp-rfc3161 $TimestampUrl `
|
||||
--file-digest sha256 `
|
||||
--description 'Autopilot Monitor bootstrap' `
|
||||
--description-url 'https://www.autopilotmonitor.com' `
|
||||
--description-url $Endpoints.WWW_BASE_URL `
|
||||
$paths
|
||||
|
||||
# Native tool: PowerShell does not throw on a non-zero exit, and an unnoticed signing
|
||||
@@ -327,8 +329,10 @@ $loaderVersion = Get-BootstrapScriptVersion -Content $loaderContent -Origin $Loa
|
||||
Write-Host "Loader script version: $loaderVersion"
|
||||
|
||||
# ------------------------------------------------------------------ 2. dev render
|
||||
$stableUrlLiteral = '$AgentDownloadUrl = "https://download.autopilotmonitor.com/agent/AutopilotMonitor-Agent.zip"'
|
||||
$devUrlLiteral = '$AgentDownloadUrl = "https://download.autopilotmonitor.com/agent/AutopilotMonitor-Agent-dev.zip"'
|
||||
# The anchors are the customer scripts' own text, built from the alias in the registry. Should
|
||||
# the alias ever move, the scripts have to move with it -- until they do, the render stops here.
|
||||
$stableUrlLiteral = '$AgentDownloadUrl = "' + $AliasUrl + '/AutopilotMonitor-Agent.zip"'
|
||||
$devUrlLiteral = '$AgentDownloadUrl = "' + $AliasUrl + '/AutopilotMonitor-Agent-dev.zip"'
|
||||
$stableManLiteral = '$VersionJsonName = "version.json"'
|
||||
$devManLiteral = '$VersionJsonName = "version-dev.json"'
|
||||
|
||||
@@ -339,8 +343,8 @@ $devContent = $sourceContent.Replace($stableUrlLiteral, $devUrlLiteral).Replace(
|
||||
Write-Host "Rendered $DevBlob (dev agent URL + version-dev.json)"
|
||||
|
||||
# The dev loader must fetch the dev bootstrap, or a dev device would run the stable chain.
|
||||
$stableLoaderLiteral = '$BootstrapUrl = "https://download.autopilotmonitor.com/agent/Install-AutopilotMonitor.ps1"'
|
||||
$devLoaderLiteral = '$BootstrapUrl = "https://download.autopilotmonitor.com/agent/Install-AutopilotMonitor-Dev.ps1"'
|
||||
$stableLoaderLiteral = '$BootstrapUrl = "' + $AliasUrl + '/Install-AutopilotMonitor.ps1"'
|
||||
$devLoaderLiteral = '$BootstrapUrl = "' + $AliasUrl + '/Install-AutopilotMonitor-Dev.ps1"'
|
||||
|
||||
if ($loaderContent.IndexOf($stableLoaderLiteral) -lt 0) {
|
||||
throw "Loader dev-render: anchor literal missing in $LoaderSource"
|
||||
|
||||
Reference in New Issue
Block a user