Modules/businessdev.ALbuild.Containers/Public/Wait-BcContainerReady.ps1

function Wait-BcContainerReady {
    <#
    .SYNOPSIS
        Waits for a Business Central container to report that it is ready for connections.
 
    .DESCRIPTION
        Polls the container log for the ready marker emitted by the generic image, failing fast if
        the container exits before becoming ready or if the timeout elapses. While waiting it streams
        the container's new start-script log lines to the console (and a periodic heartbeat when the
        log is quiet) so a long initialisation never looks hung.
 
    .PARAMETER Name
        Container name.
 
    .PARAMETER TimeoutSeconds
        Maximum time to wait. Default 1200 (20 minutes).
 
    .PARAMETER ReadyText
        The log text that signals readiness. Default 'Ready for connections'.
 
    .PARAMETER PollIntervalSeconds
        Seconds between polls. Default 5.
 
    .PARAMETER HeartbeatSeconds
        Emit an "elapsed" heartbeat after this many seconds without new log output. Default 30.
 
    .PARAMETER NoProgress
        Suppress streaming the container's startup log lines (the readiness check and heartbeat
        still apply).
 
    .PARAMETER DockerExecutable
        The Docker executable to use (default 'docker').
 
    .OUTPUTS
        System.Boolean ($true when ready; otherwise throws).
    #>

    [CmdletBinding()]
    [OutputType([bool])]
    param(
        [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $Name,
        [ValidateRange(1, [int]::MaxValue)] [int] $TimeoutSeconds = 1200,
        [string] $ReadyText = 'Ready for connections',
        [ValidateRange(1, 3600)] [int] $PollIntervalSeconds = 5,
        [ValidateRange(1, [int]::MaxValue)] [int] $HeartbeatSeconds = 30,
        [switch] $NoProgress,
        [string] $DockerExecutable = 'docker'
    )

    $start = Get-Date
    $deadline = $start.AddSeconds($TimeoutSeconds)
    $escaped = [regex]::Escape($ReadyText)
    $printedLines = 0
    $lastOutput = $start

    while ((Get-Date) -lt $deadline) {
        $logs = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -Arguments @('logs', $Name)
        $stdOut = "$($logs.StdOut)"

        # Stream the start-script log lines that appeared since the last poll, so progress is visible.
        if ($logs.Success -and -not $NoProgress) {
            $lines = @($stdOut -split "`r?`n")
            for ($i = $printedLines; $i -lt $lines.Count; $i++) {
                if (-not [string]::IsNullOrWhiteSpace($lines[$i])) {
                    Write-Host " [$Name] $($lines[$i].TrimEnd())"
                    $lastOutput = Get-Date
                }
            }
            $printedLines = $lines.Count
        }

        if ($logs.Success -and (("$stdOut`n$($logs.StdErr)") -match $escaped)) {
            Write-ALbuildLog -Level Success "Container '$Name' is ready ($([int]((Get-Date) - $start).TotalSeconds)s)."
            return $true
        }

        $container = Get-BcContainer -Name $Name -DockerExecutable $DockerExecutable
        if ($container -and -not $container.Running) {
            throw "Container '$Name' exited before becoming ready (status: $($container.Status)).$(Get-BcContainerLogTail -Name $Name -DockerExecutable $DockerExecutable)"
        }

        # Heartbeat when the log has been quiet, so the wait never looks frozen.
        if (((Get-Date) - $lastOutput).TotalSeconds -ge $HeartbeatSeconds) {
            Write-ALbuildLog -Level Information "Still waiting for container '$Name' to become ready... ($([int]((Get-Date) - $start).TotalSeconds)s elapsed)"
            $lastOutput = Get-Date
        }

        Start-Sleep -Seconds $PollIntervalSeconds
    }

    throw "Timed out after $TimeoutSeconds s waiting for container '$Name' to become ready.$(Get-BcContainerLogTail -Name $Name -DockerExecutable $DockerExecutable)"
}