mirror of
https://github.com/MAKS-IT-COM/maksit-webui.git
synced 2026-08-15 09:58:09 +02:00
632 lines
22 KiB
PowerShell
632 lines
22 KiB
PowerShell
#requires -Version 7.0
|
|
#requires -PSEdition Core
|
|
|
|
<#
|
|
.SYNOPSIS
|
|
Shared helpers for HelmSelfDeploy (OCI chart upgrade/install against a live cluster).
|
|
#>
|
|
|
|
if (-not (Get-Command Get-RegistryCredentialsFromRuntime -ErrorAction SilentlyContinue)) {
|
|
$srcDir = Split-Path $PSScriptRoot -Parent
|
|
$pluginSupportModulePath = Join-Path $srcDir "Engine/PluginSupport.psm1"
|
|
if (Test-Path $pluginSupportModulePath -PathType Leaf) {
|
|
Import-Module $pluginSupportModulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
}
|
|
|
|
$scriptConfigModulePath = Join-Path (Split-Path $PSScriptRoot -Parent) 'ScriptConfig.psm1'
|
|
if (Test-Path -LiteralPath $scriptConfigModulePath -PathType Leaf) {
|
|
Import-Module $scriptConfigModulePath -Force -Global -ErrorAction Stop
|
|
}
|
|
|
|
function Get-ContainerRegistryCredentialsFromSecret {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$SecretName,
|
|
|
|
[Parameter(Mandatory = $false)]
|
|
[psobject]$SharedSettings
|
|
)
|
|
|
|
return Get-RegistryCredentialsFromRuntime -SecretName $SecretName -SharedSettings $SharedSettings
|
|
}
|
|
|
|
function Test-HelmAvailable {
|
|
return [bool](Get-Command helm -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
function Test-KubectlAvailable {
|
|
return [bool](Get-Command kubectl -ErrorAction SilentlyContinue)
|
|
}
|
|
|
|
function Confirm-HelmAndKubectlAvailable {
|
|
if (-not (Test-HelmAvailable)) {
|
|
throw "Helm is not installed or not in PATH."
|
|
}
|
|
|
|
if (-not (Test-KubectlAvailable)) {
|
|
throw "kubectl is not installed or not in PATH."
|
|
}
|
|
}
|
|
|
|
function Test-KubectlClusterReachable {
|
|
kubectl cluster-info 2>$null | Out-Null
|
|
return $LASTEXITCODE -eq 0
|
|
}
|
|
|
|
function Get-HelmUpgradeDryRunArgs {
|
|
if (Test-KubectlClusterReachable) {
|
|
return @('--dry-run=server')
|
|
}
|
|
|
|
Write-Host "Cluster not reachable via kubectl; using client-side helm --dry-run."
|
|
return @('--dry-run')
|
|
}
|
|
|
|
function Test-SecretEnvironmentValueAvailable {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Name
|
|
)
|
|
|
|
if ([string]::IsNullOrWhiteSpace($Name)) {
|
|
return $false
|
|
}
|
|
|
|
$value = [Environment]::GetEnvironmentVariable($Name)
|
|
return -not [string]::IsNullOrWhiteSpace($value)
|
|
}
|
|
|
|
function Set-K8sNamespace {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace
|
|
)
|
|
|
|
$Namespace = $Namespace.Trim()
|
|
if ([string]::IsNullOrWhiteSpace($Namespace)) {
|
|
throw "Namespace name is missing or empty."
|
|
}
|
|
|
|
$ns = kubectl get namespace $Namespace --ignore-not-found -o name 2>$null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "kubectl get namespace failed for '$Namespace' (exit code $LASTEXITCODE)."
|
|
}
|
|
|
|
if (-not $ns) {
|
|
Write-Host "Creating namespace $Namespace"
|
|
kubectl create namespace $Namespace | Out-Null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "Failed to create namespace $Namespace"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Wait-NamespacePodsReady {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[int]$TimeoutSeconds = 300,
|
|
[int]$PollIntervalSeconds = 5
|
|
)
|
|
|
|
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
|
|
while ([DateTime]::UtcNow -lt $deadline) {
|
|
$json = kubectl get pods -n $Namespace -o json 2>$null
|
|
if ($LASTEXITCODE -ne 0) {
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
continue
|
|
}
|
|
|
|
$obj = $json | ConvertFrom-Json
|
|
$pods = @($obj.items)
|
|
if ($pods.Count -eq 0) {
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
continue
|
|
}
|
|
|
|
$ready = 0
|
|
foreach ($p in $pods) {
|
|
$phase = if ($p.status) { $p.status.phase } else { "Unknown" }
|
|
$conditions = if ($p.status.conditions) { @($p.status.conditions) } else { @() }
|
|
$readyCond = $conditions | Where-Object { $_.type -eq "Ready" } | Select-Object -First 1
|
|
if ($phase -eq "Running" -and $null -ne $readyCond -and $readyCond.status -eq "True") {
|
|
$ready++
|
|
}
|
|
}
|
|
|
|
if ($ready -eq $pods.Count) {
|
|
Write-Host "All $($pods.Count) pod(s) in namespace $Namespace are ready."
|
|
return $true
|
|
}
|
|
|
|
Write-Host "Pods in $Namespace : $ready / $($pods.Count) ready"
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
}
|
|
|
|
Write-Warning "Timeout after ${TimeoutSeconds}s: not all pods in namespace $Namespace are ready."
|
|
return $false
|
|
}
|
|
|
|
function Get-DerivedOciChartRefFromChartEntry {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$Chart,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ChartVersion
|
|
)
|
|
|
|
if ($Chart.PSObject.Properties.Name -notcontains "containerRegistry" -or -not $Chart.containerRegistry) {
|
|
return $null
|
|
}
|
|
|
|
$cr = $Chart.containerRegistry
|
|
$regHost = ""
|
|
if ($cr.PSObject.Properties.Name -contains "registryUrl" -and -not [string]::IsNullOrWhiteSpace([string]$cr.registryUrl)) {
|
|
$regHost = [string]$cr.registryUrl.Trim().TrimEnd('/')
|
|
$regHost = $regHost -replace '^(https?|oci)://', ''
|
|
}
|
|
|
|
$ociChartsProject = if ($cr.PSObject.Properties.Name -contains "ociChartsProject" -and -not [string]::IsNullOrWhiteSpace([string]$cr.ociChartsProject)) {
|
|
[string]$cr.ociChartsProject.Trim().Trim('/')
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
|
|
$chartProject = if ($cr.PSObject.Properties.Name -contains "chartProject" -and -not [string]::IsNullOrWhiteSpace([string]$cr.chartProject)) {
|
|
[string]$cr.chartProject.Trim().Trim('/')
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
|
|
if ([string]::IsNullOrWhiteSpace($regHost) -or [string]::IsNullOrWhiteSpace($chartProject)) {
|
|
return $null
|
|
}
|
|
|
|
$path = if ([string]::IsNullOrWhiteSpace($ociChartsProject)) { $chartProject } else { "$ociChartsProject/$chartProject" }
|
|
return "oci://${regHost}/${path}:${ChartVersion}"
|
|
}
|
|
|
|
function Invoke-HelmOciChartRegistryLoginFromEnv {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RegistryHost,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CredentialsSecret,
|
|
[psobject]$SharedSettings = $null
|
|
)
|
|
|
|
$RegistryHost = $RegistryHost.Trim().TrimEnd('/')
|
|
$helmCreds = Get-ContainerRegistryCredentialsFromSecret -SecretName $CredentialsSecret -SharedSettings $SharedSettings
|
|
Write-Host "Logging Helm into OCI chart registry $RegistryHost"
|
|
$prevEap = $ErrorActionPreference
|
|
try {
|
|
$ErrorActionPreference = "Continue"
|
|
$helmCreds.Password | & helm registry login $RegistryHost --username $helmCreds.User --password-stdin 2>&1 | Out-Null
|
|
}
|
|
finally {
|
|
$ErrorActionPreference = $prevEap
|
|
}
|
|
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "helm registry login failed for $RegistryHost (exit code $LASTEXITCODE)."
|
|
}
|
|
}
|
|
|
|
function Set-KubernetesImagePullSecretFromEnv {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$RegistryHost,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$SecretName,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$CredentialsSecret,
|
|
[psobject]$SharedSettings = $null
|
|
)
|
|
|
|
$RegistryHost = $RegistryHost.Trim().TrimEnd('/')
|
|
$creds = Get-ContainerRegistryCredentialsFromSecret -SecretName $CredentialsSecret -SharedSettings $SharedSettings
|
|
Write-Host "Creating/updating image pull secret '$SecretName' in namespace $Namespace"
|
|
$createArgs = @(
|
|
"create", "secret", "docker-registry", $SecretName,
|
|
"--docker-server=$RegistryHost",
|
|
"--docker-username=$($creds.User)",
|
|
"--docker-password", $creds.Password,
|
|
"-n", $Namespace,
|
|
"--dry-run=client", "-o", "yaml"
|
|
)
|
|
$secretYaml = & kubectl @createArgs 2>&1
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "kubectl create secret (dry-run) failed for '$SecretName': $secretYaml"
|
|
}
|
|
|
|
$secretYaml | kubectl apply -f -
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "kubectl apply failed for docker-registry secret '$SecretName' (exit code $LASTEXITCODE)."
|
|
}
|
|
}
|
|
|
|
function Initialize-ContainerRegistryAuthForChart {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$Chart,
|
|
[string]$DefaultHelmCredentialsSecret = "",
|
|
[psobject]$SharedSettings = $null,
|
|
[switch]$DryRun
|
|
)
|
|
|
|
$reg = if ($Chart.PSObject.Properties.Name -contains "containerRegistry") { $Chart.containerRegistry } else { $null }
|
|
if ($null -eq $reg) {
|
|
return
|
|
}
|
|
|
|
$rel = if ($Chart.releaseName) { [string]$Chart.releaseName } else { "<unknown>" }
|
|
$baseUrl = if ($reg.PSObject.Properties.Name -contains "registryUrl" -and -not [string]::IsNullOrWhiteSpace([string]$reg.registryUrl)) {
|
|
[string]$reg.registryUrl.Trim().TrimEnd('/')
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
|
|
$helmUrl = if ($reg.PSObject.Properties.Name -contains "helmRegistryUrl" -and -not [string]::IsNullOrWhiteSpace([string]$reg.helmRegistryUrl)) {
|
|
[string]$reg.helmRegistryUrl.Trim().TrimEnd('/')
|
|
}
|
|
else {
|
|
$baseUrl
|
|
}
|
|
|
|
$imgUrl = if ($reg.PSObject.Properties.Name -contains "imagesRegistryUrl" -and -not [string]::IsNullOrWhiteSpace([string]$reg.imagesRegistryUrl)) {
|
|
[string]$reg.imagesRegistryUrl.Trim().TrimEnd('/')
|
|
}
|
|
else {
|
|
$baseUrl
|
|
}
|
|
|
|
$helmCredsSecret = if ($reg.PSObject.Properties.Name -contains 'helmOciCredentialsSecret' -and -not [string]::IsNullOrWhiteSpace([string]$reg.helmOciCredentialsSecret)) {
|
|
[string]$reg.helmOciCredentialsSecret
|
|
}
|
|
elseif (-not [string]::IsNullOrWhiteSpace($DefaultHelmCredentialsSecret)) {
|
|
$DefaultHelmCredentialsSecret
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
|
|
$imgCredsSecret = if ($reg.PSObject.Properties.Name -contains 'imagesCredentialsSecret' -and -not [string]::IsNullOrWhiteSpace([string]$reg.imagesCredentialsSecret)) {
|
|
[string]$reg.imagesCredentialsSecret
|
|
}
|
|
else {
|
|
""
|
|
}
|
|
|
|
$imgSecretName = if ($reg.PSObject.Properties.Name -contains "imagePullSecretName" -and -not [string]::IsNullOrWhiteSpace([string]$reg.imagePullSecretName)) {
|
|
[string]$reg.imagePullSecretName
|
|
}
|
|
else {
|
|
"cr-maks-it"
|
|
}
|
|
|
|
if (-not [string]::IsNullOrWhiteSpace($helmCredsSecret)) {
|
|
if ([string]::IsNullOrWhiteSpace($helmUrl)) {
|
|
throw "Chart '$rel': containerRegistry.registryUrl is required for Helm OCI login."
|
|
}
|
|
|
|
if ($DryRun -and -not (Test-SecretEnvironmentValueAvailable -Name $helmCredsSecret)) {
|
|
Write-Host "Dry run: skipping Helm OCI login; set env var '$helmCredsSecret' to validate remote OCI charts."
|
|
}
|
|
else {
|
|
Invoke-HelmOciChartRegistryLoginFromEnv -RegistryHost $helmUrl -CredentialsSecret $helmCredsSecret -SharedSettings $SharedSettings
|
|
}
|
|
}
|
|
|
|
if (-not $DryRun -and -not [string]::IsNullOrWhiteSpace($imgCredsSecret)) {
|
|
if ([string]::IsNullOrWhiteSpace($imgUrl)) {
|
|
throw "Chart '$rel': containerRegistry.registryUrl is required for image pull secret."
|
|
}
|
|
|
|
Set-KubernetesImagePullSecretFromEnv -Namespace $Namespace -RegistryHost $imgUrl -SecretName $imgSecretName -CredentialsSecret $imgCredsSecret -SharedSettings $SharedSettings
|
|
}
|
|
}
|
|
|
|
function Test-HelmReleaseExists {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseName
|
|
)
|
|
|
|
$releaseExists = helm list -n $Namespace -q 2>$null | Select-String -Pattern "^${ReleaseName}$" -Quiet
|
|
return [bool]$releaseExists
|
|
}
|
|
|
|
function Invoke-PostgresClusterWait {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$PostgresWait
|
|
)
|
|
|
|
if ($null -eq $PostgresWait -or $PostgresWait.enabled -ne $true) {
|
|
return
|
|
}
|
|
|
|
$pgNs = if ($PostgresWait.namespace) { [string]$PostgresWait.namespace } else { "postgresql" }
|
|
$cluster = if ($PostgresWait.clusterName) { [string]$PostgresWait.clusterName } else { "postgresql" }
|
|
$timeout = if ($null -ne $PostgresWait.timeoutSeconds) { [int]$PostgresWait.timeoutSeconds } else { 600 }
|
|
Write-Host "postgresWait: waiting for CNPG cluster '$cluster' in namespace '$pgNs' (timeout ${timeout}s)..."
|
|
kubectl wait "cluster.postgresql.cnpg.io/$cluster" -n $pgNs --for=condition=Ready --timeout="${timeout}s"
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "postgresWait failed: cluster '$cluster' in '$pgNs' did not become Ready."
|
|
}
|
|
|
|
Write-Host "postgresWait: cluster is Ready."
|
|
}
|
|
|
|
function Get-HelmSetArgsFromChartEntry {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$Chart,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ChartVersion
|
|
)
|
|
|
|
$setArg = @()
|
|
if ($Chart.PSObject.Properties.Name -notcontains 'helmSet' -or -not $Chart.helmSet) {
|
|
return $setArg
|
|
}
|
|
|
|
foreach ($prop in $Chart.helmSet.PSObject.Properties) {
|
|
$val = ([string]$prop.Value) -replace '\{chartVersion\}', $ChartVersion
|
|
$setArg += '--set'
|
|
$setArg += "$($prop.Name)=$val"
|
|
}
|
|
|
|
return $setArg
|
|
}
|
|
|
|
function Invoke-HelmRemoteChartUpgrade {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$Chart,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ChartVersion,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$DeploySettingsDir,
|
|
[string]$DefaultHelmCredentialsSecret = "",
|
|
[psobject]$SharedSettings = $null,
|
|
[switch]$DryRun
|
|
)
|
|
|
|
$releaseName = [string]$Chart.releaseName
|
|
if ([string]::IsNullOrWhiteSpace($releaseName)) {
|
|
throw "Chart entry is missing releaseName."
|
|
}
|
|
|
|
Initialize-ContainerRegistryAuthForChart -Namespace $Namespace -Chart $Chart -DefaultHelmCredentialsSecret $DefaultHelmCredentialsSecret -SharedSettings $SharedSettings -DryRun:$DryRun
|
|
|
|
$ociRef = Get-DerivedOciChartRefFromChartEntry -Chart $Chart -ChartVersion $ChartVersion
|
|
if ([string]::IsNullOrWhiteSpace($ociRef)) {
|
|
throw "Could not derive OCI chart reference for release '$releaseName'. Set chart.containerRegistry.registryUrl and chartProject."
|
|
}
|
|
|
|
$valuesArg = @()
|
|
$valuesPath = $null
|
|
if ($Chart.PSObject.Properties.Name -contains 'valuesFile' -and -not [string]::IsNullOrWhiteSpace([string]$Chart.valuesFile)) {
|
|
$deployMode = $null
|
|
if ($null -ne $SharedSettings -and $SharedSettings.PSObject.Properties.Name -contains 'deployMode') {
|
|
$deployMode = [string]$SharedSettings.deployMode
|
|
}
|
|
|
|
if ($deployMode -in @('single', 'ha')) {
|
|
$valuesPath = Resolve-DeployValuesFilePath -DeploySettingsDir $DeploySettingsDir -ValuesFile ([string]$Chart.valuesFile) -DeployMode $deployMode
|
|
}
|
|
else {
|
|
throw "HelmSelfDeploy requires deployMode 'single' or 'ha' when valuesFile is set."
|
|
}
|
|
|
|
$valuesArg = @('-f', $valuesPath)
|
|
}
|
|
|
|
$releaseExists = Test-HelmReleaseExists -Namespace $Namespace -ReleaseName $releaseName
|
|
$reuseArg = @()
|
|
if ($valuesArg.Count -gt 0) {
|
|
if ($releaseExists) {
|
|
Write-Host "Upgrading existing release '$releaseName' in namespace '$Namespace' with values file $valuesPath (chart version $ChartVersion)"
|
|
}
|
|
else {
|
|
Write-Host "Installing new release '$releaseName' in namespace '$Namespace' with values file $valuesPath (chart version $ChartVersion)"
|
|
}
|
|
}
|
|
elseif ($releaseExists) {
|
|
Write-Host "Upgrading existing release '$releaseName' in namespace '$Namespace' to chart version $ChartVersion (reuse-values)"
|
|
$reuseArg = @("--reuse-values")
|
|
}
|
|
else {
|
|
throw "Release '$releaseName' is not installed in namespace '$Namespace'. Add values.single.yaml and values.ha.yaml beside scriptSettings.json and run utils/Invoke-ReleasePackage-HA.bat or Invoke-ReleasePackage-Single.bat for first install."
|
|
}
|
|
|
|
$setArg = Get-HelmSetArgsFromChartEntry -Chart $Chart -ChartVersion $ChartVersion
|
|
$dryRunArg = if ($DryRun) { Get-HelmUpgradeDryRunArgs } else { @() }
|
|
|
|
if ($DryRun) {
|
|
if ($valuesArg.Count -gt 0) {
|
|
Write-Host "Dry run: helm upgrade --install '$releaseName' in namespace '$Namespace' with values file $valuesPath (chart version $ChartVersion)"
|
|
}
|
|
else {
|
|
Write-Host "Dry run: helm upgrade --install '$releaseName' in namespace '$Namespace' to chart version $ChartVersion"
|
|
}
|
|
}
|
|
|
|
helm upgrade --install $releaseName $ociRef -n $Namespace --create-namespace --version $ChartVersion @reuseArg @valuesArg @setArg @dryRunArg
|
|
if ($LASTEXITCODE -ne 0) {
|
|
$action = if ($DryRun) { 'Helm dry-run upgrade/install' } else { 'Helm upgrade/install' }
|
|
throw "$action failed for release '$releaseName' (exit code $LASTEXITCODE)."
|
|
}
|
|
}
|
|
|
|
function Remove-BgpServices {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ScriptDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseName,
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyCollection()]
|
|
[object[]]$BgpServices,
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$DefaultPort
|
|
)
|
|
|
|
foreach ($svc in $BgpServices) {
|
|
$template = $svc.template
|
|
$ip = $svc.ip
|
|
$path = Join-Path $ScriptDir $template
|
|
if (-not (Test-Path $path)) {
|
|
Write-Host "BGP template not found (skip delete): $path"
|
|
continue
|
|
}
|
|
|
|
Write-Host "Removing BGP service $template (IP: $ip)"
|
|
$content = Get-Content $path -Raw
|
|
$portStr = if ($null -ne $svc.port) { [string]$svc.port } else { [string]$DefaultPort }
|
|
$content = $content -replace "__RELEASE_NAME__", $ReleaseName -replace "__NAMESPACE__", $Namespace -replace "__BGP_IP__", $ip -replace "__PORT__", $portStr
|
|
$content | kubectl delete -f - --ignore-not-found 2>$null
|
|
}
|
|
}
|
|
|
|
function Invoke-HelmReleaseUninstall {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[object]$Chart
|
|
)
|
|
|
|
$releaseName = [string]$Chart.releaseName
|
|
if ([string]::IsNullOrWhiteSpace($releaseName)) {
|
|
throw "Chart entry is missing releaseName."
|
|
}
|
|
|
|
if (-not (Test-HelmReleaseExists -Namespace $Namespace -ReleaseName $releaseName)) {
|
|
Write-Host "Release '$releaseName' not found in namespace '$Namespace', nothing to uninstall."
|
|
return
|
|
}
|
|
|
|
Write-Host "Uninstalling Helm release '$releaseName' from namespace '$Namespace' ..."
|
|
$prevEap = $ErrorActionPreference
|
|
try {
|
|
$ErrorActionPreference = "Continue"
|
|
helm uninstall $releaseName -n $Namespace 2>&1 | Out-Host
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "helm uninstall failed for release '$releaseName' (exit code $LASTEXITCODE)."
|
|
}
|
|
}
|
|
finally {
|
|
$ErrorActionPreference = $prevEap
|
|
}
|
|
}
|
|
|
|
function Wait-TcpConnectivity {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ComputerName,
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$Port,
|
|
[int]$TimeoutSeconds = 300,
|
|
[int]$PollIntervalSeconds = 3
|
|
)
|
|
|
|
$deadline = [DateTime]::UtcNow.AddSeconds($TimeoutSeconds)
|
|
while ([DateTime]::UtcNow -lt $deadline) {
|
|
$result = Test-NetConnection -ComputerName $ComputerName -Port $Port -WarningAction SilentlyContinue
|
|
if ($result.TcpTestSucceeded) {
|
|
Write-Host "Connectivity OK (${ComputerName}:${Port})"
|
|
return $true
|
|
}
|
|
|
|
Start-Sleep -Seconds $PollIntervalSeconds
|
|
}
|
|
|
|
Write-Warning "Timeout after ${TimeoutSeconds}s: no connectivity to ${ComputerName}:${Port}"
|
|
return $false
|
|
}
|
|
|
|
function Set-BgpServices {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$Namespace,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ScriptDir,
|
|
[Parameter(Mandatory = $true)]
|
|
[string]$ReleaseName,
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyCollection()]
|
|
[object[]]$BgpServices,
|
|
[Parameter(Mandatory = $true)]
|
|
[int]$DefaultPort
|
|
)
|
|
|
|
foreach ($svc in $BgpServices) {
|
|
$template = $svc.template
|
|
$ip = $svc.ip
|
|
$path = Join-Path $ScriptDir $template
|
|
if (-not (Test-Path $path)) {
|
|
Write-Warning "BGP template not found: $path"
|
|
continue
|
|
}
|
|
|
|
Write-Host "Applying BGP service $template (IP: $ip)"
|
|
$content = Get-Content $path -Raw
|
|
$portStr = if ($null -ne $svc.port) { [string]$svc.port } else { [string]$DefaultPort }
|
|
$content = $content -replace "__RELEASE_NAME__", $ReleaseName -replace "__NAMESPACE__", $Namespace -replace "__BGP_IP__", $ip -replace "__PORT__", $portStr
|
|
$content | kubectl apply -f -
|
|
if ($LASTEXITCODE -ne 0) {
|
|
throw "kubectl apply failed for BGP template $template"
|
|
}
|
|
}
|
|
}
|
|
|
|
function Test-BgpConnectivity {
|
|
param(
|
|
[Parameter(Mandatory = $true)]
|
|
[AllowEmptyCollection()]
|
|
[object[]]$BgpServices,
|
|
[int]$TimeoutSeconds = 300,
|
|
[int]$PollIntervalSeconds = 3
|
|
)
|
|
|
|
$toTest = @($BgpServices | Where-Object { $null -ne $_.port })
|
|
if ($toTest.Count -eq 0) {
|
|
Write-Warning "No BGP services have port set; skipping connectivity test."
|
|
return
|
|
}
|
|
|
|
foreach ($svc in $toTest) {
|
|
$h = $svc.ip
|
|
$p = [int]$svc.port
|
|
Write-Host "Waiting for connectivity to ${h}:${p}..."
|
|
Wait-TcpConnectivity -ComputerName $h -Port $p -TimeoutSeconds $TimeoutSeconds -PollIntervalSeconds $PollIntervalSeconds | Out-Null
|
|
}
|
|
}
|
|
|
|
Export-ModuleMember -Function `
|
|
Confirm-HelmAndKubectlAvailable, `
|
|
Set-K8sNamespace, `
|
|
Wait-NamespacePodsReady, `
|
|
Invoke-PostgresClusterWait, `
|
|
Invoke-HelmRemoteChartUpgrade, `
|
|
Invoke-HelmReleaseUninstall, `
|
|
Set-BgpServices, `
|
|
Remove-BgpServices, `
|
|
Test-BgpConnectivity
|