fix(agent99): harden runtime deploy rollback
This commit is contained in:
@@ -18,6 +18,7 @@ $BackupDir = Join-Path $DeployRoot "backup-$stamp"
|
||||
$ManifestPath = Join-Path $StateDir "runtime-manifest.json"
|
||||
$EvidencePath = Join-Path $EvidenceDir "agent99-deploy-$stamp.json"
|
||||
$script:ConfigMigrations = @()
|
||||
$script:RuntimeCopyRetries = @()
|
||||
|
||||
$runtimeFiles = @(
|
||||
"agent99-bootstrap.ps1",
|
||||
@@ -64,6 +65,29 @@ function Set-AgentProperty {
|
||||
}
|
||||
}
|
||||
|
||||
function Copy-AgentRuntimeFileWithRetry {
|
||||
param(
|
||||
[string]$Source,
|
||||
[string]$Destination,
|
||||
[int]$MaxAttempts = 20,
|
||||
[int]$DelayMilliseconds = 500
|
||||
)
|
||||
for ($attempt = 1; $attempt -le $MaxAttempts; $attempt += 1) {
|
||||
try {
|
||||
Copy-Item -Force $Source $Destination
|
||||
return
|
||||
} catch [System.IO.IOException] {
|
||||
if ($attempt -ge $MaxAttempts) { throw }
|
||||
$script:RuntimeCopyRetries += [pscustomobject]@{
|
||||
file = Split-Path -Leaf $Destination
|
||||
attempt = $attempt
|
||||
reason = "file_in_use"
|
||||
}
|
||||
Start-Sleep -Milliseconds $DelayMilliseconds
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Write-DeployEvidence {
|
||||
param([bool]$Ok, [string]$Status, [string]$ErrorText, [object[]]$Files, [object]$TaskResult)
|
||||
New-Item -ItemType Directory -Force -Path $EvidenceDir | Out-Null
|
||||
@@ -82,6 +106,8 @@ function Write-DeployEvidence {
|
||||
registerTasks = [bool]$RegisterTasks
|
||||
error = $ErrorText
|
||||
configMigrations = @($script:ConfigMigrations)
|
||||
runtimeCopyRetryCount = @($script:RuntimeCopyRetries).Count
|
||||
runtimeCopyRetries = @($script:RuntimeCopyRetries)
|
||||
files = $Files
|
||||
taskRegistration = $TaskResult
|
||||
} | ConvertTo-Json -Depth 10 | Set-Content -Path $EvidencePath -Encoding UTF8
|
||||
@@ -92,7 +118,7 @@ function Restore-Agent99Deployment {
|
||||
foreach ($item in $Rows) {
|
||||
$backupPath = Join-Path $BackupDir $item.name
|
||||
if ($item.existed -and (Test-Path $backupPath)) {
|
||||
Copy-Item -Force $backupPath $item.livePath
|
||||
Copy-AgentRuntimeFileWithRetry $backupPath $item.livePath
|
||||
} elseif (-not $item.existed -and (Test-Path $item.livePath)) {
|
||||
Remove-Item -Force $item.livePath
|
||||
}
|
||||
@@ -151,14 +177,6 @@ $promotionRows = @()
|
||||
$configBackedUp = $false
|
||||
$manifestBackedUp = $false
|
||||
try {
|
||||
foreach ($name in $runtimeFiles) {
|
||||
$livePath = Join-Path $BinDir $name
|
||||
$existed = Test-Path $livePath
|
||||
if ($existed) { Copy-Item -Force $livePath (Join-Path $BackupDir $name) }
|
||||
$promotionRows += [pscustomobject]@{ name = $name; livePath = $livePath; existed = [bool]$existed }
|
||||
Copy-Item -Force (Join-Path $StageDir $name) $livePath
|
||||
}
|
||||
|
||||
if (-not (Test-Path $ConfigPath)) { throw "Live Agent99 config not found: $ConfigPath" }
|
||||
Copy-Item -Force $ConfigPath (Join-Path $BackupDir "agent99.config.json")
|
||||
$configBackedUp = $true
|
||||
@@ -167,6 +185,14 @@ try {
|
||||
$manifestBackedUp = $true
|
||||
}
|
||||
|
||||
foreach ($name in $runtimeFiles) {
|
||||
$livePath = Join-Path $BinDir $name
|
||||
$existed = Test-Path $livePath
|
||||
if ($existed) { Copy-Item -Force $livePath (Join-Path $BackupDir $name) }
|
||||
$promotionRows += [pscustomobject]@{ name = $name; livePath = $livePath; existed = [bool]$existed }
|
||||
Copy-AgentRuntimeFileWithRetry (Join-Path $StageDir $name) $livePath
|
||||
}
|
||||
|
||||
$config = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
Add-DefaultProperty $config "sshIdentityFile" (Join-Path $AgentRoot "keys\agent99_ed25519")
|
||||
Add-DefaultProperty $config "sshTransport" ([pscustomobject]@{})
|
||||
|
||||
@@ -164,6 +164,25 @@ def test_agent99_deployer_verifies_promoted_runtime_and_writes_revision() -> Non
|
||||
assert f'"{name}"' in bootstrap
|
||||
|
||||
|
||||
def test_agent99_deployer_backs_up_manifest_before_runtime_promotion() -> None:
|
||||
deploy = DEPLOY.read_text(encoding="utf-8")
|
||||
promotion = deploy[deploy.index("$configBackedUp = $false") :]
|
||||
promotion = promotion[: promotion.index("$config = Get-Content $ConfigPath")]
|
||||
|
||||
assert promotion.index('Copy-Item -Force $ConfigPath') < promotion.index(
|
||||
"foreach ($name in $runtimeFiles)"
|
||||
)
|
||||
assert promotion.index('Copy-Item -Force $ManifestPath') < promotion.index(
|
||||
"foreach ($name in $runtimeFiles)"
|
||||
)
|
||||
assert 'Restore-Agent99Deployment $promotionRows $configBackedUp $manifestBackedUp' in deploy
|
||||
assert "function Copy-AgentRuntimeFileWithRetry" in deploy
|
||||
assert "MaxAttempts = 20" in deploy
|
||||
assert 'reason = "file_in_use"' in deploy
|
||||
assert "runtimeCopyRetryCount" in deploy
|
||||
assert "Copy-AgentRuntimeFileWithRetry (Join-Path $StageDir $name) $livePath" in deploy
|
||||
|
||||
|
||||
def test_agent99_completion_callback_waits_for_durable_same_trace_readback() -> None:
|
||||
source = CONTROL.read_text(encoding="utf-8")
|
||||
deploy = DEPLOY.read_text(encoding="utf-8")
|
||||
|
||||
Reference in New Issue
Block a user