Complete scratch iteration 004 setup and grouped route-action fixes
This commit is contained in:
@@ -142,6 +142,166 @@ function Get-ServiceHttpProbeUrl([string]$serviceName, [int]$containerPort, [str
|
||||
return "http://${probeHost}:$($Matches.port)$path"
|
||||
}
|
||||
|
||||
function Get-ComposeServiceRecords([string[]]$composeFiles) {
|
||||
$records = @()
|
||||
$seenContainers = @{}
|
||||
|
||||
foreach ($composeFile in $composeFiles) {
|
||||
$composePath = if ([System.IO.Path]::IsPathRooted($composeFile)) {
|
||||
$composeFile
|
||||
} else {
|
||||
Join-Path $ComposeDir $composeFile
|
||||
}
|
||||
|
||||
$expectedServices = Get-ComposeExpectedServices $composePath
|
||||
$services = Get-ComposeServices $composePath
|
||||
if ($expectedServices.Count -gt 0) {
|
||||
$allowed = @{}
|
||||
foreach ($name in $expectedServices) {
|
||||
$allowed[$name.ToLowerInvariant()] = $true
|
||||
}
|
||||
|
||||
$services = $services | Where-Object {
|
||||
$service = "$($_.Service)".ToLowerInvariant()
|
||||
$service -and $allowed.ContainsKey($service)
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($svc in $services) {
|
||||
$name = "$($svc.Name)"
|
||||
if (-not $name -or $seenContainers.ContainsKey($name)) {
|
||||
continue
|
||||
}
|
||||
|
||||
$seenContainers[$name] = $true
|
||||
$records += [pscustomobject]@{
|
||||
ComposeFile = $composePath
|
||||
Service = "$($svc.Service)"
|
||||
Name = $name
|
||||
State = "$($svc.State)".ToLowerInvariant()
|
||||
Health = "$($svc.Health)".ToLowerInvariant()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $records
|
||||
}
|
||||
|
||||
function Wait-ForComposeConvergence(
|
||||
[string[]]$composeFiles,
|
||||
[string]$successMessage,
|
||||
[int]$maxWaitSeconds = 180,
|
||||
[int]$restartAfterSeconds = 45,
|
||||
[int]$pollSeconds = 5,
|
||||
[switch]$RestartStalledServices
|
||||
) {
|
||||
$restartedServices = @{}
|
||||
$elapsed = 0
|
||||
|
||||
while ($elapsed -lt $maxWaitSeconds) {
|
||||
$records = Get-ComposeServiceRecords $composeFiles
|
||||
if ($records.Count -gt 0) {
|
||||
$pending = @()
|
||||
$blocking = @()
|
||||
|
||||
foreach ($record in $records) {
|
||||
if ($record.State -ne 'running') {
|
||||
$blocking += $record
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $record.Health -or $record.Health -eq 'healthy') {
|
||||
continue
|
||||
}
|
||||
|
||||
if ($record.Health -eq 'starting') {
|
||||
$pending += $record
|
||||
continue
|
||||
}
|
||||
|
||||
$blocking += $record
|
||||
}
|
||||
|
||||
if ($blocking.Count -eq 0 -and $pending.Count -eq 0 -and $elapsed -gt $pollSeconds) {
|
||||
Write-Ok $successMessage
|
||||
return $true
|
||||
}
|
||||
|
||||
if ($RestartStalledServices -and $elapsed -ge $restartAfterSeconds -and $blocking.Count -gt 0) {
|
||||
$restartGroups = @{}
|
||||
|
||||
foreach ($record in $blocking) {
|
||||
$restartKey = "$($record.ComposeFile)|$($record.Service)"
|
||||
if ($restartedServices.ContainsKey($restartKey)) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (-not $restartGroups.ContainsKey($record.ComposeFile)) {
|
||||
$restartGroups[$record.ComposeFile] = New-Object System.Collections.Generic.List[string]
|
||||
}
|
||||
|
||||
$restartGroups[$record.ComposeFile].Add($record.Service)
|
||||
$restartedServices[$restartKey] = $true
|
||||
}
|
||||
|
||||
foreach ($group in $restartGroups.GetEnumerator()) {
|
||||
$servicesToRestart = @($group.Value | Sort-Object -Unique)
|
||||
if ($servicesToRestart.Count -eq 0) {
|
||||
continue
|
||||
}
|
||||
|
||||
Write-Warn "Restarting stalled services from $($group.Key): $($servicesToRestart -join ', ')"
|
||||
Push-Location $ComposeDir
|
||||
try {
|
||||
docker compose -f $group.Key restart @servicesToRestart | Out-Null
|
||||
if ($LASTEXITCODE -eq 0) {
|
||||
Write-Ok "Restarted stalled services: $($servicesToRestart -join ', ')"
|
||||
} else {
|
||||
Write-Warn "Failed to restart stalled services: $($servicesToRestart -join ', ')"
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds $pollSeconds
|
||||
$elapsed += $pollSeconds
|
||||
}
|
||||
|
||||
$finalRecords = Get-ComposeServiceRecords $composeFiles
|
||||
$blockingSummary = @(
|
||||
$finalRecords | ForEach-Object {
|
||||
if ($_.State -ne 'running') {
|
||||
"$($_.Name) (state=$($_.State))"
|
||||
}
|
||||
elseif ($_.Health -and $_.Health -ne 'healthy' -and $_.Health -ne 'starting') {
|
||||
"$($_.Name) (health=$($_.Health))"
|
||||
}
|
||||
}
|
||||
) | Where-Object { $_ }
|
||||
|
||||
$pendingSummary = @(
|
||||
$finalRecords | Where-Object {
|
||||
$_.State -eq 'running' -and $_.Health -eq 'starting'
|
||||
} | ForEach-Object {
|
||||
"$($_.Name) (health=starting)"
|
||||
}
|
||||
)
|
||||
|
||||
if ($blockingSummary.Count -gt 0) {
|
||||
Write-Warn "Timed out waiting for compose convergence after ${maxWaitSeconds}s. Blocking services: $($blockingSummary -join ', ')"
|
||||
} elseif ($pendingSummary.Count -gt 0) {
|
||||
Write-Warn "Timed out waiting for compose convergence after ${maxWaitSeconds}s. Still starting: $($pendingSummary -join ', ')"
|
||||
} else {
|
||||
Write-Warn "Timed out waiting for compose convergence after ${maxWaitSeconds}s."
|
||||
}
|
||||
|
||||
return $false
|
||||
}
|
||||
|
||||
# ─── 1. Check prerequisites ────────────────────────────────────────────────
|
||||
|
||||
function Test-Prerequisites {
|
||||
@@ -365,44 +525,7 @@ function Start-Infrastructure {
|
||||
}
|
||||
|
||||
Write-Host ' Waiting for containers to become healthy...' -ForegroundColor Gray
|
||||
$maxWait = 120
|
||||
$elapsed = 0
|
||||
while ($elapsed -lt $maxWait) {
|
||||
$expectedServices = Get-ComposeExpectedServices 'docker-compose.dev.yml'
|
||||
$services = Get-ComposeServices 'docker-compose.dev.yml'
|
||||
if ($expectedServices.Count -gt 0) {
|
||||
$allowed = @{}
|
||||
foreach ($name in $expectedServices) {
|
||||
$allowed[$name.ToLowerInvariant()] = $true
|
||||
}
|
||||
|
||||
$services = $services | Where-Object {
|
||||
$service = "$($_.Service)".ToLowerInvariant()
|
||||
$service -and $allowed.ContainsKey($service)
|
||||
}
|
||||
}
|
||||
if ($services.Count -gt 0) {
|
||||
$allHealthy = $true
|
||||
foreach ($svc in $services) {
|
||||
$state = "$($svc.State)".ToLowerInvariant()
|
||||
$health = "$($svc.Health)".ToLowerInvariant()
|
||||
if ($state -ne 'running') {
|
||||
$allHealthy = $false
|
||||
continue
|
||||
}
|
||||
if ($health -and $health -ne 'healthy') {
|
||||
$allHealthy = $false
|
||||
}
|
||||
}
|
||||
if ($allHealthy -and $elapsed -gt 5) {
|
||||
Write-Ok 'All infrastructure containers healthy'
|
||||
return
|
||||
}
|
||||
}
|
||||
Start-Sleep -Seconds 5
|
||||
$elapsed += 5
|
||||
}
|
||||
Write-Warn "Timed out waiting for healthy status after ${maxWait}s. Check with: docker compose -f docker-compose.dev.yml ps"
|
||||
[void](Wait-ForComposeConvergence -composeFiles @('docker-compose.dev.yml') -successMessage 'All infrastructure containers healthy' -maxWaitSeconds 120)
|
||||
}
|
||||
finally {
|
||||
Pop-Location
|
||||
@@ -465,6 +588,13 @@ function Start-Platform {
|
||||
finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
[void](Wait-ForComposeConvergence `
|
||||
-composeFiles @('docker-compose.stella-ops.yml') `
|
||||
-successMessage 'Platform services converged from zero-state startup' `
|
||||
-RestartStalledServices `
|
||||
-maxWaitSeconds 180 `
|
||||
-restartAfterSeconds 45)
|
||||
}
|
||||
|
||||
function Test-ExpectedHttpStatus([string]$url, [int[]]$allowedStatusCodes, [int]$timeoutSeconds = 5, [int]$attempts = 6, [int]$retryDelaySeconds = 2) {
|
||||
|
||||
Reference in New Issue
Block a user