dark 4250a7b221 LLM action 结果分析不再传 state_summary
调整了 agent.py 和 LLM client 协议/实现。
现在只传当前 action 的结构化结果和必要诊断日志,避免历史运行态影响判断。
提示词和文档也已同步说明。

verify-ip 增加健康检查重试
默认 VERIFY_INTERVAL_SEC=10、VERIFY_MAX_ATTEMPTS=12,约 2 分钟。
verify-ip 未通过但未达到最大次数时,会播报进度、保存 checkpoint,并继续从当前 verify-ip 重试,不会进入 download-log。
参数已加入 config.txt.example、脚本配置读取、README、打包 README、Skill 文档和流程图。
2026-06-04 16:57:16 +08:00

1538 lines
58 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

param(
[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.txt'),
[string]$Action = '',
[string]$Ip = '',
[string]$HashCode = '',
[string]$RollbackIp = '',
[switch]$RollbackStopFirst,
[switch]$Help
)
# PAM 部署主脚本PowerShell 实现)。
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Stop'
function Show-DeployUsage {
@'
Usage:
powershell -File .\deploy.ps1 [-ConfigPath .\config.txt]
powershell -File .\deploy.ps1 -Action <Name> [-ConfigPath .\config.txt] [-Ip 192.168.1.10] [-HashCode xxxxx] [-RollbackStopFirst]
powershell -File .\deploy.ps1 [-ConfigPath .\config.txt] -RollbackIp 192.168.1.10 [-RollbackStopFirst]
Notes:
- deploy.bat is only a wrapper for this script.
- The wrapper avoids cmd.exe delayed-expansion issues with CLIENT_SECRET values
containing exclamation marks.
- poll-download-progress and poll-upgrade-progress only query progress once.
The Agent workflow repeats them and asks LLM/rules to judge completion.
'@ | Write-Host
}
function Write-Info([string]$Message) { Write-Host "[INFO] $Message" }
function Write-WarnLog([string]$Message) { Write-Host "[WARN] $Message" }
function Write-ErrLog([string]$Message) { Write-Host "[ERROR] $Message" }
$script:ActiveConfigPath = $ConfigPath
$script:DownloadProgressState = [ordered]@{
Status = ''
Success = ''
Step = ''
Msg = ''
Message = ''
RateOfProgress = ''
RawResponse = ''
}
$script:UpgradeProgressState = [ordered]@{
Status = ''
Success = ''
Step = ''
Msg = ''
Message = ''
RateOfProgress = ''
Code = ''
Finish = ''
LastModify = ''
RawResponse = ''
}
function Write-ResultLine([string]$Key, [AllowEmptyString()][string]$Value) {
if ($null -eq $Value) {
$Value = ''
}
$normalized = $Value.Replace("`r", ' ').Replace("`n", ' ')
Write-Host ("{0}={1}" -f $Key, $normalized)
}
function Require-IpArgument([string]$TargetIp) {
if (-not $TargetIp) {
throw 'This action requires -Ip.'
}
}
function Require-HashCodeArgument([string]$PublishHashCode) {
if (-not $PublishHashCode) {
throw 'This action requires -HashCode.'
}
}
function Write-OnlineIpsResult([string[]]$Ips) {
Write-ResultLine -Key 'ACTION' -Value 'get-online-ips'
Write-ResultLine -Key 'COUNT' -Value ([string]$Ips.Count)
foreach ($entry in $Ips) {
Write-ResultLine -Key 'IP' -Value $entry
}
}
function Write-DownloadProgressResult([string]$ActionName = 'poll-download-progress') {
Write-ResultLine -Key 'ACTION' -Value $ActionName
Write-ResultLine -Key 'STEP' -Value ([string]$script:DownloadProgressState.Step)
Write-ResultLine -Key 'MSG' -Value ([string]$script:DownloadProgressState.Msg)
Write-ResultLine -Key 'RATE_OF_PROGRESS' -Value ([string]$script:DownloadProgressState.RateOfProgress)
Write-ResultLine -Key 'STATUS' -Value ([string]$script:DownloadProgressState.Status)
Write-ResultLine -Key 'SUCCESS' -Value ([string]$script:DownloadProgressState.Success)
Write-ResultLine -Key 'MESSAGE' -Value ([string]$script:DownloadProgressState.Message)
}
function Write-UpgradeProgressResult([string]$Ip) {
Write-ResultLine -Key 'ACTION' -Value 'poll-upgrade-progress'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'STEP' -Value ([string]$script:UpgradeProgressState.Step)
Write-ResultLine -Key 'MSG' -Value ([string]$script:UpgradeProgressState.Msg)
Write-ResultLine -Key 'RATE_OF_PROGRESS' -Value ([string]$script:UpgradeProgressState.RateOfProgress)
Write-ResultLine -Key 'STATUS' -Value ([string]$script:UpgradeProgressState.Status)
Write-ResultLine -Key 'SUCCESS' -Value ([string]$script:UpgradeProgressState.Success)
Write-ResultLine -Key 'CODE' -Value ([string]$script:UpgradeProgressState.Code)
Write-ResultLine -Key 'FINISH' -Value ([string]$script:UpgradeProgressState.Finish)
Write-ResultLine -Key 'LAST_MODIFY' -Value ([string]$script:UpgradeProgressState.LastModify)
Write-ResultLine -Key 'MESSAGE' -Value ([string]$script:UpgradeProgressState.Message)
}
function Write-FlowStart([string]$Name, [string]$Detail = '') {
if ($Detail) {
Write-Info "[FLOW][START] $Name | $Detail"
} else {
Write-Info "[FLOW][START] $Name"
}
}
function Write-FlowDone([string]$Name, [string]$Detail = '') {
if ($Detail) {
Write-Info "[FLOW][DONE] $Name | $Detail"
} else {
Write-Info "[FLOW][DONE] $Name"
}
}
function Write-FlowFail([string]$Name, [string]$Detail = '') {
if ($Detail) {
Write-ErrLog "[FLOW][FAIL] $Name | $Detail"
} else {
Write-ErrLog "[FLOW][FAIL] $Name"
}
}
function Invoke-FlowStep {
param(
[string]$Name,
[scriptblock]$Action,
[string]$Detail = ''
)
Write-FlowStart -Name $Name -Detail $Detail
try {
$result = & $Action
Write-FlowDone -Name $Name
return $result
} catch {
Write-FlowFail -Name $Name -Detail $_.Exception.Message
throw
}
}
function Get-ManualRollbackCommand {
param(
[string]$Ip,
[bool]$StopFirst
)
$command = "powershell -File .\deploy.ps1 -ConfigPath `"$script:ActiveConfigPath`" -RollbackIp `"$Ip`""
if ($StopFirst) {
$command += ' -RollbackStopFirst'
}
return $command
}
function Get-PendingRollbackStatus {
param(
[string]$Ip,
[string]$Stage,
[bool]$StopFirst,
[string]$Reason
)
$status = "PENDING_AGENT_CONFIRMATION(stopFirst=$($StopFirst.ToString().ToLowerInvariant()))"
$command = Get-ManualRollbackCommand -Ip $Ip -StopFirst $StopFirst
Write-WarnLog "检测到需要回滚: ip=$Ip stage=$Stage reason=$Reason stopFirst=$StopFirst"
Write-WarnLog "当前脚本不会自动执行回滚。请由 Agent 与用户确认后,再执行: $command"
return $status
}
function Convert-ResponseContent {
param([AllowNull()][string]$Content)
if ([string]::IsNullOrWhiteSpace($Content)) {
return $null
}
try {
if ($PSVersionTable.PSVersion.Major -ge 6) {
return $Content | ConvertFrom-Json -Depth 100
}
return $Content | ConvertFrom-Json
} catch {
return $Content
}
}
function Get-ErrorBody {
param([System.Management.Automation.ErrorRecord]$ErrorRecord)
try {
$response = $ErrorRecord.Exception.Response
if ($null -eq $response) {
return $ErrorRecord.Exception.Message
}
$stream = $response.GetResponseStream()
if ($null -eq $stream) {
return $ErrorRecord.Exception.Message
}
$reader = New-Object System.IO.StreamReader($stream)
try {
return $reader.ReadToEnd()
} finally {
$reader.Dispose()
}
} catch {
return $ErrorRecord.Exception.Message
}
}
function Get-ResponseValue {
param(
$Response,
[string[]]$Candidates
)
foreach ($candidate in $Candidates) {
$current = $Response
foreach ($segment in ($candidate -split '\.')) {
if ($null -eq $current) {
break
}
if ($current -is [string]) {
$current = $null
break
}
if ($current -is [System.Collections.IDictionary]) {
if ($current.Contains($segment)) {
$current = $current[$segment]
} else {
$current = $null
break
}
} elseif ($current.PSObject.Properties.Name -contains $segment) {
$current = $current.$segment
} else {
$current = $null
break
}
}
if ($null -ne $current -and -not [string]::IsNullOrWhiteSpace([string]$current)) {
return [string]$current
}
}
return $null
}
function Get-PrimaryResponseMessage {
param($Response)
$message = Get-ResponseValue -Response $Response -Candidates @('message')
if (-not $message) {
$message = Get-ResponseValue -Response $Response -Candidates @('msg')
}
return $message
}
function Test-ResponseFailure {
param($Response)
$successFlag = Get-ResponseValue -Response $Response -Candidates @('success')
$code = Get-ResponseValue -Response $Response -Candidates @('code')
$status = Get-ResponseValue -Response $Response -Candidates @('status')
$message = Get-PrimaryResponseMessage -Response $Response
if ($successFlag -eq 'false') {
return $true
}
if ($code -and $code -ne '0') {
return $true
}
if ((@($status, $message) -join ' ') -match '(?i)fail|error') {
return $true
}
return $false
}
function Get-ScopedResponseObject {
param(
$Response,
[string]$ScopeKey
)
if (-not $ScopeKey -or $null -eq $Response -or $Response -is [string]) {
return $Response
}
if ($Response -is [System.Collections.IDictionary]) {
if ($Response.Contains($ScopeKey)) {
return $Response[$ScopeKey]
}
return $Response
}
$property = $Response.PSObject.Properties | Where-Object { $_.Name -eq $ScopeKey } | Select-Object -First 1
if ($property) {
return $property.Value
}
return $Response
}
function Get-ProgressStateMessage {
param(
[System.Collections.IDictionary]$State,
[string]$DefaultMessage
)
$message = [string]$State.Message
if (-not $message) { $message = [string]$State.Msg }
if (-not $message) { $message = [string]$State.Step }
if (-not $message) { $message = $DefaultMessage }
return $message
}
function Get-PamConfig {
param([string]$Path)
$config = [ordered]@{}
if (Test-Path -LiteralPath $Path) {
foreach ($rawLine in Get-Content -LiteralPath $Path -Encoding UTF8) {
$line = $rawLine.TrimEnd("`r")
if ($line -match '^\s*$' -or $line -match '^\s*[#;]') {
continue
}
$index = $line.IndexOf('=')
if ($index -lt 1) {
continue
}
$key = $line.Substring(0, $index).Trim()
$value = $line.Substring($index + 1).Trim()
if ($value -match '^(.*?\S)\s+[;#].*$') {
$value = $Matches[1]
}
switch ($key) {
'HOME_BASE_URL' { $config[$key] = $value }
'CLIENT_ID' { $config[$key] = $value }
'CLIENT_SECRET' { $config[$key] = $value }
'AIRPORT_CODE' { $config[$key] = $value }
'APP_NAME' { $config[$key] = $value }
'MODULE_NAME' { $config[$key] = $value }
'VERSION_NUMBER' { $config[$key] = $value }
'ZIP_FILE_PATH' { $config[$key] = $value }
'ACTION_TYPE' { $config[$key] = $value }
'TIMEOUT' { $config[$key] = $value }
'LOG_NAME' { $config[$key] = $value }
'POLL_INTERVAL_SEC' { $config[$key] = $value }
'DOWNLOAD_POLL_MAX_ATTEMPTS' { $config[$key] = $value }
'UPGRADE_POLL_MAX_ATTEMPTS' { $config[$key] = $value }
'VERIFY_INTERVAL_SEC' { $config[$key] = $value }
'VERIFY_MAX_ATTEMPTS' { $config[$key] = $value }
}
}
} else {
Write-WarnLog "Config file not found: $Path. Defaults will be used."
}
$defaults = [ordered]@{
HOME_BASE_URL = 'https://pam.home.com'
CLIENT_ID = 'your_client_id'
CLIENT_SECRET = 'your_client_secret'
AIRPORT_CODE = 'HET'
APP_NAME = 'PAM'
MODULE_NAME = 'Node'
VERSION_NUMBER = '2.0.5'
ZIP_FILE_PATH = 'C:\path\to\pam-2.0.5.zip'
ACTION_TYPE = 'FULL'
TIMEOUT = '120'
LOG_NAME = 'app.log'
POLL_INTERVAL_SEC = '2'
DOWNLOAD_POLL_MAX_ATTEMPTS = '60'
UPGRADE_POLL_MAX_ATTEMPTS = '600'
VERIFY_INTERVAL_SEC = '10'
VERIFY_MAX_ATTEMPTS = '12'
}
foreach ($name in $defaults.Keys) {
if (-not $config.Contains($name) -or [string]::IsNullOrWhiteSpace([string]$config[$name])) {
$config[$name] = $defaults[$name]
}
}
return [pscustomobject]$config
}
function Join-RequestPairs {
param([System.Collections.IDictionary]$Values)
$pairs = foreach ($key in $Values.Keys) {
$encodedValue = [System.Uri]::EscapeDataString([string]$Values[$key])
'{0}={1}' -f $key, $encodedValue
}
return ($pairs -join '&')
}
function Test-ZipFile {
param($Config)
if (-not (Test-Path -LiteralPath $Config.ZIP_FILE_PATH)) {
throw "Package file not found: $($Config.ZIP_FILE_PATH)"
}
}
function Invoke-PamWebRequest {
param(
[ValidateSet('GET', 'POST', 'PUT')]
[string]$Method,
[string]$Url,
[string]$Token,
[hashtable]$Headers = @{},
[AllowNull()]$Body = $null,
[string]$ContentType = '',
[string]$OutFile = ''
)
$allHeaders = @{}
if ($Token) {
$allHeaders['Authorization'] = "Bearer $Token"
}
foreach ($key in $Headers.Keys) {
$allHeaders[$key] = $Headers[$key]
}
$params = @{
Uri = $Url
Method = $Method
Headers = $allHeaders
ErrorAction = 'Stop'
}
if ($PSVersionTable.PSVersion.Major -lt 6) {
$params['UseBasicParsing'] = $true
}
if ($PSBoundParameters.ContainsKey('Body') -and $null -ne $Body -and "$Body" -ne '') {
$params['Body'] = $Body
}
if ($ContentType) {
$params['ContentType'] = $ContentType
}
if ($OutFile) {
$params['OutFile'] = $OutFile
}
try {
$response = Invoke-WebRequest @params
if ($OutFile) {
return $OutFile
}
return Convert-ResponseContent $response.Content
} catch {
$body = Get-ErrorBody $_
throw "Request failed [$Method] $Url`n$body"
}
}
function Invoke-PamMultipartUpload {
param(
[string]$Url,
[string]$Token,
[string]$FilePath,
[hashtable]$Fields
)
Add-Type -AssemblyName System.Net.Http
$client = [System.Net.Http.HttpClient]::new()
try {
$client.DefaultRequestHeaders.Authorization =
[System.Net.Http.Headers.AuthenticationHeaderValue]::new('Bearer', $Token)
$content = [System.Net.Http.MultipartFormDataContent]::new()
foreach ($entry in $Fields.GetEnumerator()) {
$stringContent = [System.Net.Http.StringContent]::new([string]$entry.Value, [System.Text.Encoding]::UTF8)
$content.Add($stringContent, $entry.Key)
}
$stream = [System.IO.File]::OpenRead($FilePath)
try {
$fileContent = [System.Net.Http.StreamContent]::new($stream)
$fileContent.Headers.ContentType =
[System.Net.Http.Headers.MediaTypeHeaderValue]::Parse('application/octet-stream')
$content.Add($fileContent, 'file', [System.IO.Path]::GetFileName($FilePath))
$response = $client.PostAsync($Url, $content).GetAwaiter().GetResult()
$body = $response.Content.ReadAsStringAsync().GetAwaiter().GetResult()
if (-not $response.IsSuccessStatusCode) {
throw "Upload failed [HTTP $([int]$response.StatusCode)]`n$body"
}
return Convert-ResponseContent $body
} finally {
$stream.Dispose()
}
} finally {
$client.Dispose()
}
}
function Get-Token {
param($Config)
Write-Info 'Getting token...'
$body = Join-RequestPairs ([ordered]@{
grant_type = 'client_credentials'
client_id = $Config.CLIENT_ID
client_secret = $Config.CLIENT_SECRET
})
$response = Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/oauth/token" -Token '' -Body $body -ContentType 'application/x-www-form-urlencoded'
$token = Get-ResponseValue -Response $response -Candidates @('access_token')
if (-not $token) {
throw "Invalid token response: $response"
}
return $token
}
function New-VersionRecord {
param($Config, [string]$Token)
Write-Info 'Step 2.1: create version record'
$body = Join-RequestPairs ([ordered]@{
versionNumber = $Config.VERSION_NUMBER
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
description = 'Auto Deploy'
})
[void](Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/api/version/upgrade" -Token $Token -Body $body -ContentType 'application/x-www-form-urlencoded')
}
function Upload-Package {
param($Config, [string]$Token)
Write-Info 'Step 2.2: upload package'
$response = Invoke-PamMultipartUpload -Url "$($Config.HOME_BASE_URL)/api/version/upgrade/upload" -Token $Token -FilePath $Config.ZIP_FILE_PATH -Fields @{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
versionNumber = $Config.VERSION_NUMBER
}
$hashCode = Get-ResponseValue -Response $response -Candidates @('hashCode', 'data.hashCode')
if (-not $hashCode -and $response -is [string]) {
$hashCode = $response.Trim()
}
if (-not $hashCode) {
throw "Unable to parse hashCode from upload response: $response"
}
return $hashCode
}
function Publish-Version {
param($Config, [string]$Token, [string]$HashCode)
Write-Info 'Step 2.3: publish version'
$payload = @{
airportCodesWhite = @($Config.AIRPORT_CODE)
hashCode = $HashCode
state = 'RELEASE'
} | ConvertTo-Json -Depth 5
$query = Join-RequestPairs ([ordered]@{
versionNumber = $Config.VERSION_NUMBER
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
})
[void](Invoke-PamWebRequest -Method PUT -Url "$($Config.HOME_BASE_URL)/api/version/upgrade/profile?$query" -Token $Token -Body $payload -ContentType 'application/json')
}
function Get-NodeUrl {
param($Config, [string]$Token)
Write-Info 'Step 3.1: resolve node url'
$response = Invoke-PamWebRequest -Method GET -Url "$($Config.HOME_BASE_URL)/api/mcp/airport/target-node?airportCode=$($Config.AIRPORT_CODE)" -Token $Token
if ($response -is [System.Collections.IDictionary]) {
return [string]($response.Keys | Select-Object -First 1)
}
$propertyNames = @($response.PSObject.Properties.Name)
if ($propertyNames.Count -gt 0) {
return [string]$propertyNames[0]
}
throw "Unable to resolve node url: $response"
}
function Get-OnlineIps {
param($Config, [string]$Token, [string]$NodeUrl)
Write-Info 'Step 3.2: query online IP list'
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
})
$response = Invoke-PamWebRequest -Method GET -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/ips?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$ips = @()
if ($response -is [System.Array]) {
$ips = @($response | ForEach-Object { [string]$_ } | Where-Object { $_ })
} elseif ($response -is [System.Collections.IEnumerable] -and -not ($response -is [string])) {
$ips = @($response | ForEach-Object { [string]$_ } | Where-Object { $_ })
}
if ($ips.Count -eq 0) {
throw "No online workstation matched the module. Raw response: $response"
}
return $ips
}
function Wait-DownloadProgress {
param($Config, [string]$Token, [string]$NodeUrl)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
versionNumber = $Config.VERSION_NUMBER
})
$progressUrl = "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/download-cloud/progress?$query"
$script:DownloadProgressState = [ordered]@{
Status = ''
Success = ''
Step = ''
Msg = ''
Message = ''
RateOfProgress = ''
RawResponse = ''
}
$maxAttempts = 60
[int]::TryParse([string]$Config.DOWNLOAD_POLL_MAX_ATTEMPTS, [ref]$maxAttempts) | Out-Null
if ($maxAttempts -lt 1) { $maxAttempts = 60 }
$pollIntervalSec = 2
[int]::TryParse([string]$Config.POLL_INTERVAL_SEC, [ref]$pollIntervalSec) | Out-Null
if ($pollIntervalSec -lt 0) { $pollIntervalSec = 2 }
for ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
$response = Invoke-PamWebRequest -Method GET -Url $progressUrl -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$status = Get-ResponseValue -Response $response -Candidates @('status')
$successFlag = Get-ResponseValue -Response $response -Candidates @('success')
$step = Get-ResponseValue -Response $response -Candidates @('step')
$msg = Get-ResponseValue -Response $response -Candidates @('msg')
$progressValue = Get-ResponseValue -Response $response -Candidates @('rateOfProgress', 'progress', 'percent', 'data.rateOfProgress', 'data.progress', 'data.percent')
$message = Get-ResponseValue -Response $response -Candidates @('message')
if (-not $message) { $message = $msg }
$script:DownloadProgressState = [ordered]@{
Status = [string]$status
Success = [string]$successFlag
Step = [string]$step
Msg = [string]$msg
Message = [string]$message
RateOfProgress = [string]$progressValue
RawResponse = [string]$response
}
$progressParts = [System.Collections.Generic.List[string]]::new()
if ($msg) { $progressParts.Add("msg=$msg") }
if ($step) { $progressParts.Add("step=$step") }
if ($progressValue) { $progressParts.Add("rateOfProgress=$progressValue") }
if ($status) { $progressParts.Add("status=$status") }
if ($successFlag) { $progressParts.Add("success=$successFlag") }
if ($message -and $message -ne $msg) { $progressParts.Add("message=$message") }
if ($progressParts.Count -gt 0) {
Write-Info ("Step 3.3b: async download progress -> {0}" -f ($progressParts -join ', '))
} else {
Write-Info ("Step 3.3b: async download progress polling... ({0}/{1})" -f ($attempt + 1), $maxAttempts)
}
if ($step -eq 'DONE' -or $status -eq 'completed' -or $successFlag -eq 'true' -or (($msg -eq 'success') -and ($progressValue -eq '100'))) {
return
}
if ((@($step, $message, $msg) -join ' ') -match '(?i)fail|error') {
if (-not $message) { $message = $step }
if (-not $message) { $message = $msg }
throw "Node download failed: $message"
}
Start-Sleep -Seconds $pollIntervalSec
}
throw 'Node download timed out.'
}
function Read-DownloadProgress {
param($Config, [string]$Token, [string]$NodeUrl)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
versionNumber = $Config.VERSION_NUMBER
})
$progressUrl = "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/download-cloud/progress?$query"
$response = Invoke-PamWebRequest -Method GET -Url $progressUrl -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$status = Get-ResponseValue -Response $response -Candidates @('status')
$successFlag = Get-ResponseValue -Response $response -Candidates @('success')
$step = Get-ResponseValue -Response $response -Candidates @('step')
$msg = Get-ResponseValue -Response $response -Candidates @('msg')
$progressValue = Get-ResponseValue -Response $response -Candidates @('rateOfProgress', 'progress', 'percent', 'data.rateOfProgress', 'data.progress', 'data.percent')
$message = Get-ResponseValue -Response $response -Candidates @('message')
if (-not $message) { $message = $msg }
$script:DownloadProgressState = [ordered]@{
Status = [string]$status
Success = [string]$successFlag
Step = [string]$step
Msg = [string]$msg
Message = [string]$message
RateOfProgress = [string]$progressValue
RawResponse = [string]$response
}
$progressParts = [System.Collections.Generic.List[string]]::new()
if ($msg) { $progressParts.Add("msg=$msg") }
if ($step) { $progressParts.Add("step=$step") }
if ($progressValue) { $progressParts.Add("rateOfProgress=$progressValue") }
if ($status) { $progressParts.Add("status=$status") }
if ($successFlag) { $progressParts.Add("success=$successFlag") }
if ($message -and $message -ne $msg) { $progressParts.Add("message=$message") }
if ($progressParts.Count -gt 0) {
Write-Info ("Step 3.3b: async download progress single query -> {0}" -f ($progressParts -join ', '))
} else {
Write-Info 'Step 3.3b: async download progress single query returned no explicit progress fields.'
}
if ((@($step, $message, $msg, $status) -join ' ') -match '(?i)fail|error') {
if (-not $message) { $message = $step }
if (-not $message) { $message = $msg }
throw "Node download failed: $message"
}
}
function Create-DownloadTask {
param($Config, [string]$Token, [string]$NodeUrl)
Write-Info 'Step 3.3: download package to node'
$query = Join-RequestPairs ([ordered]@{
versionNumber = $Config.VERSION_NUMBER
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
timeOut = '0'
})
[void](Invoke-PamWebRequest -Method GET -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/download-cloud?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
'airport-code' = $Config.AIRPORT_CODE
})
}
function Download-CloudToNode {
param($Config, [string]$Token, [string]$NodeUrl)
Create-DownloadTask -Config $Config -Token $Token -NodeUrl $NodeUrl
Wait-DownloadProgress -Config $Config -Token $Token -NodeUrl $NodeUrl
}
function Wait-UpgradeProgress {
param(
$Config,
[string]$Token,
[string]$NodeUrl,
[string]$Ip
)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
versionNumber = $Config.VERSION_NUMBER
})
$progressUrl = "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/progress?$query"
$script:UpgradeProgressState = [ordered]@{
Status = ''
Success = ''
Step = ''
Msg = ''
Message = ''
RateOfProgress = ''
Code = ''
Finish = ''
LastModify = ''
RawResponse = ''
}
$maxAttempts = 600
[int]::TryParse([string]$Config.UPGRADE_POLL_MAX_ATTEMPTS, [ref]$maxAttempts) | Out-Null
if ($maxAttempts -lt 1) { $maxAttempts = 600 }
$pollIntervalSec = 2
[int]::TryParse([string]$Config.POLL_INTERVAL_SEC, [ref]$pollIntervalSec) | Out-Null
if ($pollIntervalSec -lt 0) { $pollIntervalSec = 2 }
for ($attempt = 0; $attempt -lt $maxAttempts; $attempt++) {
$response = Invoke-PamWebRequest -Method GET -Url $progressUrl -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$progressResponse = Get-ScopedResponseObject -Response $response -ScopeKey $Ip
$status = Get-ResponseValue -Response $progressResponse -Candidates @('status')
$successFlag = Get-ResponseValue -Response $progressResponse -Candidates @('success')
$step = Get-ResponseValue -Response $progressResponse -Candidates @('step')
$msg = Get-ResponseValue -Response $progressResponse -Candidates @('msg')
$progressValue = Get-ResponseValue -Response $progressResponse -Candidates @('rateOfProgress', 'progress', 'percent', 'data.rateOfProgress', 'data.progress', 'data.percent')
$message = Get-ResponseValue -Response $progressResponse -Candidates @('message')
$code = Get-ResponseValue -Response $progressResponse -Candidates @('code')
$finish = Get-ResponseValue -Response $progressResponse -Candidates @('finish')
$lastModify = Get-ResponseValue -Response $progressResponse -Candidates @('lastModify')
if (-not $message) { $message = $msg }
$script:UpgradeProgressState = [ordered]@{
Status = [string]$status
Success = [string]$successFlag
Step = [string]$step
Msg = [string]$msg
Message = [string]$message
RateOfProgress = [string]$progressValue
Code = [string]$code
Finish = [string]$finish
LastModify = [string]$lastModify
RawResponse = [string]$response
}
$progressParts = [System.Collections.Generic.List[string]]::new()
$progressParts.Add("ip=$Ip")
if ($msg) { $progressParts.Add("msg=$msg") }
if ($step) { $progressParts.Add("step=$step") }
if ($progressValue) { $progressParts.Add("rateOfProgress=$progressValue") }
if ($code) { $progressParts.Add("code=$code") }
if ($finish) { $progressParts.Add("finish=$finish") }
if ($status) { $progressParts.Add("status=$status") }
if ($successFlag) { $progressParts.Add("success=$successFlag") }
if ($lastModify) { $progressParts.Add("lastModify=$lastModify") }
if ($message -and $message -ne $msg) { $progressParts.Add("message=$message") }
if ($progressParts.Count -gt 1) {
Write-Info ("Step 3.4a: async upgrade progress -> {0}" -f ($progressParts -join ', '))
} else {
Write-Info ("Step 3.4a: async upgrade progress polling... ip={0} ({1}/{2})" -f $Ip, ($attempt + 1), $maxAttempts)
}
if ($step -eq 'DONE' -or $finish -eq 'true' -or $status -eq 'completed' -or $successFlag -eq 'true') {
return
}
if (($msg -eq 'success') -and ($progressValue -eq '100') -and ((-not $code) -or $code -eq '0')) {
return
}
if ($code -and $code -ne '0') {
if (-not $message) { $message = $msg }
if (-not $message) { $message = $step }
if (-not $message) { $message = "code=$code" }
throw "Node upgrade failed: ip=$Ip, message=$message"
}
if ((@($step, $message, $msg, $status) -join ' ') -match '(?i)fail|error') {
if (-not $message) { $message = $step }
if (-not $message) { $message = $msg }
throw "Node upgrade failed: ip=$Ip, message=$message"
}
Start-Sleep -Seconds $pollIntervalSec
}
throw "Node upgrade timed out: ip=$Ip"
}
function Read-UpgradeProgress {
param(
$Config,
[string]$Token,
[string]$NodeUrl,
[string]$Ip
)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
versionNumber = $Config.VERSION_NUMBER
})
$progressUrl = "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/progress?$query"
$response = Invoke-PamWebRequest -Method GET -Url $progressUrl -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$progressResponse = Get-ScopedResponseObject -Response $response -ScopeKey $Ip
$status = Get-ResponseValue -Response $progressResponse -Candidates @('status')
$successFlag = Get-ResponseValue -Response $progressResponse -Candidates @('success')
$step = Get-ResponseValue -Response $progressResponse -Candidates @('step')
$msg = Get-ResponseValue -Response $progressResponse -Candidates @('msg')
$progressValue = Get-ResponseValue -Response $progressResponse -Candidates @('rateOfProgress', 'progress', 'percent', 'data.rateOfProgress', 'data.progress', 'data.percent')
$message = Get-ResponseValue -Response $progressResponse -Candidates @('message')
$code = Get-ResponseValue -Response $progressResponse -Candidates @('code')
$finish = Get-ResponseValue -Response $progressResponse -Candidates @('finish')
$lastModify = Get-ResponseValue -Response $progressResponse -Candidates @('lastModify')
if (-not $message) { $message = $msg }
$script:UpgradeProgressState = [ordered]@{
Status = [string]$status
Success = [string]$successFlag
Step = [string]$step
Msg = [string]$msg
Message = [string]$message
RateOfProgress = [string]$progressValue
Code = [string]$code
Finish = [string]$finish
LastModify = [string]$lastModify
RawResponse = [string]$response
}
$progressParts = [System.Collections.Generic.List[string]]::new()
$progressParts.Add("ip=$Ip")
if ($msg) { $progressParts.Add("msg=$msg") }
if ($step) { $progressParts.Add("step=$step") }
if ($progressValue) { $progressParts.Add("rateOfProgress=$progressValue") }
if ($code) { $progressParts.Add("code=$code") }
if ($finish) { $progressParts.Add("finish=$finish") }
if ($status) { $progressParts.Add("status=$status") }
if ($successFlag) { $progressParts.Add("success=$successFlag") }
if ($lastModify) { $progressParts.Add("lastModify=$lastModify") }
if ($message -and $message -ne $msg) { $progressParts.Add("message=$message") }
if ($progressParts.Count -gt 1) {
Write-Info ("Step 3.4a: async upgrade progress single query -> {0}" -f ($progressParts -join ', '))
} else {
Write-Info ("Step 3.4a: async upgrade progress single query returned no explicit progress fields: ip={0}" -f $Ip)
}
if ($code -and $code -ne '0') {
if (-not $message) { $message = $msg }
if (-not $message) { $message = $step }
if (-not $message) { $message = "code=$code" }
throw "Node upgrade failed: ip=$Ip, message=$message"
}
if ((@($step, $message, $msg, $status) -join ' ') -match '(?i)fail|error') {
if (-not $message) { $message = $step }
if (-not $message) { $message = $msg }
throw "Node upgrade failed: ip=$Ip, message=$message"
}
}
function Invoke-UpgradeRequest {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
$query = Join-RequestPairs ([ordered]@{
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
versionNumber = $Config.VERSION_NUMBER
action = $Config.ACTION_TYPE
autoStart = 'false'
timeOut = '0'
})
Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
}
function Start-Application {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
$query = Join-RequestPairs ([ordered]@{
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
runStart = 'true'
})
[void](Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/start-stop?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
})
}
function Stop-Application {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
$query = Join-RequestPairs ([ordered]@{
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
runStart = 'false'
})
[void](Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/start-stop?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
})
}
function Verify-Ip {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
})
Invoke-PamWebRequest -Method GET -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/verify?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
}
function Download-DeployLog {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
$logsDir = Join-Path $PSScriptRoot 'logs'
if (-not (Test-Path -LiteralPath $logsDir)) {
$null = New-Item -ItemType Directory -Path $logsDir
}
$logFile = Join-Path $logsDir ("deploy_{0}.zip" -f $Ip)
$errorFile = Join-Path $logsDir ("error_{0}.log" -f $Ip)
$query = Join-RequestPairs ([ordered]@{
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
logName = $Config.LOG_NAME
})
try {
[void](Invoke-PamWebRequest -Method GET -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/log-download?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
} -OutFile $logFile)
if ((Get-Item -LiteralPath $logFile).Length -gt 0) {
@(
'Archive format: zip'
("Saved path: {0}" -f $logFile)
("Size: {0} bytes" -f (Get-Item -LiteralPath $logFile).Length)
) | Set-Content -LiteralPath "$logFile.summary"
} else {
'Zip archive empty or no data' | Set-Content -LiteralPath "$logFile.summary"
}
} catch {
Get-ErrorBody $_ | Set-Content -LiteralPath $errorFile
"Log download failed. See $errorFile" | Set-Content -LiteralPath $logFile
'Log download failed' | Set-Content -LiteralPath "$logFile.summary"
}
return $logFile
}
function Invoke-Rollback {
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip, [bool]$StopFirst)
if ($StopFirst) {
try {
Stop-Application -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
} catch {
}
}
try {
$body = Join-RequestPairs ([ordered]@{
airportCode = $Config.AIRPORT_CODE
targetIp = $Ip
applicationName = $Config.APP_NAME
moduleName = $Config.MODULE_NAME
timeOut = $Config.TIMEOUT
})
$response = Invoke-PamWebRequest -Method POST -Url "$($Config.HOME_BASE_URL)/node-proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/rollback" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
} -Body $body -ContentType 'application/x-www-form-urlencoded'
$rollbackSuccess = Get-ResponseValue -Response $response -Candidates @('success')
if ($rollbackSuccess -and $rollbackSuccess -ne 'true') {
return 'ROLLBACK_FAILED'
}
} catch {
return 'ROLLBACK_REQUEST_FAILED'
}
try {
$verify = Verify-Ip -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
if ((Get-ResponseValue -Response $verify -Candidates @('success')) -eq 'true') {
return 'ROLLBACK_SUCCESS'
}
return 'ROLLBACK_VERIFY_FAILED'
} catch {
return 'ROLLBACK_VERIFY_FAILED'
}
}
function Invoke-IpDeploy {
param(
$Config,
[string]$Token,
[string]$NodeUrl,
[string]$Ip
)
Write-Info "Processing IP: $Ip"
try {
$upgrade = Invoke-FlowStep -Name "Invoke-UpgradeRequest[$Ip]" -Action {
Invoke-UpgradeRequest -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
} catch {
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'UPGRADE'
Message = 'Upgrade request failed'
Rollback = 'ROLLBACK_NOT_RUN'
LogFile = $logFile
}
}
if (Test-ResponseFailure -Response $upgrade) {
$message = Get-PrimaryResponseMessage -Response $upgrade
if (-not $message) { $message = 'Upgrade task creation failed' }
$rollback = Get-PendingRollbackStatus -Ip $Ip -Stage 'UPGRADE' -StopFirst:$false -Reason $message
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'UPGRADE'
Message = $message
Rollback = $rollback
LogFile = $logFile
}
}
try {
Invoke-FlowStep -Name "Wait-UpgradeProgress[$Ip]" -Action {
Wait-UpgradeProgress -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
} | Out-Null
} catch {
$message = Get-ProgressStateMessage -State $script:UpgradeProgressState -DefaultMessage 'Upgrade progress polling failed'
$rollback = Get-PendingRollbackStatus -Ip $Ip -Stage 'UPGRADE_PROGRESS' -StopFirst:$false -Reason $message
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'UPGRADE_PROGRESS'
Message = $message
Rollback = $rollback
LogFile = $logFile
}
}
try {
Invoke-FlowStep -Name "Start-Application[$Ip]" -Action {
Start-Application -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
} | Out-Null
} catch {
$rollback = Get-PendingRollbackStatus -Ip $Ip -Stage 'START' -StopFirst:$true -Reason 'Application start failed'
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'START'
Message = 'Application start failed'
Rollback = $rollback
LogFile = $logFile
}
}
try {
$verify = Invoke-FlowStep -Name "Verify-Ip[$Ip]" -Action {
Verify-Ip -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
} catch {
$rollback = Get-PendingRollbackStatus -Ip $Ip -Stage 'VERIFY' -StopFirst:$true -Reason 'Health check request failed'
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'VERIFY'
Message = 'Health check request failed'
Rollback = $rollback
LogFile = $logFile
}
}
if ((Get-ResponseValue -Response $verify -Candidates @('success')) -eq 'true') {
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'SUCCESS'
Stage = '-'
Message = '-'
Rollback = '-'
LogFile = $logFile
}
}
$message = Get-ResponseValue -Response $verify -Candidates @('message')
if (-not $message) { $message = 'Health check failed' }
$rollback = Get-PendingRollbackStatus -Ip $Ip -Stage 'VERIFY' -StopFirst:$true -Reason $message
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action {
Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
}
return [pscustomobject]@{
Ip = $Ip
Status = 'FAILED'
Stage = 'VERIFY'
Message = $message
Rollback = $rollback
LogFile = $logFile
}
}
function Write-DeployReport {
param(
$Config,
[System.Collections.Generic.List[object]]$Results,
[int]$TotalCount
)
$successCount = @($Results | Where-Object { $_.Status -eq 'SUCCESS' }).Count
$failCount = @($Results | Where-Object { $_.Status -ne 'SUCCESS' }).Count
Write-Host ''
Write-Host '====================== DEPLOY REPORT ======================'
Write-Host 'Mode: Batch/PowerShell'
Write-Host "Airport: $($Config.AIRPORT_CODE)"
Write-Host "Application: $($Config.APP_NAME)"
Write-Host "Module: $($Config.MODULE_NAME)"
Write-Host "Version: $($Config.VERSION_NUMBER)"
Write-Host "Total: $TotalCount"
Write-Host "Success: $successCount"
Write-Host "Failed: $failCount"
Write-Host ''
Write-Host ('{0,-18} {1,-8} {2,-12} {3,-22} {4}' -f 'IP', 'STATUS', 'STAGE', 'ROLLBACK', 'LOG')
foreach ($item in $Results) {
Write-Host ('{0,-18} {1,-8} {2,-12} {3,-22} {4}' -f $item.Ip, $item.Status, $item.Stage, $item.Rollback, $item.LogFile)
if ($item.Status -ne 'SUCCESS') {
Write-Host (" Reason: {0}" -f $item.Message)
}
}
}
function Invoke-PamDeploy {
param([string]$ConfigPath)
$script:ActiveConfigPath = $ConfigPath
$config = Invoke-FlowStep -Name 'Get-PamConfig' -Detail "path=$ConfigPath" -Action {
Get-PamConfig -Path $ConfigPath
}
Invoke-FlowStep -Name 'Test-ZipFile' -Action {
Test-ZipFile -Config $config
} | Out-Null
Write-Info "Deploy start: airport=$($config.AIRPORT_CODE), version=$($config.VERSION_NUMBER), module=$($config.APP_NAME)/$($config.MODULE_NAME)"
$token = Invoke-FlowStep -Name 'Get-Token' -Action {
Get-Token -Config $config
}
Invoke-FlowStep -Name 'New-VersionRecord' -Action {
New-VersionRecord -Config $config -Token $token
} | Out-Null
$hashCode = Invoke-FlowStep -Name 'Upload-Package' -Action {
Upload-Package -Config $config -Token $token
}
Invoke-FlowStep -Name 'Publish-Version' -Action {
Publish-Version -Config $config -Token $token -HashCode $hashCode
} | Out-Null
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action {
Get-NodeUrl -Config $config -Token $token
}
$ips = Invoke-FlowStep -Name 'Get-OnlineIps' -Action {
Get-OnlineIps -Config $config -Token $token -NodeUrl $nodeUrl
}
Invoke-FlowStep -Name 'Download-CloudToNode' -Action {
Download-CloudToNode -Config $config -Token $token -NodeUrl $nodeUrl
} | Out-Null
$results = [System.Collections.Generic.List[object]]::new()
foreach ($ip in $ips) {
$results.Add((Invoke-IpDeploy -Config $config -Token $token -NodeUrl $nodeUrl -Ip $ip))
}
Write-DeployReport -Config $config -Results $results -TotalCount $ips.Count
}
function Invoke-PamManualRollback {
param(
[string]$ConfigPath,
[string]$Ip,
[bool]$StopFirst
)
$script:ActiveConfigPath = $ConfigPath
$config = Invoke-FlowStep -Name 'Get-PamConfig' -Detail "path=$ConfigPath" -Action {
Get-PamConfig -Path $ConfigPath
}
Write-Info "Manual rollback start: airport=$($config.AIRPORT_CODE), ip=$Ip, stopFirst=$StopFirst"
$token = Invoke-FlowStep -Name 'Get-Token' -Action {
Get-Token -Config $config
}
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action {
Get-NodeUrl -Config $config -Token $token
}
$result = Invoke-FlowStep -Name "Invoke-Rollback[$Ip]" -Action {
Invoke-Rollback -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip -StopFirst:$StopFirst
}
Write-Info "Manual rollback done: ip=$Ip result=$result"
Write-Host "ROLLBACK RESULT: $result"
}
function Invoke-PamAction {
param(
[string]$ConfigPath,
[string]$Action,
[string]$Ip,
[string]$HashCode,
[bool]$StopFirst
)
$script:ActiveConfigPath = $ConfigPath
$config = Invoke-FlowStep -Name 'Get-PamConfig' -Detail "path=$ConfigPath" -Action {
Get-PamConfig -Path $ConfigPath
}
switch ($Action.ToLowerInvariant()) {
'get-token' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
Write-ResultLine -Key 'ACTION' -Value 'get-token'
Write-ResultLine -Key 'TOKEN' -Value $token
}
'create-version' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
Invoke-FlowStep -Name 'New-VersionRecord' -Action { New-VersionRecord -Config $config -Token $token } | Out-Null
Write-ResultLine -Key 'ACTION' -Value 'create-version'
Write-ResultLine -Key 'VERSION_NUMBER' -Value $config.VERSION_NUMBER
Write-ResultLine -Key 'RESULT' -Value 'OK'
}
'upload-package' {
Invoke-FlowStep -Name 'Test-ZipFile' -Action { Test-ZipFile -Config $config } | Out-Null
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$hash = Invoke-FlowStep -Name 'Upload-Package' -Action { Upload-Package -Config $config -Token $token }
Write-ResultLine -Key 'ACTION' -Value 'upload-package'
Write-ResultLine -Key 'HASH_CODE' -Value $hash
}
'publish-version' {
Require-HashCodeArgument -PublishHashCode $HashCode
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
Invoke-FlowStep -Name 'Publish-Version' -Action { Publish-Version -Config $config -Token $token -HashCode $HashCode } | Out-Null
Write-ResultLine -Key 'ACTION' -Value 'publish-version'
Write-ResultLine -Key 'HASH_CODE' -Value $HashCode
Write-ResultLine -Key 'RESULT' -Value 'OK'
}
'get-node-url' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Write-ResultLine -Key 'ACTION' -Value 'get-node-url'
Write-ResultLine -Key 'NODE_URL' -Value $nodeUrl
}
'get-online-ips' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
$ips = Invoke-FlowStep -Name 'Get-OnlineIps' -Action { Get-OnlineIps -Config $config -Token $token -NodeUrl $nodeUrl }
Write-OnlineIpsResult -Ips @($ips)
}
'create-download-task' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name 'Create-DownloadTask' -Action { Create-DownloadTask -Config $config -Token $token -NodeUrl $nodeUrl } | Out-Null
Write-ResultLine -Key 'ACTION' -Value 'create-download-task'
Write-ResultLine -Key 'TIME_OUT' -Value '0'
Write-ResultLine -Key 'RESULT' -Value 'TASK_CREATED'
}
'poll-download-progress' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name 'Read-DownloadProgress' -Action { Read-DownloadProgress -Config $config -Token $token -NodeUrl $nodeUrl } | Out-Null
Write-DownloadProgressResult
}
'download-cloud-to-node' {
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name 'Download-CloudToNode' -Action { Download-CloudToNode -Config $config -Token $token -NodeUrl $nodeUrl } | Out-Null
Write-DownloadProgressResult -ActionName 'download-cloud-to-node'
Write-ResultLine -Key 'RESULT' -Value 'DONE'
}
'poll-upgrade-progress' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name "Read-UpgradeProgress[$Ip]" -Action { Read-UpgradeProgress -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip } | Out-Null
Write-UpgradeProgressResult -Ip $Ip
}
'upgrade-ip' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
$response = Invoke-FlowStep -Name "Invoke-UpgradeRequest[$Ip]" -Action { Invoke-UpgradeRequest -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip }
if (Test-ResponseFailure -Response $response) {
$message = Get-PrimaryResponseMessage -Response $response
if (-not $message) { $message = 'Upgrade task creation failed' }
throw "Upgrade task creation failed: ip=$Ip, message=$message"
}
Write-ResultLine -Key 'ACTION' -Value 'upgrade-ip'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'TIME_OUT' -Value '0'
Write-ResultLine -Key 'RESULT' -Value 'TASK_CREATED'
Write-ResultLine -Key 'SUCCESS' -Value (Get-ResponseValue -Response $response -Candidates @('success'))
Write-ResultLine -Key 'MESSAGE' -Value (Get-PrimaryResponseMessage -Response $response)
Write-ResultLine -Key 'RAW_RESPONSE' -Value ([string]$response)
}
'start-ip' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name "Start-Application[$Ip]" -Action { Start-Application -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip } | Out-Null
Write-ResultLine -Key 'ACTION' -Value 'start-ip'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'RUN_START' -Value 'true'
Write-ResultLine -Key 'RESULT' -Value 'OK'
}
'stop-ip' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
Invoke-FlowStep -Name "Stop-Application[$Ip]" -Action { Stop-Application -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip } | Out-Null
Write-ResultLine -Key 'ACTION' -Value 'stop-ip'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'RUN_START' -Value 'false'
Write-ResultLine -Key 'RESULT' -Value 'OK'
}
'verify-ip' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
$response = Invoke-FlowStep -Name "Verify-Ip[$Ip]" -Action { Verify-Ip -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip }
Write-ResultLine -Key 'ACTION' -Value 'verify-ip'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'SUCCESS' -Value (Get-ResponseValue -Response $response -Candidates @('success'))
Write-ResultLine -Key 'MESSAGE' -Value (Get-ResponseValue -Response $response -Candidates @('message'))
Write-ResultLine -Key 'RAW_RESPONSE' -Value ([string]$response)
}
'download-log' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
$logFile = Invoke-FlowStep -Name "Download-DeployLog[$Ip]" -Action { Download-DeployLog -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip }
Write-ResultLine -Key 'ACTION' -Value 'download-log'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'LOG_FILE' -Value $logFile
}
'rollback-ip' {
Require-IpArgument -TargetIp $Ip
$token = Invoke-FlowStep -Name 'Get-Token' -Action { Get-Token -Config $config }
$nodeUrl = Invoke-FlowStep -Name 'Get-NodeUrl' -Action { Get-NodeUrl -Config $config -Token $token }
$rollbackResult = Invoke-FlowStep -Name "Invoke-Rollback[$Ip]" -Action { Invoke-Rollback -Config $config -Token $token -NodeUrl $nodeUrl -Ip $Ip -StopFirst:$StopFirst }
Write-ResultLine -Key 'ACTION' -Value 'rollback-ip'
Write-ResultLine -Key 'IP' -Value $Ip
Write-ResultLine -Key 'STOP_FIRST' -Value ($StopFirst.ToString().ToLowerInvariant())
Write-ResultLine -Key 'ROLLBACK_RESULT' -Value $rollbackResult
}
default {
throw "Unknown action: $Action"
}
}
}
if ($Help) {
Show-DeployUsage
exit 0
}
if ($MyInvocation.InvocationName -ne '.') {
try {
if ($Action) {
Invoke-PamAction -ConfigPath $ConfigPath -Action $Action -Ip $Ip -HashCode $HashCode -StopFirst:$RollbackStopFirst.IsPresent
} elseif ($RollbackIp) {
Invoke-PamManualRollback -ConfigPath $ConfigPath -Ip $RollbackIp -StopFirst:$RollbackStopFirst.IsPresent
} else {
Invoke-PamDeploy -ConfigPath $ConfigPath
}
} catch {
Write-ErrLog $_
exit 1
}
}