dark 143cd76c6a 1、新增action给agent使用
2、新增SKILL描述
2026-05-20 16:58:01 +08:00

1147 lines
41 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.
'@ | 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 = ''
}
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-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-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 }
}
}
} 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'
}
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 = ''
}
for ($attempt = 0; $attempt -lt 60; $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}/60)" -f ($attempt + 1))
}
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 2
}
throw 'Node download timed out.'
}
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 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 = $Config.TIMEOUT
})
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}.log" -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) {
Get-Content -LiteralPath $logFile -Tail 5 | Set-Content -LiteralPath "$logFile.summary"
} else {
'Log content 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 ((Get-ResponseValue -Response $upgrade -Candidates @('success')) -ne 'true') {
$message = Get-ResponseValue -Response $upgrade -Candidates @('message')
if (-not $message) { $message = 'Upgrade 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 "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 'Wait-DownloadProgress' -Action { Wait-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'
}
'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 }
Write-ResultLine -Key 'ACTION' -Value 'upgrade-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)
}
'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
}
}