764 lines
24 KiB
PowerShell
764 lines
24 KiB
PowerShell
param(
|
||
[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.txt'),
|
||
[switch]$Help
|
||
)
|
||
|
||
# PAM 部署主脚本(PowerShell 实现)。
|
||
Set-StrictMode -Version Latest
|
||
$ErrorActionPreference = 'Stop'
|
||
|
||
function Show-DeployUsage {
|
||
@'
|
||
Usage:
|
||
powershell -File .\deploy.ps1 [-ConfigPath .\config.txt]
|
||
|
||
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" }
|
||
|
||
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
|
||
})
|
||
$progressUrl = "$($Config.HOME_BASE_URL)/node_proxy/$($Config.AIRPORT_CODE)/api/mcp/version/upgrade/download-cloud/progress?$query"
|
||
|
||
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')
|
||
if ($status -eq 'completed' -or $successFlag -eq 'true') {
|
||
return
|
||
}
|
||
|
||
$message = Get-ResponseValue -Response $response -Candidates @('message')
|
||
if ($message -and $message -match '(?i)fail|error') {
|
||
throw "Node download failed: $message"
|
||
}
|
||
|
||
Start-Sleep -Seconds 2
|
||
}
|
||
|
||
throw 'Node download timed out.'
|
||
}
|
||
|
||
function Download-CloudToNode {
|
||
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 = $Config.TIMEOUT
|
||
})
|
||
|
||
[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
|
||
})
|
||
|
||
Wait-DownloadProgress -Config $Config -Token $Token -NodeUrl $NodeUrl
|
||
}
|
||
|
||
function Invoke-UpgradeRequest {
|
||
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
|
||
|
||
$body = 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" -Token $Token -Headers @{
|
||
'Target-Node' = $NodeUrl
|
||
} -Body $body -ContentType 'application/x-www-form-urlencoded'
|
||
}
|
||
|
||
function Start-Application {
|
||
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
|
||
|
||
$body = 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" -Token $Token -Headers @{
|
||
'Target-Node' = $NodeUrl
|
||
} -Body $body -ContentType 'application/x-www-form-urlencoded')
|
||
}
|
||
|
||
function Stop-Application {
|
||
param($Config, [string]$Token, [string]$NodeUrl, [string]$Ip)
|
||
|
||
$body = 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" -Token $Token -Headers @{
|
||
'Target-Node' = $NodeUrl
|
||
} -Body $body -ContentType 'application/x-www-form-urlencoded')
|
||
}
|
||
|
||
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-UpgradeRequest -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
} catch {
|
||
$logFile = 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') {
|
||
$rollback = Invoke-Rollback -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip -StopFirst:$false
|
||
$logFile = Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
$message = Get-ResponseValue -Response $upgrade -Candidates @('message')
|
||
if (-not $message) { $message = 'Upgrade failed' }
|
||
return [pscustomobject]@{
|
||
Ip = $Ip
|
||
Status = 'FAILED'
|
||
Stage = 'UPGRADE'
|
||
Message = $message
|
||
Rollback = $rollback
|
||
LogFile = $logFile
|
||
}
|
||
}
|
||
|
||
try {
|
||
Start-Application -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
} catch {
|
||
$rollback = Invoke-Rollback -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip -StopFirst:$true
|
||
$logFile = 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 = Verify-Ip -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
} catch {
|
||
$rollback = Invoke-Rollback -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip -StopFirst:$true
|
||
$logFile = 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 = Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
return [pscustomobject]@{
|
||
Ip = $Ip
|
||
Status = 'SUCCESS'
|
||
Stage = '-'
|
||
Message = '-'
|
||
Rollback = '-'
|
||
LogFile = $logFile
|
||
}
|
||
}
|
||
|
||
$rollback = Invoke-Rollback -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip -StopFirst:$true
|
||
$logFile = Download-DeployLog -Config $Config -Token $Token -NodeUrl $NodeUrl -Ip $Ip
|
||
$message = Get-ResponseValue -Response $verify -Candidates @('message')
|
||
if (-not $message) { $message = 'Health check failed' }
|
||
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)
|
||
|
||
$config = Get-PamConfig -Path $ConfigPath
|
||
Test-ZipFile -Config $config
|
||
|
||
Write-Info "Deploy start: airport=$($config.AIRPORT_CODE), version=$($config.VERSION_NUMBER), module=$($config.APP_NAME)/$($config.MODULE_NAME)"
|
||
|
||
$token = Get-Token -Config $config
|
||
New-VersionRecord -Config $config -Token $token
|
||
$hashCode = Upload-Package -Config $config -Token $token
|
||
Publish-Version -Config $config -Token $token -HashCode $hashCode
|
||
$nodeUrl = Get-NodeUrl -Config $config -Token $token
|
||
$ips = Get-OnlineIps -Config $config -Token $token -NodeUrl $nodeUrl
|
||
Download-CloudToNode -Config $config -Token $token -NodeUrl $nodeUrl
|
||
|
||
$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
|
||
}
|
||
|
||
if ($Help) {
|
||
Show-DeployUsage
|
||
exit 0
|
||
}
|
||
|
||
if ($MyInvocation.InvocationName -ne '.') {
|
||
try {
|
||
Invoke-PamDeploy -ConfigPath $ConfigPath
|
||
} catch {
|
||
Write-ErrLog $_
|
||
exit 1
|
||
}
|
||
}
|