dark 039a3e1bdc 支持云下载继承版本参数并调整回滚请求格式
- 新增 PARENT_VERSION_NUMBER 可选配置,默认空值不传
- create-download-task 非空时透传 parentVersionNumber
- 支持 LLM/规则从自然语言和 key=value 中抽取继承版本参数
- 将 rollback 接口参数从表单 body 改为 URL query 拼接
- 同步 README、打包说明和 Skill 文档
- 增加 MCP 参数透传、配置写入和 rollback query 调用测试
2026-06-05 10:33:53 +08:00

1545 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 }
'PARENT_VERSION_NUMBER' { $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'
PARENT_VERSION_NUMBER = ''
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'
})
if ($Config.PARENT_VERSION_NUMBER) {
$query += '&' + (Join-RequestPairs ([ordered]@{
parentVersionNumber = $Config.PARENT_VERSION_NUMBER
}))
}
[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 {
$query = 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?$query" -Token $Token -Headers @{
'Target-Node' = $NodeUrl
}
$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
}
}