아바타 저장소 승격 계약을 완성
This commit is contained in:
parent
ac9b702688
commit
ccdcfcd2f5
36 changed files with 14734 additions and 222 deletions
392
scripts/public-runtime-task-definition-cutover.ps1
Normal file
392
scripts/public-runtime-task-definition-cutover.ps1
Normal file
|
|
@ -0,0 +1,392 @@
|
|||
function Get-PublicRuntimeTaskXmlSha256 {
|
||||
param([Parameter(Mandatory = $true)][string]$Xml)
|
||||
|
||||
$bytes = [System.Text.UTF8Encoding]::new($false).GetBytes($Xml)
|
||||
$hasher = [System.Security.Cryptography.SHA256]::Create()
|
||||
try {
|
||||
return ([BitConverter]::ToString($hasher.ComputeHash($bytes))).Replace("-", "").ToLowerInvariant()
|
||||
} finally {
|
||||
$hasher.Dispose()
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PublicRuntimeTaskXmlContractSha256 {
|
||||
param([Parameter(Mandatory = $true)][string]$Xml)
|
||||
|
||||
try {
|
||||
[xml]$document = $Xml
|
||||
$enabledNodes = @(
|
||||
$document.SelectNodes("//*[local-name()='Settings']/*[local-name()='Enabled']")
|
||||
)
|
||||
if ($enabledNodes.Count -ne 1) {
|
||||
throw "Task XML must contain exactly one settings Enabled node"
|
||||
}
|
||||
$enabledNodes[0].InnerText = "TASK_OPERATIONAL_STATE"
|
||||
return Get-PublicRuntimeTaskXmlSha256 -Xml $document.OuterXml
|
||||
} catch {
|
||||
throw "Public runtime task XML contract normalization failed"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PublicRuntimeTaskDefinitionSetSha256 {
|
||||
param([Parameter(Mandatory = $true)][object[]]$Entries)
|
||||
|
||||
$lines = @()
|
||||
foreach ($entry in @($Entries | Sort-Object role)) {
|
||||
if (
|
||||
[string]$entry.role -notmatch "^(boot|watchdog)$" -or
|
||||
[string]$entry.xml_sha256 -notmatch "^[0-9a-f]{64}$"
|
||||
) {
|
||||
throw "Public runtime task definition digest input is invalid"
|
||||
}
|
||||
$enabled = "false"
|
||||
if ([bool]$entry.enabled) {
|
||||
$enabled = "true"
|
||||
}
|
||||
$lines += ([string]$entry.role + ":" + [string]$entry.xml_sha256 + ":" + $enabled)
|
||||
}
|
||||
if ($lines.Count -ne 2) {
|
||||
throw "Public runtime task definition set must contain exactly two roles"
|
||||
}
|
||||
return Get-PublicRuntimeTaskXmlSha256 -Xml (@($lines) -join "`n")
|
||||
}
|
||||
|
||||
function Get-ExactRootScheduledTaskForDefinition {
|
||||
param([Parameter(Mandatory = $true)][string]$TaskName)
|
||||
|
||||
if (
|
||||
[string]::IsNullOrWhiteSpace($TaskName) -or
|
||||
$TaskName -match "[\\/\x00-\x1f\x7f]"
|
||||
) {
|
||||
throw "Public runtime scheduled task name is invalid"
|
||||
}
|
||||
$matches = @(
|
||||
Get-ScheduledTask `
|
||||
-TaskName $TaskName `
|
||||
-TaskPath "\" `
|
||||
-ErrorAction SilentlyContinue
|
||||
)
|
||||
if ($matches.Count -ne 1) {
|
||||
throw "Public runtime root scheduled task is missing or ambiguous"
|
||||
}
|
||||
return $matches[0]
|
||||
}
|
||||
|
||||
function Assert-PublicRuntimeCoordinatedTaskNamesExact {
|
||||
param([Parameter(Mandatory = $true)][string[]]$TaskNames)
|
||||
|
||||
$actual = @($TaskNames | Sort-Object -Unique)
|
||||
$expected = @("VignettePublicRuntime", "VignettePublicRuntimeWatchdog")
|
||||
if (
|
||||
$actual.Count -ne 2 -or
|
||||
-not ($actual -ccontains $expected[0]) -or
|
||||
-not ($actual -ccontains $expected[1])
|
||||
) {
|
||||
throw "Public runtime task-definition cutover requires the exact two root tasks"
|
||||
}
|
||||
}
|
||||
|
||||
function Get-PublicRuntimeTaskDefinitionSnapshot {
|
||||
param(
|
||||
[string]$BootTaskName = "VignettePublicRuntime",
|
||||
[string]$WatchdogTaskName = "VignettePublicRuntimeWatchdog",
|
||||
[switch]$RequireEnabled
|
||||
)
|
||||
|
||||
if ([string]::Equals(
|
||||
$BootTaskName,
|
||||
$WatchdogTaskName,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)) {
|
||||
throw "Public runtime boot and watchdog task names must differ"
|
||||
}
|
||||
$entries = @()
|
||||
foreach ($spec in @(
|
||||
[pscustomobject][ordered]@{ role = "boot"; task_name = $BootTaskName },
|
||||
[pscustomobject][ordered]@{ role = "watchdog"; task_name = $WatchdogTaskName }
|
||||
)) {
|
||||
$task = Get-ExactRootScheduledTaskForDefinition -TaskName $spec.task_name
|
||||
if ($RequireEnabled -and -not [bool]$task.Settings.Enabled) {
|
||||
throw "Expected public runtime task is not enabled before bootstrap"
|
||||
}
|
||||
$actions = @($task.Actions)
|
||||
if ($actions.Count -ne 1) {
|
||||
throw "Public runtime task must have exactly one action"
|
||||
}
|
||||
$xml = [string](Export-ScheduledTask -InputObject $task -ErrorAction Stop)
|
||||
if ([string]::IsNullOrWhiteSpace($xml)) {
|
||||
throw "Public runtime scheduled task XML snapshot is empty"
|
||||
}
|
||||
$entries += [pscustomobject][ordered]@{
|
||||
role = [string]$spec.role
|
||||
task_name = [string]$spec.task_name
|
||||
xml_sha256 = Get-PublicRuntimeTaskXmlSha256 -Xml $xml
|
||||
contract_sha256 = Get-PublicRuntimeTaskXmlContractSha256 -Xml $xml
|
||||
enabled = [bool]$task.Settings.Enabled
|
||||
action_execute = [string]$actions[0].Execute
|
||||
action_arguments = [string]$actions[0].Arguments
|
||||
action_working_directory = [string]$actions[0].WorkingDirectory
|
||||
}
|
||||
}
|
||||
return [pscustomobject][ordered]@{
|
||||
entries = @($entries)
|
||||
set_sha256 = Get-PublicRuntimeTaskDefinitionSetSha256 -Entries $entries
|
||||
}
|
||||
}
|
||||
|
||||
function Assert-PublicRuntimeTaskDefinitionSnapshotCurrent {
|
||||
param([Parameter(Mandatory = $true)][object]$ExpectedSnapshot)
|
||||
|
||||
$boot = @($ExpectedSnapshot.entries | Where-Object role -eq "boot")
|
||||
$watchdog = @($ExpectedSnapshot.entries | Where-Object role -eq "watchdog")
|
||||
if ($boot.Count -ne 1 -or $watchdog.Count -ne 1) {
|
||||
throw "Expected public runtime task definition snapshot is invalid"
|
||||
}
|
||||
$actual = Get-PublicRuntimeTaskDefinitionSnapshot `
|
||||
-BootTaskName ([string]$boot[0].task_name) `
|
||||
-WatchdogTaskName ([string]$watchdog[0].task_name)
|
||||
if ([string]$actual.set_sha256 -cne [string]$ExpectedSnapshot.set_sha256) {
|
||||
throw "Public runtime task definition set drifted"
|
||||
}
|
||||
return $actual
|
||||
}
|
||||
|
||||
function Get-PublicRuntimeExpectedTaskActionContracts {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$StableSourceRoot,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedSourceCommit,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedSourceTree,
|
||||
[Parameter(Mandatory = $true)][string]$PythonPath,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadDir,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadManifestPath,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedUserUploadManifestSha256,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadWriteFreezePath,
|
||||
[Parameter(Mandatory = $true)][string]$CloudflaredPath,
|
||||
[Parameter(Mandatory = $true)][string]$CloudflaredConfigPath,
|
||||
[Parameter(Mandatory = $true)][string]$PublicHealthUrl
|
||||
)
|
||||
|
||||
$root = (Resolve-Path -LiteralPath $StableSourceRoot).Path
|
||||
$bootScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\boot-public-runtime.ps1")).Path
|
||||
$watchScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\watch-public-runtime.ps1")).Path
|
||||
$startScript = (Resolve-Path -LiteralPath (Join-Path $root "scripts\start-public-runtime.ps1")).Path
|
||||
$watchLauncher = (Resolve-Path -LiteralPath (Join-Path $root "scripts\watch-public-runtime-task.vbs")).Path
|
||||
$bootSha256 = (Get-FileHash -LiteralPath $bootScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$watchSha256 = (Get-FileHash -LiteralPath $watchScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$startSha256 = (Get-FileHash -LiteralPath $startScript -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$resolvedPython = (Resolve-Path -LiteralPath $PythonPath).Path
|
||||
$resolvedCloudflared = (Resolve-Path -LiteralPath $CloudflaredPath).Path
|
||||
$resolvedCloudflaredConfig = (Resolve-Path -LiteralPath $CloudflaredConfigPath).Path
|
||||
$pythonSha256 = (Get-FileHash -LiteralPath $resolvedPython -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$cloudflaredSha256 = (Get-FileHash -LiteralPath $resolvedCloudflared -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$cloudflaredConfigSha256 = (Get-FileHash -LiteralPath $resolvedCloudflaredConfig -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
$windowsPowerShell = Join-Path $env:SystemRoot "System32\WindowsPowerShell\v1.0\powershell.exe"
|
||||
$wscript = Join-Path $env:SystemRoot "System32\wscript.exe"
|
||||
|
||||
$bootArguments = @(
|
||||
"-NoProfile",
|
||||
"-ExecutionPolicy Bypass",
|
||||
"-WindowStyle Hidden",
|
||||
"-File `"$bootScript`"",
|
||||
"-StableSourceRoot `"$root`"",
|
||||
"-ExpectedSourceCommit $ExpectedSourceCommit",
|
||||
"-ExpectedSourceTree $ExpectedSourceTree",
|
||||
"-ExpectedBootScriptSha256 $bootSha256",
|
||||
"-ExpectedStartScriptSha256 $startSha256",
|
||||
"-ExpectedPythonSha256 $pythonSha256",
|
||||
"-ExpectedCloudflaredSha256 $cloudflaredSha256",
|
||||
"-ExpectedCloudflaredConfigSha256 $cloudflaredConfigSha256",
|
||||
"-Python `"$resolvedPython`"",
|
||||
"-Cloudflared `"$resolvedCloudflared`"",
|
||||
"-CloudflaredConfig `"$resolvedCloudflaredConfig`"",
|
||||
"-UserUploadDir `"$UserUploadDir`"",
|
||||
"-UserUploadManifestPath `"$UserUploadManifestPath`"",
|
||||
"-ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256",
|
||||
"-UserUploadWriteFreezePath `"$UserUploadWriteFreezePath`""
|
||||
) -join " "
|
||||
$watchArguments = @(
|
||||
"-File `"$watchScript`"",
|
||||
"-StableSourceRoot `"$root`"",
|
||||
"-ExpectedSourceCommit $ExpectedSourceCommit",
|
||||
"-ExpectedSourceTree $ExpectedSourceTree",
|
||||
"-ExpectedWatchdogSha256 $watchSha256",
|
||||
"-ExpectedStartScriptSha256 $startSha256",
|
||||
"-ExpectedPythonSha256 $pythonSha256",
|
||||
"-ExpectedCloudflaredSha256 $cloudflaredSha256",
|
||||
"-ExpectedCloudflaredConfigSha256 $cloudflaredConfigSha256",
|
||||
"-Python `"$resolvedPython`"",
|
||||
"-Cloudflared `"$resolvedCloudflared`"",
|
||||
"-CloudflaredConfig `"$resolvedCloudflaredConfig`"",
|
||||
"-UserUploadDir `"$UserUploadDir`"",
|
||||
"-UserUploadManifestPath `"$UserUploadManifestPath`"",
|
||||
"-ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256",
|
||||
"-UserUploadWriteFreezePath `"$UserUploadWriteFreezePath`"",
|
||||
"-PublicHealthUrl `"$PublicHealthUrl`""
|
||||
) -join " "
|
||||
return @(
|
||||
[pscustomobject][ordered]@{
|
||||
role = "boot"
|
||||
execute = $windowsPowerShell
|
||||
arguments = $bootArguments
|
||||
working_directory = $root
|
||||
},
|
||||
[pscustomobject][ordered]@{
|
||||
role = "watchdog"
|
||||
execute = $wscript
|
||||
arguments = "`"$watchLauncher`" $watchArguments"
|
||||
working_directory = $root
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
function Assert-NewPublicRuntimeTaskDefinitionsPinned {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][object]$Snapshot,
|
||||
[Parameter(Mandatory = $true)][string]$StableSourceRoot,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedSourceCommit,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedSourceTree,
|
||||
[Parameter(Mandatory = $true)][string]$PythonPath,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadDir,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadManifestPath,
|
||||
[Parameter(Mandatory = $true)][string]$ExpectedUserUploadManifestSha256,
|
||||
[Parameter(Mandatory = $true)][string]$UserUploadWriteFreezePath,
|
||||
[Parameter(Mandatory = $true)][string]$CloudflaredPath,
|
||||
[Parameter(Mandatory = $true)][string]$CloudflaredConfigPath,
|
||||
[Parameter(Mandatory = $true)][string]$PublicHealthUrl,
|
||||
[switch]$AllowEnabled
|
||||
)
|
||||
|
||||
if (@($Snapshot.entries).Count -ne 2) {
|
||||
throw "New public runtime task definition set is incomplete"
|
||||
}
|
||||
$contracts = @(
|
||||
Get-PublicRuntimeExpectedTaskActionContracts `
|
||||
-StableSourceRoot $StableSourceRoot `
|
||||
-ExpectedSourceCommit $ExpectedSourceCommit `
|
||||
-ExpectedSourceTree $ExpectedSourceTree `
|
||||
-PythonPath $PythonPath `
|
||||
-UserUploadDir $UserUploadDir `
|
||||
-UserUploadManifestPath $UserUploadManifestPath `
|
||||
-ExpectedUserUploadManifestSha256 $ExpectedUserUploadManifestSha256 `
|
||||
-UserUploadWriteFreezePath $UserUploadWriteFreezePath `
|
||||
-CloudflaredPath $CloudflaredPath `
|
||||
-CloudflaredConfigPath $CloudflaredConfigPath `
|
||||
-PublicHealthUrl $PublicHealthUrl
|
||||
)
|
||||
foreach ($entry in @($Snapshot.entries)) {
|
||||
if (-not $AllowEnabled -and [bool]$entry.enabled) {
|
||||
throw "New public runtime task definition must remain disabled until operational receipt"
|
||||
}
|
||||
$contract = @($contracts | Where-Object role -eq ([string]$entry.role))
|
||||
if (
|
||||
$contract.Count -ne 1 -or
|
||||
-not [string]::Equals(
|
||||
[string]$entry.action_execute,
|
||||
[string]$contract[0].execute,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
) -or
|
||||
[string]$entry.action_arguments -cne [string]$contract[0].arguments -or
|
||||
-not [string]::Equals(
|
||||
[string]$entry.action_working_directory,
|
||||
[string]$contract[0].working_directory,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)
|
||||
) {
|
||||
throw "New public runtime task action contract drift"
|
||||
}
|
||||
}
|
||||
return $Snapshot
|
||||
}
|
||||
|
||||
function Invoke-PublicRuntimeTaskDefinitionInstallerPairDisabled {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][scriptblock]$BootInstaller,
|
||||
[Parameter(Mandatory = $true)][scriptblock]$WatchdogInstaller,
|
||||
[Parameter(Mandatory = $true)][object[]]$MaintenanceSnapshot,
|
||||
[int]$TimeoutSec = 30
|
||||
)
|
||||
|
||||
try {
|
||||
$null = & $BootInstaller
|
||||
$null = & $WatchdogInstaller
|
||||
Assert-PublicRuntimeTasksDisabledAndIdle `
|
||||
-Snapshot $MaintenanceSnapshot `
|
||||
-TimeoutSec $TimeoutSec
|
||||
} catch {
|
||||
Suspend-PublicRuntimeTasks `
|
||||
-Snapshot $MaintenanceSnapshot `
|
||||
-TimeoutSec $TimeoutSec
|
||||
Assert-PublicRuntimeTasksDisabledAndIdle `
|
||||
-Snapshot $MaintenanceSnapshot `
|
||||
-TimeoutSec $TimeoutSec
|
||||
throw
|
||||
}
|
||||
}
|
||||
|
||||
function Enable-NewPublicRuntimeTaskDefinitions {
|
||||
param([Parameter(Mandatory = $true)][object]$DisabledSnapshot)
|
||||
|
||||
$null = Assert-PublicRuntimeTaskDefinitionSnapshotCurrent `
|
||||
-ExpectedSnapshot $DisabledSnapshot
|
||||
$boot = @($DisabledSnapshot.entries | Where-Object role -eq "boot")
|
||||
$watchdog = @($DisabledSnapshot.entries | Where-Object role -eq "watchdog")
|
||||
if ($boot.Count -ne 1 -or $watchdog.Count -ne 1) {
|
||||
throw "Disabled public runtime task definition snapshot is invalid"
|
||||
}
|
||||
try {
|
||||
foreach ($entry in @($DisabledSnapshot.entries)) {
|
||||
Enable-ScheduledTask `
|
||||
-TaskName ([string]$entry.task_name) `
|
||||
-TaskPath "\" `
|
||||
-ErrorAction Stop `
|
||||
| Out-Null
|
||||
}
|
||||
} catch {
|
||||
foreach ($entry in @($DisabledSnapshot.entries)) {
|
||||
Disable-ScheduledTask `
|
||||
-TaskName ([string]$entry.task_name) `
|
||||
-TaskPath "\" `
|
||||
-ErrorAction SilentlyContinue `
|
||||
| Out-Null
|
||||
}
|
||||
$failedTruth = Get-PublicRuntimeTaskDefinitionSnapshot `
|
||||
-BootTaskName ([string]$boot[0].task_name) `
|
||||
-WatchdogTaskName ([string]$watchdog[0].task_name)
|
||||
if (@($failedTruth.entries | Where-Object enabled).Count -ne 0) {
|
||||
throw "Public runtime task enable failed and compensation could not disable both tasks"
|
||||
}
|
||||
throw
|
||||
}
|
||||
$operational = Get-PublicRuntimeTaskDefinitionSnapshot `
|
||||
-BootTaskName ([string]$boot[0].task_name) `
|
||||
-WatchdogTaskName ([string]$watchdog[0].task_name) `
|
||||
-RequireEnabled
|
||||
foreach ($prior in @($DisabledSnapshot.entries)) {
|
||||
$current = @($operational.entries | Where-Object role -eq ([string]$prior.role))
|
||||
if (
|
||||
$current.Count -ne 1 -or
|
||||
[string]$current[0].contract_sha256 -cne [string]$prior.contract_sha256 -or
|
||||
[string]$current[0].action_arguments -cne [string]$prior.action_arguments -or
|
||||
-not [string]::Equals(
|
||||
[string]$current[0].action_execute,
|
||||
[string]$prior.action_execute,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
) -or
|
||||
-not [string]::Equals(
|
||||
[string]$current[0].action_working_directory,
|
||||
[string]$prior.action_working_directory,
|
||||
[System.StringComparison]::OrdinalIgnoreCase
|
||||
)
|
||||
) {
|
||||
foreach ($entry in @($DisabledSnapshot.entries)) {
|
||||
Disable-ScheduledTask `
|
||||
-TaskName ([string]$entry.task_name) `
|
||||
-TaskPath "\" `
|
||||
-ErrorAction SilentlyContinue `
|
||||
| Out-Null
|
||||
}
|
||||
throw "Public runtime task definition drifted while becoming operational"
|
||||
}
|
||||
}
|
||||
return $operational
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue