stela ops usage fixes roles propagation and timoeut, one account to support multi tenants, migrations consolidation, search to support documentation, doctor and open api vector db search
This commit is contained in:
129
devops/compose/scripts/router-mode-redeploy.ps1
Normal file
129
devops/compose/scripts/router-mode-redeploy.ps1
Normal file
@@ -0,0 +1,129 @@
|
||||
param(
|
||||
[ValidateSet("microservice", "reverseproxy")]
|
||||
[string]$Mode = "microservice",
|
||||
[string]$ComposeFile = "docker-compose.stella-ops.yml",
|
||||
[int]$WaitTimeoutSeconds = 1200,
|
||||
[int]$RecoveryAttempts = 2,
|
||||
[int]$RecoveryWaitSeconds = 180
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
$configPath = switch ($Mode) {
|
||||
"microservice" { "./router-gateway-local.json" }
|
||||
"reverseproxy" { "./router-gateway-local.reverseproxy.json" }
|
||||
default { throw "Unsupported mode: $Mode" }
|
||||
}
|
||||
|
||||
Write-Host "Redeploy mode: $Mode"
|
||||
Write-Host "Gateway config: $configPath"
|
||||
Write-Host "Compose file: $ComposeFile"
|
||||
|
||||
$env:ROUTER_GATEWAY_CONFIG = $configPath
|
||||
|
||||
function Invoke-Compose {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string[]]$Args,
|
||||
[switch]$IgnoreExitCode
|
||||
)
|
||||
|
||||
& docker compose -f $ComposeFile @Args
|
||||
$exitCode = $LASTEXITCODE
|
||||
if (-not $IgnoreExitCode -and $exitCode -ne 0) {
|
||||
throw "docker compose $($Args -join ' ') failed with exit code $exitCode."
|
||||
}
|
||||
|
||||
return $exitCode
|
||||
}
|
||||
|
||||
function Get-UnhealthyContainers {
|
||||
$containers = & docker ps --filter "health=unhealthy" --format "{{.Names}}"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Failed to query unhealthy containers."
|
||||
}
|
||||
|
||||
$filtered = @($containers | Where-Object { -not [string]::IsNullOrWhiteSpace($_) -and $_ -like "stellaops-*" })
|
||||
return [string[]]$filtered
|
||||
}
|
||||
|
||||
function Get-ComposeServiceName {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ContainerName
|
||||
)
|
||||
|
||||
$service = & docker inspect --format "{{ index .Config.Labels \"com.docker.compose.service\" }}" $ContainerName 2>$null
|
||||
if ($LASTEXITCODE -ne 0 -or [string]::IsNullOrWhiteSpace($service)) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return $service.Trim()
|
||||
}
|
||||
|
||||
function Wait-ForContainerHealth {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$ContainerName,
|
||||
[Parameter(Mandatory = $true)]
|
||||
[int]$TimeoutSeconds
|
||||
)
|
||||
|
||||
$deadline = (Get-Date).AddSeconds($TimeoutSeconds)
|
||||
while ((Get-Date) -lt $deadline) {
|
||||
$status = (& docker inspect --format "{{if .State.Health}}{{.State.Health.Status}}{{else}}none{{end}}" $ContainerName 2>$null).Trim()
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($status -eq "healthy" -or $status -eq "none") {
|
||||
return $true
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 5
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
Invoke-Compose -Args @("down", "-v", "--remove-orphans") | Out-Null
|
||||
|
||||
$upExitCode = Invoke-Compose -Args @("up", "-d", "--wait", "--wait-timeout", $WaitTimeoutSeconds.ToString()) -IgnoreExitCode
|
||||
if ($upExitCode -ne 0) {
|
||||
Write-Warning "docker compose up returned exit code $upExitCode. Running unhealthy-service recovery."
|
||||
}
|
||||
|
||||
for ($attempt = 1; $attempt -le $RecoveryAttempts; $attempt++) {
|
||||
$unhealthyContainers = @(Get-UnhealthyContainers)
|
||||
if ($unhealthyContainers.Count -eq 0) {
|
||||
break
|
||||
}
|
||||
|
||||
Write-Warning "Recovery attempt ${attempt}: unhealthy containers detected: $($unhealthyContainers -join ', ')"
|
||||
$services = New-Object System.Collections.Generic.HashSet[string]([System.StringComparer]::OrdinalIgnoreCase)
|
||||
|
||||
foreach ($containerName in $unhealthyContainers) {
|
||||
$serviceName = Get-ComposeServiceName -ContainerName $containerName
|
||||
if (-not [string]::IsNullOrWhiteSpace($serviceName)) {
|
||||
[void]$services.Add($serviceName)
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($serviceName in $services) {
|
||||
Write-Host "Recreating service: $serviceName"
|
||||
Invoke-Compose -Args @("up", "-d", "--force-recreate", "--no-deps", $serviceName) | Out-Null
|
||||
}
|
||||
|
||||
foreach ($containerName in $unhealthyContainers) {
|
||||
[void](Wait-ForContainerHealth -ContainerName $containerName -TimeoutSeconds $RecoveryWaitSeconds)
|
||||
}
|
||||
}
|
||||
|
||||
$remainingUnhealthy = @(Get-UnhealthyContainers)
|
||||
if ($remainingUnhealthy.Count -gt 0) {
|
||||
throw "Redeploy completed with unresolved unhealthy containers: $($remainingUnhealthy -join ', ')"
|
||||
}
|
||||
|
||||
Write-Host "Redeploy complete for mode '$Mode'."
|
||||
102
devops/compose/scripts/router-routeprefix-smoke.ps1
Normal file
102
devops/compose/scripts/router-routeprefix-smoke.ps1
Normal file
@@ -0,0 +1,102 @@
|
||||
param(
|
||||
[string]$RouterConfigPath = "devops/compose/router-gateway-local.json",
|
||||
[string]$OpenApiPath = "devops/compose/openapi_current.json",
|
||||
[string]$GatewayBaseUrl = "https://127.1.0.1",
|
||||
[ValidateSet("Microservice", "ReverseProxy", "StaticFiles")]
|
||||
[string]$RouteType = "Microservice",
|
||||
[string]$OutputCsv = "devops/compose/openapi_routeprefix_smoke.csv"
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProgressPreference = "SilentlyContinue"
|
||||
|
||||
function Get-JsonFromFile {
|
||||
param([Parameter(Mandatory = $true)][string]$Path)
|
||||
if (-not (Test-Path -LiteralPath $Path)) {
|
||||
throw "File not found: $Path"
|
||||
}
|
||||
|
||||
return Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function Get-OpenApiPathMap {
|
||||
param([Parameter(Mandatory = $true)]$OpenApi)
|
||||
$map = @{}
|
||||
foreach ($prop in $OpenApi.paths.PSObject.Properties) {
|
||||
$map[$prop.Name] = $prop.Value
|
||||
}
|
||||
|
||||
return $map
|
||||
}
|
||||
|
||||
function Get-HttpStatusCode {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Url
|
||||
)
|
||||
|
||||
$statusText = (& curl.exe -k -s -o NUL -w "%{http_code}" $Url).Trim()
|
||||
if ($statusText -match "^\d{3}$") {
|
||||
return [int]$statusText
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
$routerConfig = Get-JsonFromFile -Path $RouterConfigPath
|
||||
$openApi = Get-JsonFromFile -Path $OpenApiPath
|
||||
$openApiPathMap = Get-OpenApiPathMap -OpenApi $openApi
|
||||
$openApiPaths = @($openApiPathMap.Keys)
|
||||
|
||||
$routes = @($routerConfig.Gateway.Routes | Where-Object { $_.Type -eq $RouteType })
|
||||
$rows = New-Object System.Collections.Generic.List[object]
|
||||
|
||||
foreach ($route in $routes) {
|
||||
$prefix = [string]$route.Path
|
||||
$matches = @()
|
||||
|
||||
foreach ($candidate in $openApiPaths) {
|
||||
if ($candidate.StartsWith($prefix, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
$operation = $openApiPathMap[$candidate]
|
||||
if ($null -ne $operation -and $operation.PSObject.Properties.Match("get").Count -gt 0) {
|
||||
$matches += $candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$selectedPath = $null
|
||||
if ($matches.Count -gt 0) {
|
||||
$selectedPath = ($matches | Sort-Object `
|
||||
@{ Expression = { $_ -match '\{[^}]+\}' } }, `
|
||||
@{ Expression = { $_ -match '(^|/)(startupz|readyz|livez)$' } }, `
|
||||
@{ Expression = { $_.Length } }, `
|
||||
@{ Expression = { $_ } })[0]
|
||||
}
|
||||
|
||||
$status = $null
|
||||
if ($null -ne $selectedPath) {
|
||||
$url = "$GatewayBaseUrl$selectedPath"
|
||||
$status = Get-HttpStatusCode -Url $url
|
||||
}
|
||||
|
||||
$rows.Add([pscustomobject]@{
|
||||
RouteType = $RouteType
|
||||
RoutePath = $prefix
|
||||
RouteTarget = [string]$route.TranslatesTo
|
||||
SelectedOpenApiPath = $selectedPath
|
||||
StatusCode = $status
|
||||
})
|
||||
}
|
||||
|
||||
$rows | Export-Csv -LiteralPath $OutputCsv -NoTypeInformation -Encoding UTF8
|
||||
|
||||
$statusSummary = $rows |
|
||||
Where-Object { $null -ne $_.StatusCode } |
|
||||
Group-Object -Property StatusCode |
|
||||
Sort-Object { [int]$_.Name } |
|
||||
ForEach-Object { "$($_.Name)=$($_.Count)" }
|
||||
|
||||
Write-Host "routes_total=$($routes.Count)"
|
||||
Write-Host "routes_with_selected_get=$(@($rows | Where-Object { $_.SelectedOpenApiPath }).Count)"
|
||||
Write-Host "status_summary=$($statusSummary -join ',')"
|
||||
Write-Host "output_csv=$OutputCsv"
|
||||
Reference in New Issue
Block a user