texts fixes, search bar fixes, global menu fixes.
This commit is contained in:
@@ -138,6 +138,9 @@ docker compose -f docker-compose.stella-ops.yml up -d
|
||||
# Reverse-proxy fallback mode (no route-table edits required)
|
||||
ROUTER_GATEWAY_CONFIG=./router-gateway-local.reverseproxy.json \
|
||||
docker compose -f docker-compose.stella-ops.yml up -d
|
||||
|
||||
# Optional: mode switch helper with health recovery + header-search smoke checks
|
||||
pwsh ./scripts/router-mode-redeploy.ps1 -Mode microservice
|
||||
```
|
||||
|
||||
Validation endpoints:
|
||||
@@ -148,6 +151,9 @@ curl -k https://127.1.0.1/openapi.json
|
||||
|
||||
# Timeline API schema (through router-gateway)
|
||||
curl -k https://127.1.0.1/openapi.json | jq '.paths["/api/v1/timeline"]'
|
||||
|
||||
# Header search routing smoke (fails on missing /api/v1/search* or /api/v1/advisory-ai/search* routes)
|
||||
pwsh ./scripts/header-search-smoke.ps1
|
||||
```
|
||||
|
||||
### With Observability
|
||||
|
||||
@@ -347,10 +347,10 @@ services:
|
||||
Logging__LogLevel__Microsoft.AspNetCore.Authorization: "Debug"
|
||||
Platform__Storage__Driver: "postgres"
|
||||
Platform__Storage__PostgresConnectionString: *postgres-connection
|
||||
Platform__EnvironmentSettings__AuthorizeEndpoint: "https://127.1.0.1/connect/authorize"
|
||||
Platform__EnvironmentSettings__TokenEndpoint: "https://127.1.0.1/connect/token"
|
||||
Platform__EnvironmentSettings__RedirectUri: "https://127.1.0.1/auth/callback"
|
||||
Platform__EnvironmentSettings__PostLogoutRedirectUri: "https://127.1.0.1/"
|
||||
Platform__EnvironmentSettings__AuthorizeEndpoint: "https://stella-ops.local/connect/authorize"
|
||||
Platform__EnvironmentSettings__TokenEndpoint: "https://stella-ops.local/connect/token"
|
||||
Platform__EnvironmentSettings__RedirectUri: "https://stella-ops.local/auth/callback"
|
||||
Platform__EnvironmentSettings__PostLogoutRedirectUri: "https://stella-ops.local/"
|
||||
Platform__EnvironmentSettings__Scope: "openid profile email offline_access ui.read ui.admin ui.preferences.read ui.preferences.write authority:tenants.read authority:users.read authority:roles.read authority:clients.read authority:tokens.read authority:branding.read authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve policy:run policy:activate policy:audit policy:edit policy:operate policy:publish airgap:seal airgap:status:read orch:read analytics.read advisory:read advisory-ai:view advisory-ai:operate vex:read vexhub:read exceptions:read exceptions:approve aoc:verify findings:read release:read scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin ops.health integration:read integration:write integration:operate timeline:read timeline:write"
|
||||
STELLAOPS_ROUTER_URL: "http://router.stella-ops.local"
|
||||
STELLAOPS_PLATFORM_URL: "http://platform.stella-ops.local"
|
||||
@@ -1190,8 +1190,8 @@ services:
|
||||
AirGap__Egress__Enabled: "false"
|
||||
volumes:
|
||||
- *cert-volume
|
||||
- taskrunner-artifacts-data:/app/artifacts
|
||||
tmpfs:
|
||||
- /app/artifacts:mode=1777
|
||||
- /app/queue:mode=1777
|
||||
- /app/state:mode=1777
|
||||
- /app/approvals:mode=1777
|
||||
@@ -2208,6 +2208,9 @@ services:
|
||||
ADVISORYAI__AdvisoryAI__Inference__Mode: "${ADVISORY_AI_INFERENCE_MODE:-Local}"
|
||||
ADVISORYAI__AdvisoryAI__Inference__Remote__BaseAddress: "${ADVISORY_AI_REMOTE_BASEADDRESS:-}"
|
||||
ADVISORYAI__AdvisoryAI__Inference__Remote__ApiKey: "${ADVISORY_AI_REMOTE_APIKEY:-}"
|
||||
ADVISORYAI__KnowledgeSearch__ConnectionString: *postgres-connection
|
||||
ADVISORYAI__KnowledgeSearch__FindingsAdapterEnabled: "true"
|
||||
ADVISORYAI__KnowledgeSearch__FindingsAdapterBaseUrl: "http://scanner.stella-ops.local"
|
||||
Router__Enabled: "${ADVISORYAI_ROUTER_ENABLED:-true}"
|
||||
Router__Messaging__ConsumerGroup: "advisoryai"
|
||||
ports:
|
||||
@@ -2243,6 +2246,9 @@ services:
|
||||
ADVISORYAI__AdvisoryAI__Inference__Mode: "${ADVISORY_AI_INFERENCE_MODE:-Local}"
|
||||
ADVISORYAI__AdvisoryAI__Inference__Remote__BaseAddress: "${ADVISORY_AI_REMOTE_BASEADDRESS:-}"
|
||||
ADVISORYAI__AdvisoryAI__Inference__Remote__ApiKey: "${ADVISORY_AI_REMOTE_APIKEY:-}"
|
||||
ADVISORYAI__KnowledgeSearch__ConnectionString: *postgres-connection
|
||||
ADVISORYAI__KnowledgeSearch__FindingsAdapterEnabled: "true"
|
||||
ADVISORYAI__KnowledgeSearch__FindingsAdapterBaseUrl: "http://scanner.stella-ops.local"
|
||||
volumes:
|
||||
- *cert-volume
|
||||
networks:
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
"authority": {
|
||||
"issuer": "https://authority.stella-ops.local/",
|
||||
"clientId": "stella-ops-ui",
|
||||
"authorizeEndpoint": "https://127.1.0.1/connect/authorize",
|
||||
"tokenEndpoint": "https://127.1.0.1/connect/token",
|
||||
"redirectUri": "https://127.1.0.1/auth/callback",
|
||||
"postLogoutRedirectUri": "https://127.1.0.1/",
|
||||
"authorizeEndpoint": "https://stella-ops.local/connect/authorize",
|
||||
"tokenEndpoint": "https://stella-ops.local/connect/token",
|
||||
"redirectUri": "https://stella-ops.local/auth/callback",
|
||||
"postLogoutRedirectUri": "https://stella-ops.local/",
|
||||
"scope": "openid profile email offline_access ui.read ui.admin ui.preferences.read ui.preferences.write authority:tenants.read authority:users.read authority:roles.read authority:clients.read authority:tokens.read authority:branding.read authority.audit.read graph:read sbom:read scanner:read policy:read policy:simulate policy:author policy:review policy:approve policy:run policy:activate policy:audit policy:edit policy:operate policy:publish airgap:seal airgap:status:read orch:read analytics.read advisory:read advisory-ai:view advisory-ai:operate vex:read vexhub:read exceptions:read exceptions:approve aoc:verify findings:read release:read scheduler:read scheduler:operate notify.viewer notify.operator notify.admin notify.escalate evidence:read export.viewer export.operator export.admin vuln:view vuln:investigate vuln:operate vuln:audit platform.context.read platform.context.write doctor:run doctor:admin ops.health integration:read integration:write integration:operate timeline:read timeline:write",
|
||||
"audience": "stella-ops-api",
|
||||
"dpopAlgorithms": [
|
||||
|
||||
18
devops/compose/postgres-init/13-platform-translations.sql
Normal file
18
devops/compose/postgres-init/13-platform-translations.sql
Normal file
@@ -0,0 +1,18 @@
|
||||
-- Ensure Platform localization DB overrides table exists for /platform/i18n.
|
||||
-- This is idempotent and safe to run on new compose databases.
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS platform;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS platform.translations (
|
||||
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
locale VARCHAR(10) NOT NULL,
|
||||
key VARCHAR(512) NOT NULL,
|
||||
value TEXT NOT NULL,
|
||||
tenant_id VARCHAR(128) NOT NULL DEFAULT '_system',
|
||||
updated_by VARCHAR(256) NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT ux_translations_tenant_locale_key UNIQUE (tenant_id, locale, key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_translations_tenant_locale
|
||||
ON platform.translations (tenant_id, locale);
|
||||
@@ -220,15 +220,27 @@
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "Microservice",
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/api/v1/advisory-ai/adapters",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "Microservice",
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/api/v1/search",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/search",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/api/v1/advisory-ai",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory-ai",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "Microservice",
|
||||
"Path": "/v1/evidence-packs",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/evidence-packs",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -184,10 +184,16 @@
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai/adapters",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/api/v1/search",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/search",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
"Type": "ReverseProxy",
|
||||
"Path": "/api/v1/advisory-ai",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/api/v1/advisory-ai",
|
||||
"TranslatesTo": "http://advisoryai.stella-ops.local/v1/advisory-ai",
|
||||
"PreserveAuthHeaders": true
|
||||
},
|
||||
{
|
||||
|
||||
284
devops/compose/scripts/header-search-smoke.ps1
Normal file
284
devops/compose/scripts/header-search-smoke.ps1
Normal file
@@ -0,0 +1,284 @@
|
||||
param(
|
||||
[string]$GatewayBaseUrl = "https://stella-ops.local",
|
||||
[string]$AdvisoryAiBaseUrl = "http://advisoryai.stella-ops.local",
|
||||
[string]$Tenant = "stellaops",
|
||||
[int]$TimeoutSeconds = 20,
|
||||
[switch]$SkipUiResponsivenessProbe
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
function Invoke-Probe {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Name,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Method,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$Path,
|
||||
[string]$Body
|
||||
)
|
||||
|
||||
$headersFile = [System.IO.Path]::GetTempFileName()
|
||||
$bodyFile = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$curlArgs = @(
|
||||
"-k"
|
||||
"-sS"
|
||||
"--noproxy"
|
||||
"*"
|
||||
"-D"
|
||||
$headersFile
|
||||
"-o"
|
||||
$bodyFile
|
||||
"-X"
|
||||
$Method
|
||||
"--max-time"
|
||||
$TimeoutSeconds.ToString([System.Globalization.CultureInfo]::InvariantCulture)
|
||||
"-H"
|
||||
"Accept: application/json"
|
||||
"-H"
|
||||
"X-Tenant: $Tenant"
|
||||
"-H"
|
||||
"X-StellaOps-Tenant: $Tenant"
|
||||
"-H"
|
||||
"X-Stella-Tenant: $Tenant"
|
||||
)
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($Body)) {
|
||||
$curlArgs += @(
|
||||
"-H"
|
||||
"Content-Type: application/json"
|
||||
"--data-raw"
|
||||
$Body
|
||||
)
|
||||
}
|
||||
|
||||
$url = "$GatewayBaseUrl$Path"
|
||||
$curlArgs += @("-w", "%{http_code}", $url)
|
||||
|
||||
$statusText = (& curl.exe @curlArgs).Trim()
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "curl failed for $Name ($Method $Path) with exit code $LASTEXITCODE."
|
||||
}
|
||||
|
||||
if ($statusText -notmatch "^\d{3}$") {
|
||||
throw "Unable to parse HTTP status for $Name ($Method $Path). Raw status text: '$statusText'."
|
||||
}
|
||||
|
||||
$statusCode = [int]$statusText
|
||||
$contentTypeLine = Get-Content -LiteralPath $headersFile |
|
||||
Where-Object { $_ -match "^[Cc]ontent-[Tt]ype:" } |
|
||||
Select-Object -Last 1
|
||||
$contentTypeLine = [string]$contentTypeLine
|
||||
$contentType = if ([string]::IsNullOrWhiteSpace($contentTypeLine)) {
|
||||
""
|
||||
} else {
|
||||
(($contentTypeLine -replace "^[Cc]ontent-[Tt]ype:\s*", "") -replace "\r", "").Trim()
|
||||
}
|
||||
|
||||
$responseBody = [string](Get-Content -LiteralPath $bodyFile -Raw -ErrorAction SilentlyContinue)
|
||||
if ($null -eq $responseBody) {
|
||||
$responseBody = ""
|
||||
}
|
||||
if ($responseBody.Length -gt 800) {
|
||||
$responseBody = $responseBody.Substring(0, 800)
|
||||
}
|
||||
|
||||
return [pscustomobject]@{
|
||||
Name = $Name
|
||||
Method = $Method
|
||||
Path = $Path
|
||||
Url = $url
|
||||
StatusCode = $statusCode
|
||||
ContentType = $contentType
|
||||
BodyPreview = $responseBody
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $headersFile -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $bodyFile -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-UiResponsivenessProbe {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$BaseUrl
|
||||
)
|
||||
|
||||
$repoRoot = Resolve-Path (Join-Path $PSScriptRoot "..\\..\\..")
|
||||
$webNodeModules = Join-Path $repoRoot "src\\Web\\StellaOps.Web\\node_modules"
|
||||
if (-not (Test-Path -LiteralPath $webNodeModules)) {
|
||||
throw "UI responsiveness probe requires $webNodeModules. Install web dependencies first."
|
||||
}
|
||||
|
||||
$probeScriptPath = [System.IO.Path]::ChangeExtension([System.IO.Path]::GetTempFileName(), ".cjs")
|
||||
$probeScript = @'
|
||||
const { chromium } = require("playwright");
|
||||
|
||||
const targetUrl = process.argv[2];
|
||||
if (!targetUrl) {
|
||||
throw new Error("Missing target URL argument.");
|
||||
}
|
||||
|
||||
const evaluateWithTimeout = async (page, timeoutMs) => {
|
||||
return Promise.race([
|
||||
page.evaluate(() => ({
|
||||
readyState: document.readyState,
|
||||
title: document.title,
|
||||
location: window.location.href,
|
||||
appRootPresent: !!document.querySelector("app-root"),
|
||||
globalSearchInputCount: document.querySelectorAll('app-global-search input[type="text"]').length,
|
||||
})),
|
||||
new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error(`ui-evaluate-timeout-${timeoutMs}ms`)), timeoutMs);
|
||||
}),
|
||||
]);
|
||||
};
|
||||
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true, args: ["--no-sandbox", "--disable-setuid-sandbox"] });
|
||||
try {
|
||||
const context = await browser.newContext({ ignoreHTTPSErrors: true });
|
||||
const page = await context.newPage();
|
||||
|
||||
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 30000 });
|
||||
await page.waitForTimeout(3000);
|
||||
const probe = await evaluateWithTimeout(page, 10000);
|
||||
console.log(JSON.stringify(probe));
|
||||
} finally {
|
||||
await browser.close();
|
||||
}
|
||||
})().catch((error) => {
|
||||
console.error(error?.stack ?? String(error));
|
||||
process.exit(1);
|
||||
});
|
||||
'@
|
||||
|
||||
$originalNodePath = $env:NODE_PATH
|
||||
try {
|
||||
Set-Content -LiteralPath $probeScriptPath -Value $probeScript -Encoding UTF8
|
||||
$env:NODE_PATH = $webNodeModules
|
||||
$probeOutput = (& node $probeScriptPath $BaseUrl 2>&1)
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "UI responsiveness probe failed for $BaseUrl. Output: $probeOutput"
|
||||
}
|
||||
|
||||
$probeJsonLine = [string]($probeOutput | Select-Object -Last 1)
|
||||
$probe = $probeJsonLine | ConvertFrom-Json
|
||||
if ([string]::IsNullOrWhiteSpace($probe.title)) {
|
||||
throw "UI responsiveness probe returned an empty document title for $BaseUrl."
|
||||
}
|
||||
|
||||
Write-Host "[OK] UI responsiveness probe: title='$($probe.title)' readyState=$($probe.readyState) url=$($probe.location)"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $probeScriptPath -ErrorAction SilentlyContinue
|
||||
if ($null -eq $originalNodePath) {
|
||||
Remove-Item Env:NODE_PATH -ErrorAction SilentlyContinue
|
||||
} else {
|
||||
$env:NODE_PATH = $originalNodePath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$rebuildProbeHeaders = @(
|
||||
"-H", "Accept: application/json",
|
||||
"-H", "Content-Type: application/json",
|
||||
"-H", "X-StellaOps-Tenant: $Tenant",
|
||||
"-H", "X-StellaOps-Scopes: advisory-ai:admin advisory-ai:operate advisory-ai:view",
|
||||
"-H", "X-StellaOps-Actor: header-search-smoke"
|
||||
)
|
||||
|
||||
$rebuildHeadersFile = [System.IO.Path]::GetTempFileName()
|
||||
$rebuildBodyFile = [System.IO.Path]::GetTempFileName()
|
||||
try {
|
||||
$rebuildStatus = (& curl.exe -sS --noproxy "*" -D $rebuildHeadersFile -o $rebuildBodyFile -X POST --max-time $TimeoutSeconds @rebuildProbeHeaders -w "%{http_code}" "$AdvisoryAiBaseUrl/v1/search/index/rebuild").Trim()
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "curl failed for unified search rebuild probe."
|
||||
}
|
||||
|
||||
if ($rebuildStatus -notmatch "^\d{3}$") {
|
||||
throw "Unable to parse HTTP status for unified search rebuild probe. Raw status text: '$rebuildStatus'."
|
||||
}
|
||||
|
||||
$rebuildStatusCode = [int]$rebuildStatus
|
||||
$rebuildBodyRaw = [string](Get-Content -LiteralPath $rebuildBodyFile -Raw -ErrorAction SilentlyContinue)
|
||||
if ($null -eq $rebuildBodyRaw) {
|
||||
$rebuildBodyRaw = ""
|
||||
}
|
||||
|
||||
if ($rebuildStatusCode -ne 200) {
|
||||
throw "Unified search rebuild probe failed: status=$rebuildStatusCode body='$rebuildBodyRaw'."
|
||||
}
|
||||
|
||||
$rebuildPayload = $rebuildBodyRaw | ConvertFrom-Json
|
||||
$chunkCount = [int]$rebuildPayload.chunkCount
|
||||
if ($chunkCount -le 0) {
|
||||
throw "Unified search rebuild returned no chunks (chunkCount=$chunkCount). Check KnowledgeSearch connection and adapter ingestion wiring."
|
||||
}
|
||||
|
||||
Write-Host "[OK] Unified search ingestion rebuild: status=$rebuildStatusCode chunkCount=$chunkCount"
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $rebuildHeadersFile -ErrorAction SilentlyContinue
|
||||
Remove-Item -LiteralPath $rebuildBodyFile -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
$probes = @(
|
||||
@{
|
||||
Name = "Platform envsettings route"
|
||||
Method = "GET"
|
||||
Path = "/platform/envsettings.json"
|
||||
Body = $null
|
||||
AllowedStatusCodes = @(200)
|
||||
},
|
||||
@{
|
||||
Name = "Platform i18n route"
|
||||
Method = "GET"
|
||||
Path = "/platform/i18n/en-US.json"
|
||||
Body = $null
|
||||
AllowedStatusCodes = @(200)
|
||||
},
|
||||
@{
|
||||
Name = "Unified search query route"
|
||||
Method = "POST"
|
||||
Path = "/api/v1/search/query"
|
||||
Body = '{"q":"header route smoke","limit":5}'
|
||||
AllowedStatusCodes = @(200, 400, 401, 403, 422, 429)
|
||||
},
|
||||
@{
|
||||
Name = "Advisory search history route"
|
||||
Method = "GET"
|
||||
Path = "/api/v1/advisory-ai/search/history"
|
||||
Body = $null
|
||||
AllowedStatusCodes = @(200, 204, 400, 401, 403)
|
||||
}
|
||||
)
|
||||
|
||||
foreach ($probe in $probes) {
|
||||
$result = Invoke-Probe -Name $probe.Name -Method $probe.Method -Path $probe.Path -Body $probe.Body
|
||||
|
||||
if ($result.StatusCode -eq 200 -and $result.ContentType -like "text/html*") {
|
||||
throw "$($probe.Name) returned HTML (likely SPA fallback) for $($probe.Path)."
|
||||
}
|
||||
|
||||
if (-not $probe.AllowedStatusCodes.Contains($result.StatusCode)) {
|
||||
throw "$($probe.Name) failed for $($probe.Path): status=$($result.StatusCode), contentType='$($result.ContentType)', body='$($result.BodyPreview)'."
|
||||
}
|
||||
|
||||
if ($probe.Path -eq "/platform/i18n/en-US.json" -and $result.ContentType -notlike "application/json*") {
|
||||
throw "$($probe.Name) must return JSON. Received contentType='$($result.ContentType)'."
|
||||
}
|
||||
|
||||
Write-Host "[OK] $($probe.Name): status=$($result.StatusCode) path=$($probe.Path)"
|
||||
}
|
||||
|
||||
if (-not $SkipUiResponsivenessProbe) {
|
||||
Invoke-UiResponsivenessProbe -BaseUrl $GatewayBaseUrl
|
||||
}
|
||||
|
||||
Write-Host "Header search routing smoke checks passed."
|
||||
@@ -4,24 +4,41 @@ param(
|
||||
[string]$ComposeFile = "docker-compose.stella-ops.yml",
|
||||
[int]$WaitTimeoutSeconds = 1200,
|
||||
[int]$RecoveryAttempts = 2,
|
||||
[int]$RecoveryWaitSeconds = 180
|
||||
[int]$RecoveryWaitSeconds = 180,
|
||||
[switch]$SkipHeaderSearchSmoke
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$configPath = switch ($Mode) {
|
||||
"microservice" { "./router-gateway-local.json" }
|
||||
"reverseproxy" { "./router-gateway-local.reverseproxy.json" }
|
||||
$composeDirectory = Split-Path -Parent $PSScriptRoot
|
||||
$resolvedComposeFile = if ([System.IO.Path]::IsPathRooted($ComposeFile)) {
|
||||
$ComposeFile
|
||||
} else {
|
||||
Join-Path -Path $composeDirectory -ChildPath $ComposeFile
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $resolvedComposeFile)) {
|
||||
throw "Compose file not found: $resolvedComposeFile"
|
||||
}
|
||||
|
||||
$configFileName = switch ($Mode) {
|
||||
"microservice" { "router-gateway-local.json" }
|
||||
"reverseproxy" { "router-gateway-local.reverseproxy.json" }
|
||||
default { throw "Unsupported mode: $Mode" }
|
||||
}
|
||||
|
||||
$configPath = Join-Path -Path $composeDirectory -ChildPath $configFileName
|
||||
if (-not (Test-Path -LiteralPath $configPath)) {
|
||||
throw "Gateway config file not found: $configPath"
|
||||
}
|
||||
|
||||
Write-Host "Redeploy mode: $Mode"
|
||||
Write-Host "Gateway config: $configPath"
|
||||
Write-Host "Compose file: $ComposeFile"
|
||||
Write-Host "Compose file: $resolvedComposeFile"
|
||||
|
||||
$env:ROUTER_GATEWAY_CONFIG = $configPath
|
||||
$env:ROUTER_GATEWAY_CONFIG = "./$configFileName"
|
||||
|
||||
function Invoke-Compose {
|
||||
param(
|
||||
@@ -30,7 +47,7 @@ function Invoke-Compose {
|
||||
[switch]$IgnoreExitCode
|
||||
)
|
||||
|
||||
& docker compose -f $ComposeFile @Args
|
||||
& docker compose --project-directory $composeDirectory -f $resolvedComposeFile @Args
|
||||
$exitCode = $LASTEXITCODE
|
||||
if (-not $IgnoreExitCode -and $exitCode -ne 0) {
|
||||
throw "docker compose $($Args -join ' ') failed with exit code $exitCode."
|
||||
@@ -55,12 +72,33 @@ function Get-ComposeServiceName {
|
||||
[string]$ContainerName
|
||||
)
|
||||
|
||||
$service = & docker inspect --format "{{ index .Config.Labels \"com.docker.compose.service\" }}" $ContainerName 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($service)) {
|
||||
$inspectJson = & docker inspect $ContainerName 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or $null -eq $inspectJson) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $service.Trim()
|
||||
try {
|
||||
$inspect = $inspectJson | ConvertFrom-Json
|
||||
if ($null -eq $inspect) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$first = if ($inspect -is [System.Array]) { $inspect[0] } else { $inspect }
|
||||
$labels = $first.Config.Labels
|
||||
if ($null -eq $labels) {
|
||||
return $null
|
||||
}
|
||||
|
||||
$service = $labels."com.docker.compose.service"
|
||||
if ([string]::IsNullOrWhiteSpace($service)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $service.Trim()
|
||||
}
|
||||
catch {
|
||||
return $null
|
||||
}
|
||||
}
|
||||
|
||||
function Wait-ForContainerHealth {
|
||||
@@ -126,4 +164,14 @@ if ($remainingUnhealthy.Count -gt 0) {
|
||||
throw "Redeploy completed with unresolved unhealthy containers: $($remainingUnhealthy -join ', ')"
|
||||
}
|
||||
|
||||
if (-not $SkipHeaderSearchSmoke) {
|
||||
$headerSearchSmokeScript = Join-Path -Path $PSScriptRoot -ChildPath "header-search-smoke.ps1"
|
||||
if (-not (Test-Path -LiteralPath $headerSearchSmokeScript)) {
|
||||
throw "Header search smoke script not found: $headerSearchSmokeScript"
|
||||
}
|
||||
|
||||
Write-Host "Running header search route smoke checks..."
|
||||
& $headerSearchSmokeScript
|
||||
}
|
||||
|
||||
Write-Host "Redeploy complete for mode '$Mode'."
|
||||
|
||||
Reference in New Issue
Block a user