Modules/businessdev.ALbuild.Containers/Public/Remove-BcContainer.ps1
|
function Remove-BcContainer { <# .SYNOPSIS Removes a Business Central container (and its anonymous volumes). .PARAMETER Name Container name. .PARAMETER DockerExecutable The Docker executable to use (default 'docker'). .EXAMPLE Remove-BcContainer -Name bld #> # No ConfirmImpact='High': ALbuild runs primarily non-interactively (pipelines, the MCP, the VS Code # extension), where a High-impact confirmation prompt has no host UI and ShouldProcess throws a # NullReferenceException. -WhatIf still works and callers can opt into -Confirm; destructive intent is # gated by the consumers (e.g. the MCP's approval flow), not by an auto-prompt here. [CmdletBinding(SupportsShouldProcess)] param( [Parameter(Mandatory, ValueFromPipeline, ValueFromPipelineByPropertyName)] [ValidateNotNullOrEmpty()] [Alias('ContainerName')] [string] $Name, [string] $DockerExecutable = 'docker' ) process { if (-not $PSCmdlet.ShouldProcess($Name, 'Remove Business Central container')) { return } # Read the protocol label BEFORE removal (the container is gone after 'docker rm'). HTTP # containers never had a self-signed cert trusted, so we skip the cert cleanup for them. $protocolLabel = '' $lbl = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru -SuccessExitCodes @(0, 1) ` -Arguments @('inspect', '-f', '{{index .Config.Labels "albuild.protocol"}}', $Name) if ($lbl.Success) { $protocolLabel = "$($lbl.StdOut)".Trim() } # Retried, because on Windows this fails transiently. The layer's VHD can still be attached for # a moment after the container stops - antivirus, the search indexer or a lazily closed handle - # and the engine reports 'windowsfilter failed to remove root filesystem: failed to detach VHD # ... access denied'. A moment later the same call succeeds. Run 27339 lost a whole platform # version to exactly this, so the wait is cheaper than the container it costs. # # Only the removal is retried. Everything after it is host-side cleanup that cannot hit this. $result = $null $attempts = 5 for ($attempt = 1; $attempt -le $attempts; $attempt++) { $result = Invoke-BcDocker -DockerExecutable $DockerExecutable -Quiet -PassThru ` -Arguments @('rm', '--force', '--volumes', $Name) # 'docker rm --force' exits 0 even when the container does not exist - it just writes a # "No such container" message to stderr - so check the message, not only the exit code, # before reporting a successful removal. (A genuine removal writes nothing matching this.) if ($result.StdErr -match '(?i)no such container') { Write-ALbuildLog -Level Warning "Container '$Name' does not exist; nothing to remove." return } if ($result.Success) { break } # Retry only the transient filesystem/VHD detach condition. Anything else - a bad name, a # dead daemon - will not improve by waiting, and waiting would only hide it. $transient = "$($result.StdErr)" -match '(?i)failed to remove root filesystem|detach|being used by|access is denied|zugriff verweigert|device is not ready|nicht bereit' if (-not $transient -or $attempt -eq $attempts) { break } $wait = [Math]::Min(8, [Math]::Pow(2, $attempt - 1)) Write-ALbuildLog -Level Warning "Removing container '$Name' failed on attempt $attempt of $attempts (the layer is still held); retrying in $wait s." Start-Sleep -Seconds $wait } if (-not $result.Success) { throw "Failed to remove container '$Name' after $attempts attempt(s): $($result.StdErr.Trim())" } # Clean up the host folder bind-mounted into the container (see Get-BcContainerHostShare). $hostShare = Get-BcContainerHostShare -Name $Name if (Test-Path -LiteralPath $hostShare) { Remove-Item -LiteralPath $hostShare -Recurse -Force -ErrorAction SilentlyContinue } # Remove any certificate ALbuild trusted for this container (best effort; no-op if none). # Skip for HTTP containers - they never presented a self-signed cert to trust. if ($protocolLabel -ne 'http') { try { [void](Unregister-BcContainerCertificate -Name $Name -Confirm:$false) } catch { Write-ALbuildLog -Level Warning "Could not remove trusted certificate for '$Name': $($_.Exception.Message)" } } Write-ALbuildLog -Level Success "Removed container '$Name'." } } |