From 6dc09bab81e59439ff76cd844a901985c5128c20 Mon Sep 17 00:00:00 2001 From: Paul Harper Date: Fri, 30 Oct 2026 06:07:16 -0700 Subject: [PATCH 1/5] Pin the plain agent version in the version file The version file used to hold a full image tag (1.2.9-ubuntu-26.04 or 1.2.9-windows-ltsc2022), so a user rolling back had to know the tag suffix for their container mode, and a bare version on Linux silently selected the legacy numeric tag. The installers now write the plain version and the management scripts append the suffix for the detected container mode when starting the container. Legacy full tags in the version file are still accepted and rewritten to the plain version on the next start or upgrade. The -WindowsVersion override is now persisted as $WINDOWS_VERSION in settings.ps1 instead of in the pinned tag; a variant pinned by a legacy Windows tag is migrated there. Co-Authored-By: Claude Fable 5.1 --- README.md | 8 +- install.ps1 | 13 +++- install.sh | 2 +- proxy-agent-manager.ps1 | 165 +++++++++++++++++++--------------------- proxy-agent-manager.sh | 36 ++++++--- 5 files changed, 117 insertions(+), 107 deletions(-) diff --git a/README.md b/README.md index f02f740..36e29cb 100644 --- a/README.md +++ b/README.md @@ -62,9 +62,9 @@ To install into a custom directory: $env:RUNTIME = 'docker'; $env:TOKEN = 'YOUR_AGENT_TOKEN'; & .\install.ps1 -InstallDir C:\path\to\dir ``` -The installer detects the Docker container mode. Linux-container mode, including Windows 10/11 with Docker Desktop and WSL2, uses the explicit Ubuntu image tag `-ubuntu-26.04`. Existing Linux installations that pin the legacy numeric tag `` continue to work and are upgraded using the explicit Ubuntu tag. Windows-container mode detects the Windows Server LTSC version and selects the matching image tag. To override detection for a host known to support a specific Windows image, pass `-WindowsVersion ltsc2019`, `-WindowsVersion ltsc2022`, or `-WindowsVersion ltsc2025`. +The installer pins the plain agent version (for example `1.2.9`) in the `version` file, and the management script appends the image tag suffix for the Docker container mode when it starts the container. Linux-container mode, including Windows 10/11 with Docker Desktop and WSL2, uses the Ubuntu image tag `-ubuntu-26.04`. Windows-container mode detects the Windows Server LTSC version and selects the matching `-windows-` image tag. Existing installations whose `version` file contains a full image tag keep working; the file is rewritten to the plain version on the next `start` or `upgrade`. To override detection for a host known to support a specific Windows image, pass `-WindowsVersion ltsc2019`, `-WindowsVersion ltsc2022`, or `-WindowsVersion ltsc2025`. -Windows Server hosts use an exact LTSC mapping: Server 2019 selects `ltsc2019`, Server 2022 selects `ltsc2022`, and Server 2025 selects `ltsc2025`. The `-WindowsVersion` option is an operator-controlled override and does not validate host/image compatibility; confirm the supported Windows container version and required isolation mode before using it. +Windows Server hosts use an exact LTSC mapping: Server 2019 selects `ltsc2019`, Server 2022 selects `ltsc2022`, and Server 2025 selects `ltsc2025`. The `-WindowsVersion` option is an operator-controlled override and does not validate host/image compatibility; confirm the supported Windows container version and required isolation mode before using it. The override is saved as `$WINDOWS_VERSION` in `settings.ps1`, so `upgrade` keeps using it. Linux releases continue to use `proxy-agent:` for backward compatibility and are also published as `proxy-agent:-ubuntu-26.04`. Both tags reference the same Linux multi-architecture image. Windows releases use the explicit `proxy-agent:-windows-ltsc2019`, `proxy-agent:-windows-ltsc2022`, or `proxy-agent:-windows-ltsc2025` tags. @@ -84,7 +84,7 @@ $HOME/fivetran-proxy-agent/ ├── config/ │ └── config.json --> Agent configuration (permissions: 600) ├── logs/ --> Agent and manager logs -└── version --> Pinned agent image tag +└── version --> Pinned agent version (for example 1.2.9) # Windows %USERPROFILE%\fivetran-proxy-agent\ @@ -92,7 +92,7 @@ $HOME/fivetran-proxy-agent/ ├── config\ │ └── config.json --> Agent configuration (owner read/write; SYSTEM and Administrators read) ├── logs\ --> Agent and manager logs -└── version --> Pinned agent image tag +└── version --> Pinned agent version (for example 1.2.9) ``` ## Managing the agent diff --git a/install.ps1 b/install.ps1 index 1b55549..cf37c8d 100644 --- a/install.ps1 +++ b/install.ps1 @@ -240,7 +240,7 @@ function Get-LatestVersion { exit 1 } - return ($versions | Sort-Object { [version]($_ -replace $releasePattern, '') } | Select-Object -Last 1) + return ($versions | ForEach-Object { $_ -replace $releasePattern, '' } | Sort-Object { [version]$_ } | Select-Object -Last 1) } function Get-LatestLinuxVersion { @@ -259,7 +259,7 @@ function Get-LatestLinuxVersion { exit 1 } - return ($versions | Sort-Object { [version]($_ -replace $releasePattern, '') } | Select-Object -Last 1) + return ($versions | ForEach-Object { $_ -replace $releasePattern, '' } | Sort-Object { [version]$_ } | Select-Object -Last 1) } # ── Entry point ────────────────────────────────────────────────────────────── @@ -373,8 +373,13 @@ try { $null = New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir 'config') $null = New-Item -ItemType Directory -Force -Path (Join-Path $InstallDir 'logs') - # Persist chosen container memory for proxy-agent-manager.ps1 to read - Set-Content -LiteralPath (Join-Path $InstallDir $SETTINGS_FILE) -Value "`$MEMORY_ALLOCATION_MB = $memoryMB" -Encoding ascii + # Persist settings for proxy-agent-manager.ps1 to read: the container memory + # and, when given, the Windows image variant override. + $settings = @("`$MEMORY_ALLOCATION_MB = $memoryMB") + if ($WindowsVersion) { + $settings += "`$WINDOWS_VERSION = '$WindowsVersion'" + } + Set-Content -LiteralPath (Join-Path $InstallDir $SETTINGS_FILE) -Value $settings -Encoding ascii # Download management script from public repo (temp file → move for atomicity) Write-Host "Downloading management script..." diff --git a/install.sh b/install.sh index 50c2c3a..4322ea7 100755 --- a/install.sh +++ b/install.sh @@ -229,7 +229,7 @@ get_latest_version() { local version version=$(echo "$tags_json" \ | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+-ubuntu-26\.04"' \ - | tr -d '"' \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -V \ | tail -1 \ || true) diff --git a/proxy-agent-manager.ps1 b/proxy-agent-manager.ps1 index 6eb00a8..ab035d7 100644 --- a/proxy-agent-manager.ps1 +++ b/proxy-agent-manager.ps1 @@ -15,12 +15,17 @@ $VERSION_FILE = Join-Path $BASE_DIR 'version' $LOGFILE = Join-Path $BASE_DIR 'logs\proxy-agent-manager.log' $CONTAINER_LOG_DIR = '/app/logs' $SETTINGS_FILE = Join-Path $BASE_DIR 'settings.ps1' +# Appended to the plain agent version to form the Linux image tag. +$LINUX_IMAGE_TAG_SUFFIX = '-ubuntu-26.04' +# settings.ps1 may set $MEMORY_ALLOCATION_MB and, for Windows containers, +# $WINDOWS_VERSION (an LTSC variant that overrides host detection). if (Test-Path -LiteralPath $SETTINGS_FILE) { . $SETTINGS_FILE } # Unset or empty only, so 0 is rejected like any other invalid setting. if ("$MEMORY_ALLOCATION_MB" -eq '') { $MEMORY_ALLOCATION_MB = 5120 } +if (-not $WINDOWS_VERSION) { $WINDOWS_VERSION = $null } # ── Startup validation ─────────────────────────────────────────────────────── @@ -55,8 +60,8 @@ if (-not (Test-Path $VERSION_FILE)) { exit 1 } -$CURRENT_VERSION = (Get-Content $VERSION_FILE -Raw).Trim() -if (-not $CURRENT_VERSION) { +$PINNED_VERSION = (Get-Content $VERSION_FILE -Raw).Trim() +if (-not $PINNED_VERSION) { Write-Host "ERROR: $VERSION_FILE is empty." -ForegroundColor Red exit 1 } @@ -105,65 +110,68 @@ function Get-WindowsLtscVersion { throw "Unsupported Windows Server build '$($operatingSystem.BuildNumber)' ('$($operatingSystem.Caption)')." } -function Get-ImageTagType { +# Returns the plain agent version for a version file entry. Accepts a plain +# version (1.2.9) or a legacy image tag (1.2.9-ubuntu-26.04, 1.2.9-windows-ltsc2022). +function Get-ReleaseVersion { param([Parameter(Mandatory)][string]$Tag) - if ($Tag -match '^\d+\.\d+\.\d+-windows-(?:ltsc2019|ltsc2022|ltsc2025)$') { - return 'windows' - } - if ($Tag -match '^\d+\.\d+\.\d+-ubuntu-26\.04$') { - return 'linux' - } - if ($Tag -match '^\d+\.\d+\.\d+$') { - return 'legacy-linux' + if ($Tag -match '^(?\d+\.\d+\.\d+)(?:-ubuntu-26\.04|-windows-(?:ltsc2019|ltsc2022|ltsc2025))?$') { + return $Matches.version } - throw "Invalid proxy-agent image tag '$Tag'." + throw "Invalid proxy agent version '$Tag' in $VERSION_FILE. Expected ..." } -function Get-ImageTagVariant { - param([Parameter(Mandatory)][string]$Tag) +# Returns the LTSC variant to use for Windows containers, or $null for Linux +# containers. A variant pinned by a legacy Windows image tag in the version file +# wins and is saved to settings.ps1, then the $WINDOWS_VERSION setting, then the +# variant detected from the host. +function Resolve-WindowsVersion { + param([Parameter(Mandatory)][ValidateSet('linux', 'windows')][string]$ContainerOsType) - if ($Tag -match '^\d+\.\d+\.\d+-windows-(?ltsc2019|ltsc2022|ltsc2025)$') { - return $Matches.variant + $pinnedWindowsVariant = $null + if ($PINNED_VERSION -match '-windows-(?ltsc2019|ltsc2022|ltsc2025)$') { + $pinnedWindowsVariant = $Matches.variant } - - throw "Invalid Windows proxy-agent image tag '$Tag'. Expected -windows-ltsc2019, -windows-ltsc2022, or -windows-ltsc2025." -} - -function Resolve-ImageTag { - param( - [Parameter(Mandatory)][string]$Tag, - [Parameter(Mandatory)][ValidateSet('linux', 'windows')][string]$ContainerOsType - ) - - $tagType = Get-ImageTagType -Tag $Tag - - if ($Tag -match '^\d+\.\d+\.\d+$') { - if ($ContainerOsType -eq 'windows') { - return "$Tag-windows-$(Get-WindowsLtscVersion)" + if ($ContainerOsType -ne 'windows') { + if ($pinnedWindowsVariant) { + throw "Windows proxy-agent image tags require Docker to run Windows containers." } - return $Tag + return $null } - - if ($tagType -eq 'windows' -and $ContainerOsType -ne 'windows') { - throw "Windows proxy-agent image tags require Docker to run Windows containers." + if ($pinnedWindowsVariant) { + $legacyVariant = $pinnedWindowsVariant + if ($WINDOWS_VERSION -ne $legacyVariant) { + Add-Content -Path $SETTINGS_FILE -Value "`$WINDOWS_VERSION = '$legacyVariant'" -Encoding ascii + $script:WINDOWS_VERSION = $legacyVariant + } + return $legacyVariant } - if ($tagType -eq 'linux' -and $ContainerOsType -ne 'linux') { - throw "Linux proxy-agent image tags require Docker to run Linux containers." + if ($WINDOWS_VERSION) { + return $WINDOWS_VERSION } - - return $Tag + return Get-WindowsLtscVersion } -function Get-ReleaseVersion { - param([Parameter(Mandatory)][string]$Tag) +function Get-ImageTag { + param( + [Parameter(Mandatory)][string]$Version, + [Parameter(Mandatory)][ValidateSet('linux', 'windows')][string]$ContainerOsType, + [string]$WindowsVersion + ) - if ($Tag -match '^(?\d+\.\d+\.\d+)(?:(?:-ubuntu-26\.04)|(?:-windows-(?:ltsc2019|ltsc2022|ltsc2025)))?$') { - return $Matches.version + if ($ContainerOsType -eq 'windows') { + return "$Version-windows-$WindowsVersion" } + return "$Version$LINUX_IMAGE_TAG_SUFFIX" +} - throw "Invalid proxy-agent image tag '$Tag'." +function Set-PinnedVersion { + param([Parameter(Mandatory)][string]$Version) + if ($Version -ne $PINNED_VERSION) { + Set-Content -Path $VERSION_FILE -Value $Version -Encoding ascii + $script:PINNED_VERSION = $Version + } } function Get-LatestVersion { @@ -204,15 +212,12 @@ function Get-LatestVersion { exit 1 } - $latest = $versions | Sort-Object { [version]($_ -replace $releasePattern, '') } | Select-Object -Last 1 - return $latest + return ($versions | ForEach-Object { $_ -replace $releasePattern, '' } | Sort-Object { [version]$_ } | Select-Object -Last 1) } function Compare-SemVer { param([string]$A, [string]$B) - $aVer = [version](Get-ReleaseVersion -Tag $A) - $bVer = [version](Get-ReleaseVersion -Tag $B) - return $aVer.CompareTo($bVer) + return ([version]$A).CompareTo([version]$B) } # Only the commands that start a container check the setting, so stop, status @@ -235,10 +240,10 @@ function Stop-ProxyAgent { function Start-ProxyAgent { param( - [Parameter(Mandatory)][string]$Version, + [Parameter(Mandatory)][string]$ImageTag, [Parameter(Mandatory)][ValidateSet('linux', 'windows')][string]$ContainerOsType ) - Write-Log "Starting $CONTAINER_NAME (version: $Version)..." + Write-Log "Starting $CONTAINER_NAME (image tag: $ImageTag)..." Stop-ProxyAgent @@ -283,7 +288,7 @@ function Start-ProxyAgent { '--health-timeout', '15s', '-v', "${configDirectoryMount}:C:/config:ro", '-v', "${logsMount}:$containerLogDir", - "${IMAGE}:${Version}", + "${IMAGE}:${ImageTag}", '-i', $containerConfigPath ) } else { @@ -296,7 +301,7 @@ function Start-ProxyAgent { '--health-timeout', '3s', '-v', "${configFileMount}:/config/config.json:ro", '-v', "${logsMount}:$containerLogDir", - "${IMAGE}:${Version}", + "${IMAGE}:${ImageTag}", '-i', $containerConfigPath ) } @@ -348,55 +353,39 @@ function Start-ProxyAgent { function Start-CurrentProxyAgent { $containerOsType = Get-ContainerOsType - $resolvedVersion = Resolve-ImageTag -Tag $CURRENT_VERSION -ContainerOsType $containerOsType + $windowsVersion = Resolve-WindowsVersion -ContainerOsType $containerOsType + $imageTag = Get-ImageTag -Version $CURRENT_VERSION -ContainerOsType $containerOsType -WindowsVersion $windowsVersion + Set-PinnedVersion $CURRENT_VERSION - if (-not (Start-ProxyAgent -Version $resolvedVersion -ContainerOsType $containerOsType)) { + if (-not (Start-ProxyAgent -ImageTag $imageTag -ContainerOsType $containerOsType)) { return $false } - if ($resolvedVersion -ne $CURRENT_VERSION) { - Set-Content -Path $VERSION_FILE -Value $resolvedVersion -Encoding ascii - } - return $true } function Invoke-Upgrade { Write-Host "Checking for latest version..." $containerOsType = Get-ContainerOsType - $currentVersion = Resolve-ImageTag -Tag $CURRENT_VERSION -ContainerOsType $containerOsType - if ($containerOsType -eq 'windows') { - $windowsVersion = Get-ImageTagVariant -Tag $currentVersion - $latestVersion = Get-LatestVersion -ContainerOsType windows -WindowsVersion $windowsVersion - } else { - $tagType = Get-ImageTagType -Tag $currentVersion - if ($tagType -eq 'windows') { - throw "Windows proxy-agent image tags require Docker to run Windows containers." - } - $latestVersion = Get-LatestVersion -ContainerOsType linux - } - - if ((Compare-SemVer $latestVersion $currentVersion) -eq 0) { - if ($currentVersion -ne $CURRENT_VERSION) { - Write-Log "Switching image tag from $CURRENT_VERSION to $currentVersion..." - if (-not (Start-ProxyAgent -Version $currentVersion -ContainerOsType $containerOsType)) { - exit 1 - } - Set-Content -Path $VERSION_FILE -Value $currentVersion -Encoding ascii - } + $windowsVersion = Resolve-WindowsVersion -ContainerOsType $containerOsType + $latestVersion = Get-LatestVersion -ContainerOsType $containerOsType -WindowsVersion $windowsVersion - Write-Host "Already running the latest version ($currentVersion)." + if ((Compare-SemVer $latestVersion $CURRENT_VERSION) -eq 0) { + Set-PinnedVersion $CURRENT_VERSION + Write-Host "Already running the latest version ($CURRENT_VERSION)." exit 0 } - Write-Log "Upgrading from $currentVersion to $latestVersion..." - Set-Content -Path $VERSION_FILE -Value $latestVersion -Encoding ascii + Write-Log "Upgrading from $CURRENT_VERSION to $latestVersion..." + Set-PinnedVersion $latestVersion - if (-not (Start-ProxyAgent -Version $latestVersion -ContainerOsType $containerOsType)) { - Write-Log "Upgrade failed, rolling back to $currentVersion..." - Set-Content -Path $VERSION_FILE -Value $currentVersion -Encoding ascii - if (-not (Start-ProxyAgent -Version $currentVersion -ContainerOsType $containerOsType)) { - Write-Log "Rollback to $currentVersion failed." + $latestTag = Get-ImageTag -Version $latestVersion -ContainerOsType $containerOsType -WindowsVersion $windowsVersion + if (-not (Start-ProxyAgent -ImageTag $latestTag -ContainerOsType $containerOsType)) { + Write-Log "Upgrade failed, rolling back to $CURRENT_VERSION..." + Set-PinnedVersion $CURRENT_VERSION + $currentTag = Get-ImageTag -Version $CURRENT_VERSION -ContainerOsType $containerOsType -WindowsVersion $windowsVersion + if (-not (Start-ProxyAgent -ImageTag $currentTag -ContainerOsType $containerOsType)) { + Write-Log "Rollback to $CURRENT_VERSION failed." } exit 1 } @@ -404,6 +393,8 @@ function Invoke-Upgrade { # ── Commands ───────────────────────────────────────────────────────────────── +$CURRENT_VERSION = Get-ReleaseVersion -Tag $PINNED_VERSION + switch ($Command) { 'start' { Assert-MemoryAllocation diff --git a/proxy-agent-manager.sh b/proxy-agent-manager.sh index 75ecbb7..058d268 100755 --- a/proxy-agent-manager.sh +++ b/proxy-agent-manager.sh @@ -11,6 +11,8 @@ BASE_DIR="$(cd "$(dirname "$0")" && pwd)" # PROXY_AGENT_IMAGE and PROXY_AGENT_REGISTRY_TAGS_URL are overridable so the # test suite can run against a stub image and registry. IMAGE="${PROXY_AGENT_IMAGE:-us-docker.pkg.dev/prod-eng-fivetran-public-repos/public-docker-us/proxy-agent}" +# Appended to the plain agent version to form the Linux image tag. +IMAGE_TAG_SUFFIX="-ubuntu-26.04" CONFIG_FILE="$BASE_DIR/config/config.json" VERSION_FILE="$BASE_DIR/version" SETTINGS_FILE="$BASE_DIR/settings.sh" @@ -39,8 +41,8 @@ if [ ! -f "$VERSION_FILE" ]; then exit 1 fi # Also strips the UTF-8 byte order mark an editor on Windows may add. -CURRENT_VERSION=$(LC_ALL=C tr -d '[:space:]\357\273\277' < "$VERSION_FILE") -if [ -z "$CURRENT_VERSION" ]; then +PINNED_VERSION=$(LC_ALL=C tr -d '[:space:]\357\273\277' < "$VERSION_FILE") +if [ -z "$PINNED_VERSION" ]; then echo "ERROR: $VERSION_FILE is empty." exit 1 fi @@ -82,12 +84,14 @@ get_latest_version() { } echo "$tags_json" \ | grep -oE '"[0-9]+\.[0-9]+\.[0-9]+-ubuntu-26\.04"' \ - | tr -d '"' \ + | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' \ | sort -V \ | tail -1 \ || true } +# Prints the plain agent version for a version file entry. Accepts a plain +# version (1.2.9) or a legacy image tag (1.2.9-ubuntu-26.04). get_release_version() { local tag="$1" if [[ "$tag" =~ ^([0-9]+\.[0-9]+\.[0-9]+)(-ubuntu-26\.04)?$ ]]; then @@ -95,10 +99,18 @@ get_release_version() { return 0 fi - echo "ERROR: Invalid Linux proxy-agent image tag '$tag'. Expected or -ubuntu-26.04." >&2 + echo "ERROR: Invalid proxy agent version '$tag' in $VERSION_FILE. Expected ..." >&2 return 1 } +image_tag() { + echo "${IMAGE}:$1${IMAGE_TAG_SUFFIX}" +} + +pin_version() { + echo "$1" > "$VERSION_FILE" +} + upgrade_agent() { echo "Checking for latest version..." local latest_version @@ -107,18 +119,16 @@ upgrade_agent() { echo "ERROR: Unable to determine latest version" >&2 exit 1 fi - local current_release latest_release - current_release=$(get_release_version "$CURRENT_VERSION") || exit 1 - latest_release=$(get_release_version "$latest_version") || exit 1 - if [ "$latest_release" = "$current_release" ]; then + if [ "$latest_version" = "$CURRENT_VERSION" ]; then + pin_version "$CURRENT_VERSION" echo "Already running the latest version ($CURRENT_VERSION)." exit 0 fi log "Upgrading from $CURRENT_VERSION to $latest_version..." - echo "$latest_version" > "$VERSION_FILE" + pin_version "$latest_version" if ! start_agent "$latest_version"; then log "Upgrade failed, rolling back to $CURRENT_VERSION..." - echo "$CURRENT_VERSION" > "$VERSION_FILE" + pin_version "$CURRENT_VERSION" if ! start_agent "$CURRENT_VERSION"; then log "Rollback to $CURRENT_VERSION failed." fi @@ -159,7 +169,7 @@ start_agent() { --health-start-period 30s \ -v "$BASE_DIR/config/config.json:/config/config.json:ro" \ -v "$BASE_DIR/logs:$CONTAINER_LOG_DIR" \ - "${IMAGE}:${version}" \ + "$(image_tag "$version")" \ -i /config/config.json local timeout=60 @@ -191,9 +201,12 @@ start_agent() { # ── Commands ───────────────────────────────────────────────────────────────── +CURRENT_VERSION=$(get_release_version "$PINNED_VERSION") || exit 1 + case "${1:-}" in start) check_memory_allocation + pin_version "$CURRENT_VERSION" start_agent "$CURRENT_VERSION" ;; @@ -210,6 +223,7 @@ case "${1:-}" in check_memory_allocation log "Restarting ${CONTAINER_NAME}..." stop_agent + pin_version "$CURRENT_VERSION" start_agent "$CURRENT_VERSION" ;; From 624c6fbd9b3667448ce85fa2c6ed97569bdd8c64 Mon Sep 17 00:00:00 2001 From: Paul Harper Date: Mon, 2 Nov 2026 09:53:33 -0800 Subject: [PATCH 2/5] Test the plain and legacy version file formats Cover each accepted version file entry in the manager unit tests: a plain version, a legacy Ubuntu tag rewritten to the plain version on start, restart and upgrade, and a legacy Windows tag migrated to the plain version plus the $WINDOWS_VERSION setting. Also cover the $WINDOWS_VERSION setting overriding host detection, and that the bash manager rejects Windows tags. The integration tests now check that the version file holds exactly the plain version, start from a legacy tag, and check that install.ps1 persists -WindowsVersion. Co-Authored-By: Claude Opus 5.5 --- tests/bash/manager.bats | 48 +++++++++++++++++++--- tests/integration/linux.sh | 20 ++++----- tests/integration/windows.ps1 | 19 +++++++-- tests/pwsh/Install.Tests.ps1 | 10 +++-- tests/pwsh/Manager.Tests.ps1 | 76 ++++++++++++++++++++++++++++++----- 5 files changed, 141 insertions(+), 32 deletions(-) diff --git a/tests/bash/manager.bats b/tests/bash/manager.bats index a643401..eff4b7a 100644 --- a/tests/bash/manager.bats +++ b/tests/bash/manager.bats @@ -16,7 +16,7 @@ setup() { mkdir -p "$AGENT_DIR/config" cp "$REPO_ROOT/proxy-agent-manager.sh" "$AGENT_DIR/" echo '{"agent_id": "abc123"}' > "$AGENT_DIR/config/config.json" - echo "1.2.9-ubuntu-26.04" > "$AGENT_DIR/version" + echo "1.2.9" > "$AGENT_DIR/version" # State shared with the stubs in ./stubs; see the comments there. export DOCKER_LOG="$AGENT_DIR/docker.log" @@ -40,11 +40,32 @@ last_image() { cat "$DOCKER_LAST_IMAGE" } +version_file() { + cat "$AGENT_DIR/version" +} + @test "start runs the pinned Ubuntu image with the default memory limit" { run manager start [ "$status" -eq 0 ] [ "$(last_image)" = "$IMAGE_REPO:1.2.9-ubuntu-26.04" ] grep -q -- "--memory=5120m" "$DOCKER_LOG" + [ "$(version_file)" = "1.2.9" ] +} + +@test "start accepts a legacy Ubuntu image tag and rewrites it to the plain version" { + echo "1.2.9-ubuntu-26.04" > "$AGENT_DIR/version" + run manager start + [ "$status" -eq 0 ] + [ "$(last_image)" = "$IMAGE_REPO:1.2.9-ubuntu-26.04" ] + [ "$(version_file)" = "1.2.9" ] +} + +@test "restart rewrites a legacy Ubuntu image tag to the plain version" { + echo "1.2.9-ubuntu-26.04" > "$AGENT_DIR/version" + run manager restart + [ "$status" -eq 0 ] + [ "$(last_image)" = "$IMAGE_REPO:1.2.9-ubuntu-26.04" ] + [ "$(version_file)" = "1.2.9" ] } @test "start rejects an invalid MEMORY_ALLOCATION_MB in settings.sh" { @@ -118,7 +139,7 @@ last_image() { run manager upgrade [ "$status" -eq 0 ] [ "$(last_image)" = "$IMAGE_REPO:1.2.10-ubuntu-26.04" ] - [[ "$(cat "$AGENT_DIR/version")" == 1.2.10* ]] + [ "$(version_file)" = "1.2.10" ] } @test "upgrade queries the registry tag listing, or its override" { @@ -141,6 +162,15 @@ last_image() { ! grep -q "^run" "$DOCKER_LOG" } +@test "upgrade from a legacy Ubuntu image tag rewrites it to the plain version when already latest" { + export STUB_CURL_RESPONSE='{"tags": ["1.2.9-ubuntu-26.04","1.2.11"]}' + echo "1.2.9-ubuntu-26.04" > "$AGENT_DIR/version" + run manager upgrade + [ "$status" -eq 0 ] + [[ "$output" == *"Already running the latest version (1.2.9)"* ]] + [ "$(version_file)" = "1.2.9" ] +} + @test "upgrade is a no-op when a bare version file entry matches the latest Ubuntu tag" { export STUB_CURL_RESPONSE='{"tags": ["1.2.10-ubuntu-26.04","1.2.11"]}' echo "1.2.10" > "$AGENT_DIR/version" @@ -156,21 +186,29 @@ last_image() { [ "$status" -eq 1 ] [[ "$output" == *"rolling back"* ]] [ "$(last_image)" = "$IMAGE_REPO:1.2.9-ubuntu-26.04" ] - [[ "$(cat "$AGENT_DIR/version")" == 1.2.9* ]] + [ "$(version_file)" = "1.2.9" ] } @test "upgrade reports a failed rollback" { export DOCKER_UNHEALTHY_TAG="1.2." run manager upgrade [ "$status" -eq 1 ] - [[ "$output" == *"Rollback to 1.2.9-ubuntu-26.04 failed"* ]] + [[ "$output" == *"Rollback to 1.2.9 failed"* ]] } @test "upgrade rejects an invalid version file entry" { echo "2.0" > "$AGENT_DIR/version" run manager upgrade [ "$status" -eq 1 ] - [[ "$output" == *"Invalid"* ]] + [[ "$output" == *"Invalid proxy agent version '2.0'"* ]] +} + +@test "start rejects a Windows image tag" { + echo "1.2.9-windows-ltsc2022" > "$AGENT_DIR/version" + run manager start + [ "$status" -eq 1 ] + [[ "$output" == *"Invalid proxy agent version '1.2.9-windows-ltsc2022'"* ]] + [ ! -f "$DOCKER_LAST_IMAGE" ] } @test "stop removes the running container" { diff --git a/tests/integration/linux.sh b/tests/integration/linux.sh index c3d0e24..28728ca 100755 --- a/tests/integration/linux.sh +++ b/tests/integration/linux.sh @@ -60,16 +60,12 @@ assert_healthy() { assert_version_file() { local content content=$(cat "$INSTALL_DIR/version") - [[ "$content" == "$1"* ]] || fail "expected version file to start with $1, got '$content'" + [ "$content" = "$1" ] || fail "expected version file to contain $1, got '$content'" } echo "== Building stub image" docker build -q -f "$REPO_ROOT/tests/stub/Dockerfile.linux" -t "$STUB_IMAGE:$OLD_VERSION$SUFFIX" "$REPO_ROOT/tests/stub" -# The bare tags let the rollback step work whether the management script pins -# the plain version or the full image tag. -for tag in "$OLD_VERSION" "$NEW_VERSION" "$NEW_VERSION$SUFFIX"; do - docker tag "$STUB_IMAGE:$OLD_VERSION$SUFFIX" "$STUB_IMAGE:$tag" -done +docker tag "$STUB_IMAGE:$OLD_VERSION$SUFFIX" "$STUB_IMAGE:$NEW_VERSION$SUFFIX" echo "== Starting stub registry on port $PORT" python3 "$REPO_ROOT/tests/integration/registry_server.py" --port "$PORT" --root "$REPO_ROOT" \ @@ -112,11 +108,15 @@ status_output=$("$MANAGER" status) echo "== Rolling back to $OLD_VERSION via the version file" echo "$OLD_VERSION" > "$INSTALL_DIR/version" "$MANAGER" start -case "$(current_image)" in - "$STUB_IMAGE:$OLD_VERSION"|"$STUB_IMAGE:$OLD_VERSION$SUFFIX") ;; - *) fail "expected rollback to $OLD_VERSION, got $(current_image)" ;; -esac +assert_image "$STUB_IMAGE:$OLD_VERSION$SUFFIX" +assert_healthy + +echo "== Starting from a legacy image tag in the version file" +echo "$OLD_VERSION$SUFFIX" > "$INSTALL_DIR/version" +"$MANAGER" start +assert_image "$STUB_IMAGE:$OLD_VERSION$SUFFIX" assert_healthy +assert_version_file "$OLD_VERSION" echo "== Upgrading" "$MANAGER" upgrade diff --git a/tests/integration/windows.ps1 b/tests/integration/windows.ps1 index 15df7a5..e40bbbb 100644 --- a/tests/integration/windows.ps1 +++ b/tests/integration/windows.ps1 @@ -62,9 +62,9 @@ function Assert-Healthy { } function Assert-VersionFile { - param([string]$Prefix) + param([string]$Expected) $content = (Get-Content -LiteralPath (Join-Path $InstallDir 'version') -Raw).Trim() - if (-not $content.StartsWith($Prefix)) { Fail "expected version file to start with $Prefix, got '$content'" } + if ($content -ne $Expected) { Fail "expected version file to contain $Expected, got '$content'" } } function Invoke-Manager { @@ -131,6 +131,7 @@ try { Assert-VersionFile $NewVersion $settings = Get-Content -LiteralPath (Join-Path $InstallDir 'settings.ps1') -Raw if ($settings -notmatch '\$MEMORY_ALLOCATION_MB = 512') { Fail 'settings.ps1 does not record the memory allocation' } + if ($settings -match 'WINDOWS_VERSION') { Fail 'settings.ps1 records a Windows version although none was given' } $memory = [int64](docker inspect -f '{{.HostConfig.Memory}}' $Container).Trim() if ($memory -ne 512MB) { Fail "container memory limit not applied (got $memory)" } Assert-ConfigMatches $InstallDir 'config.json' @@ -145,6 +146,15 @@ try { Assert-Image "${StubImage}:$oldTag" Assert-Healthy + Write-Host '== Starting from a legacy image tag in the version file' + Set-Content -LiteralPath (Join-Path $InstallDir 'version') -Value $oldTag -Encoding ascii + $null = Invoke-Manager start + Assert-Image "${StubImage}:$oldTag" + Assert-Healthy + Assert-VersionFile $OldVersion + $settings = Get-Content -LiteralPath (Join-Path $InstallDir 'settings.ps1') -Raw + if ($settings -notmatch [regex]::Escape("`$WINDOWS_VERSION = '$ltsc'")) { Fail 'the legacy tag variant was not saved to settings.ps1' } + Write-Host '== Upgrading' $null = Invoke-Manager upgrade Assert-Image "${StubImage}:$newTag" @@ -191,9 +201,12 @@ try { Write-Host '== Installing from a WebSocket config migrates it through the configure endpoint' $wsInstallDir = Join-Path $TempRoot 'fivetran-proxy-agent-websocket' - & (Join-Path $RepoRoot 'install.ps1') (Join-Path $Fixtures 'config-websocket.json') -InstallDir $wsInstallDir + # -WindowsVersion is given here, matching the host, to cover persisting it. + & (Join-Path $RepoRoot 'install.ps1') (Join-Path $Fixtures 'config-websocket.json') -InstallDir $wsInstallDir -WindowsVersion $ltsc if ($LASTEXITCODE -ne 0) { Fail "install.ps1 with a WebSocket config exited with $LASTEXITCODE" } Assert-ConfigMatches $wsInstallDir 'config.json' + $wsSettings = Get-Content -LiteralPath (Join-Path $wsInstallDir 'settings.ps1') -Raw + if ($wsSettings -notmatch [regex]::Escape("`$WINDOWS_VERSION = '$ltsc'")) { Fail 'settings.ps1 does not record -WindowsVersion' } Assert-Image "${StubImage}:$newTag" Assert-Healthy $null = Invoke-Manager stop -Dir $wsInstallDir diff --git a/tests/pwsh/Install.Tests.ps1 b/tests/pwsh/Install.Tests.ps1 index 5f6aec5..675a31d 100644 --- a/tests/pwsh/Install.Tests.ps1 +++ b/tests/pwsh/Install.Tests.ps1 @@ -127,7 +127,7 @@ Describe 'install.ps1' { $output = Invoke-Installer (New-InstallArgs) $LASTEXITCODE | Should -Be 0 $output | Should -Match 'Using Windows image variant ltsc2022' - Get-VersionFile | Should -Be '1.2.10-windows-ltsc2022' + Get-VersionFile | Should -Be '1.2.10' $global:ProxyAgentStub.ManagerArgs | Should -Be @('start') } @@ -136,7 +136,7 @@ Describe 'install.ps1' { $global:ProxyAgentStub.Tags += '1.2.8-windows-ltsc2019' $null = Invoke-Installer (New-InstallArgs) $LASTEXITCODE | Should -Be 0 - Get-VersionFile | Should -Be '1.2.8-windows-ltsc2019' + Get-VersionFile | Should -Be '1.2.8' } It 'uses -WindowsVersion instead of detecting the host, even on an unsupported build' { @@ -145,7 +145,9 @@ Describe 'install.ps1' { $arguments.WindowsVersion = 'ltsc2025' $null = Invoke-Installer $arguments $LASTEXITCODE | Should -Be 0 - Get-VersionFile | Should -Be '1.2.11-windows-ltsc2025' + Get-VersionFile | Should -Be '1.2.11' + Get-Content -LiteralPath (Join-Path $script:InstallDir 'settings.ps1') -Raw | + Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) } It 'fails on an unsupported Windows Server build' { @@ -230,7 +232,7 @@ Describe 'install.ps1' { $output = Invoke-Installer (New-InstallArgs) $LASTEXITCODE | Should -Be 0 $output | Should -Match 'Using Linux container image' - Get-VersionFile | Should -Be '1.2.12-ubuntu-26.04' + Get-VersionFile | Should -Be '1.2.12' $global:ProxyAgentStub.ManagerArgs | Should -Be @('start') } diff --git a/tests/pwsh/Manager.Tests.ps1 b/tests/pwsh/Manager.Tests.ps1 index 005ec0f..5726603 100644 --- a/tests/pwsh/Manager.Tests.ps1 +++ b/tests/pwsh/Manager.Tests.ps1 @@ -27,7 +27,7 @@ BeforeAll { # file and the given version file content, and returns the manager's path. function New-AgentDir { param( - [string]$Version = '1.2.9-ubuntu-26.04', + [string]$Version = '1.2.9', [string]$Settings ) $dir = Join-Path ([System.IO.Path]::GetTempPath()) ("proxy-agent-test-" + [guid]::NewGuid()) @@ -46,6 +46,10 @@ BeforeAll { return (Get-Content -LiteralPath (Join-Path $script:AgentDir 'version') -Raw).Trim() } + function Get-SettingsFile { + return Get-Content -LiteralPath (Join-Path $script:AgentDir 'settings.ps1') -Raw + } + # Runs the manager, capturing every output stream as one string. The width # keeps Windows PowerShell 5.1 from wrapping long lines, such as paths, at # the console width. @@ -146,6 +150,15 @@ Describe 'proxy-agent-manager.ps1' { $LASTEXITCODE | Should -Be 0 $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-ubuntu-26.04" Get-DockerRunArgs | Should -Contain '--memory=5120m' + Get-VersionFile | Should -Be '1.2.9' + } + + It 'accepts a legacy Ubuntu image tag and rewrites it to the plain version' { + $manager = New-AgentDir -Version '1.2.9-ubuntu-26.04' + $null = Invoke-Manager $manager start + $LASTEXITCODE | Should -Be 0 + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-ubuntu-26.04" + Get-VersionFile | Should -Be '1.2.9' } It 'rejects MEMORY_ALLOCATION_MB = in settings.ps1' -ForEach @( @@ -221,6 +234,7 @@ Describe 'proxy-agent-manager.ps1' { It 'refuses a Windows image tag' { $manager = New-AgentDir -Version '1.2.9-windows-ltsc2022' { & $manager start } | Should -Throw '*Windows containers*' + Get-VersionFile | Should -Be '1.2.9-windows-ltsc2022' } It 'upgrades to the newest Ubuntu tag and ignores bare and Windows tags' { @@ -228,7 +242,7 @@ Describe 'proxy-agent-manager.ps1' { $null = Invoke-Manager $manager upgrade $LASTEXITCODE | Should -Be 0 $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.10-ubuntu-26.04" - Get-VersionFile | Should -BeLike '1.2.10*' + Get-VersionFile | Should -Be '1.2.10' } It 'queries the registry tag listing, or its override, on upgrade' { @@ -251,6 +265,14 @@ Describe 'proxy-agent-manager.ps1' { Get-DockerRunArgs | Should -BeNullOrEmpty } + It 'rewrites a legacy Ubuntu image tag to the plain version on upgrade when already latest' { + $global:ProxyAgentStub.Tags = @('1.2.9-ubuntu-26.04', '1.2.11') + $manager = New-AgentDir -Version '1.2.9-ubuntu-26.04' + $output = Invoke-Manager $manager upgrade + $output | Should -Match ([regex]::Escape('Already running the latest version (1.2.9)')) + Get-VersionFile | Should -Be '1.2.9' + } + It 'rolls back and fails when the upgraded container is unhealthy' { $global:ProxyAgentStub.UnhealthyTag = '1.2.10' $manager = New-AgentDir @@ -258,12 +280,12 @@ Describe 'proxy-agent-manager.ps1' { $LASTEXITCODE | Should -Be 1 $output | Should -Match 'rolling back' $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-ubuntu-26.04" - Get-VersionFile | Should -BeLike '1.2.9*' + Get-VersionFile | Should -Be '1.2.9' } It 'rejects an invalid version file entry on upgrade' { $manager = New-AgentDir -Version '2.0' - { & $manager upgrade } | Should -Throw '*Invalid*' + { & $manager upgrade } | Should -Throw "*Invalid proxy agent version '2.0'*" } It 'removes the running container on stop' { @@ -286,17 +308,43 @@ Describe 'proxy-agent-manager.ps1' { $null = Invoke-Manager $manager start $LASTEXITCODE | Should -Be 0 $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2022" + Get-VersionFile | Should -Be '1.2.9' } - It 'starts a pinned Windows image tag as is' { - $manager = New-AgentDir -Version '1.2.9-windows-ltsc2025' + It 'starts the LTSC version from the WINDOWS_VERSION setting over the detected one' { + $manager = New-AgentDir -Settings "`$WINDOWS_VERSION = 'ltsc2025'" $null = Invoke-Manager $manager start $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2025" } - It 'refuses an Ubuntu image tag' { + It 'migrates a legacy Windows image tag to the plain version and the WINDOWS_VERSION setting' { + $manager = New-AgentDir -Version '1.2.9-windows-ltsc2025' -Settings '$MEMORY_ALLOCATION_MB = 2048' + $null = Invoke-Manager $manager start + $LASTEXITCODE | Should -Be 0 + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2025" + Get-VersionFile | Should -Be '1.2.9' + $settings = Get-SettingsFile + $settings | Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) + $settings | Should -Match ([regex]::Escape('$MEMORY_ALLOCATION_MB = 2048')) + + # The migrated variant still wins over the detected ltsc2022. + $null = Invoke-Manager $manager restart + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2025" + } + + It 'migrates a legacy Windows image tag when no settings file exists' { + $manager = New-AgentDir -Version '1.2.9-windows-ltsc2025' + $null = Invoke-Manager $manager start + $LASTEXITCODE | Should -Be 0 + Get-SettingsFile | Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) + } + + It 'treats a legacy Ubuntu image tag as its plain version' { $manager = New-AgentDir -Version '1.2.9-ubuntu-26.04' - { & $manager start } | Should -Throw '*Linux containers*' + $null = Invoke-Manager $manager start + $LASTEXITCODE | Should -Be 0 + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2022" + Get-VersionFile | Should -Be '1.2.9' } It 'upgrades to the newest tag for the same LTSC version only' { @@ -304,7 +352,15 @@ Describe 'proxy-agent-manager.ps1' { $null = Invoke-Manager $manager upgrade $LASTEXITCODE | Should -Be 0 $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.10-windows-ltsc2022" - Get-VersionFile | Should -BeLike '1.2.10*' + Get-VersionFile | Should -Be '1.2.10' + } + + It 'upgrades within the LTSC version from the WINDOWS_VERSION setting' { + $manager = New-AgentDir -Settings "`$WINDOWS_VERSION = 'ltsc2025'" + $null = Invoke-Manager $manager upgrade + $LASTEXITCODE | Should -Be 0 + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.10-windows-ltsc2025" + Get-VersionFile | Should -Be '1.2.10' } It 'rolls back and fails when the upgraded container is unhealthy' { @@ -314,7 +370,7 @@ Describe 'proxy-agent-manager.ps1' { $LASTEXITCODE | Should -Be 1 $output | Should -Match 'rolling back' $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2022" - Get-VersionFile | Should -Be '1.2.9-windows-ltsc2022' + Get-VersionFile | Should -Be '1.2.9' } It 'fails on upgrade when no tags are published for the LTSC version' { From 358b774d72fa2d378a44ebdb8ee49486a0cc71bd Mon Sep 17 00:00:00 2001 From: Paul Harper Date: Mon, 2 Nov 2026 09:54:55 -0800 Subject: [PATCH 3/5] Warn when a Linux image tag is pinned on Windows containers A legacy -ubuntu-26.04 tag in the version file is still accepted with Windows containers, but the switch to the Windows image is now logged as a warning. Co-Authored-By: Claude Opus 5.5 --- proxy-agent-manager.ps1 | 3 +++ tests/pwsh/Manager.Tests.ps1 | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/proxy-agent-manager.ps1 b/proxy-agent-manager.ps1 index ab035d7..1a2bfdd 100644 --- a/proxy-agent-manager.ps1 +++ b/proxy-agent-manager.ps1 @@ -139,6 +139,9 @@ function Resolve-WindowsVersion { } return $null } + if ($PINNED_VERSION -match '-ubuntu-26\.04$') { + Write-Log "WARNING: $VERSION_FILE pins the Linux image tag $PINNED_VERSION, but Docker is running Windows containers. Using the Windows image for version $CURRENT_VERSION instead." + } if ($pinnedWindowsVariant) { $legacyVariant = $pinnedWindowsVariant if ($WINDOWS_VERSION -ne $legacyVariant) { diff --git a/tests/pwsh/Manager.Tests.ps1 b/tests/pwsh/Manager.Tests.ps1 index 5726603..0f77113 100644 --- a/tests/pwsh/Manager.Tests.ps1 +++ b/tests/pwsh/Manager.Tests.ps1 @@ -339,10 +339,11 @@ Describe 'proxy-agent-manager.ps1' { Get-SettingsFile | Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) } - It 'treats a legacy Ubuntu image tag as its plain version' { + It 'treats a legacy Ubuntu image tag as its plain version, with a warning' { $manager = New-AgentDir -Version '1.2.9-ubuntu-26.04' - $null = Invoke-Manager $manager start + $output = Invoke-Manager $manager start $LASTEXITCODE | Should -Be 0 + $output | Should -Match 'WARNING: .*Linux image tag 1\.2\.9-ubuntu-26\.04.*Windows containers' $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2022" Get-VersionFile | Should -Be '1.2.9' } From ce69b35de6d0d881c9953367e9ddf037fcf14c0a Mon Sep 17 00:00:00 2001 From: Paul Harper Date: Wed, 4 Nov 2026 11:24:42 -0800 Subject: [PATCH 4/5] fix(manager): defer version validation until needed --- proxy-agent-manager.ps1 | 5 +++-- proxy-agent-manager.sh | 5 +++-- tests/bash/manager.bats | 18 ++++++++++++++++++ tests/pwsh/Manager.Tests.ps1 | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+), 4 deletions(-) diff --git a/proxy-agent-manager.ps1 b/proxy-agent-manager.ps1 index 1a2bfdd..91ce741 100644 --- a/proxy-agent-manager.ps1 +++ b/proxy-agent-manager.ps1 @@ -396,10 +396,9 @@ function Invoke-Upgrade { # ── Commands ───────────────────────────────────────────────────────────────── -$CURRENT_VERSION = Get-ReleaseVersion -Tag $PINNED_VERSION - switch ($Command) { 'start' { + $CURRENT_VERSION = Get-ReleaseVersion -Tag $PINNED_VERSION Assert-MemoryAllocation if (-not (Start-CurrentProxyAgent)) { exit 1 } } @@ -408,11 +407,13 @@ switch ($Command) { Stop-ProxyAgent } 'restart' { + $CURRENT_VERSION = Get-ReleaseVersion -Tag $PINNED_VERSION Assert-MemoryAllocation Write-Log "Restarting $CONTAINER_NAME..." if (-not (Start-CurrentProxyAgent)) { exit 1 } } 'upgrade' { + $CURRENT_VERSION = Get-ReleaseVersion -Tag $PINNED_VERSION Assert-MemoryAllocation Invoke-Upgrade } diff --git a/proxy-agent-manager.sh b/proxy-agent-manager.sh index 058d268..c28c14a 100755 --- a/proxy-agent-manager.sh +++ b/proxy-agent-manager.sh @@ -201,10 +201,9 @@ start_agent() { # ── Commands ───────────────────────────────────────────────────────────────── -CURRENT_VERSION=$(get_release_version "$PINNED_VERSION") || exit 1 - case "${1:-}" in start) + CURRENT_VERSION=$(get_release_version "$PINNED_VERSION") || exit 1 check_memory_allocation pin_version "$CURRENT_VERSION" start_agent "$CURRENT_VERSION" @@ -220,6 +219,7 @@ case "${1:-}" in ;; restart) + CURRENT_VERSION=$(get_release_version "$PINNED_VERSION") || exit 1 check_memory_allocation log "Restarting ${CONTAINER_NAME}..." stop_agent @@ -228,6 +228,7 @@ case "${1:-}" in ;; upgrade) + CURRENT_VERSION=$(get_release_version "$PINNED_VERSION") || exit 1 check_memory_allocation upgrade_agent ;; diff --git a/tests/bash/manager.bats b/tests/bash/manager.bats index eff4b7a..8fa0bde 100644 --- a/tests/bash/manager.bats +++ b/tests/bash/manager.bats @@ -84,6 +84,24 @@ version_file() { [ ! -f "$DOCKER_LAST_IMAGE" ] } +@test "stop, status, and logs work with a malformed version file" { + manager start + echo "2.0" > "$AGENT_DIR/version" + + run manager status + [ "$status" -eq 0 ] + [[ "$output" == *"NAMES IMAGE STATUS"* ]] + + run manager logs + [ "$status" -eq 0 ] + grep -q "^logs -f proxy-agent-abc123" "$DOCKER_LOG" + + run manager stop + [ "$status" -eq 0 ] + [ ! -f "$DOCKER_LAST_IMAGE" ] + [ "$(version_file)" = "2.0" ] +} + @test "restart and upgrade reject an invalid MEMORY_ALLOCATION_MB without touching the agent" { manager start echo "MEMORY_ALLOCATION_MB=4GB" > "$AGENT_DIR/settings.sh" diff --git a/tests/pwsh/Manager.Tests.ps1 b/tests/pwsh/Manager.Tests.ps1 index 0f77113..86c509a 100644 --- a/tests/pwsh/Manager.Tests.ps1 +++ b/tests/pwsh/Manager.Tests.ps1 @@ -181,6 +181,25 @@ Describe 'proxy-agent-manager.ps1' { $global:ProxyAgentStub.LastImage | Should -BeNullOrEmpty } + It 'runs <_> with a malformed version file' -ForEach @('stop', 'status', 'logs') { + $manager = New-AgentDir + $null = Invoke-Manager $manager start + Set-Content -LiteralPath (Join-Path $script:AgentDir 'version') -Value '2.0' -Encoding ascii + + $null = Invoke-Manager $manager $_ + + $LASTEXITCODE | Should -Be 0 + Get-VersionFile | Should -Be '2.0' + if ($_ -eq 'stop') { + $global:ProxyAgentStub.LastImage | Should -BeNullOrEmpty + Get-DockerCall 'rm' | Should -Contain 'proxy-agent-abc123' + } elseif ($_ -eq 'status') { + Get-DockerCall 'ps' | Should -Contain 'name=proxy-agent-abc123' + } else { + Get-DockerCall 'logs' | Should -Contain '-f' + } + } + It 'rejects an invalid MEMORY_ALLOCATION_MB on <_> without touching the agent' -ForEach @('restart', 'upgrade') { $manager = New-AgentDir $null = Invoke-Manager $manager start From ae037e49f9356620f263d770ea27a5814c13923b Mon Sep 17 00:00:00 2001 From: Paul Harper Date: Wed, 4 Nov 2026 11:35:37 -0800 Subject: [PATCH 5/5] fix(windows): normalize variant and separate migrated setting --- install.ps1 | 1 + proxy-agent-manager.ps1 | 2 +- tests/pwsh/Install.Tests.ps1 | 7 ++++--- tests/pwsh/Manager.Tests.ps1 | 13 +++++++++++++ 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/install.ps1 b/install.ps1 index cf37c8d..c107644 100644 --- a/install.ps1 +++ b/install.ps1 @@ -15,6 +15,7 @@ param( $ErrorActionPreference = 'Stop' $ProgressPreference = 'SilentlyContinue' +if ($WindowsVersion) { $WindowsVersion = $WindowsVersion.ToLowerInvariant() } # ── Constants ──────────────────────────────────────────────────────────────── diff --git a/proxy-agent-manager.ps1 b/proxy-agent-manager.ps1 index 91ce741..b265cb6 100644 --- a/proxy-agent-manager.ps1 +++ b/proxy-agent-manager.ps1 @@ -145,7 +145,7 @@ function Resolve-WindowsVersion { if ($pinnedWindowsVariant) { $legacyVariant = $pinnedWindowsVariant if ($WINDOWS_VERSION -ne $legacyVariant) { - Add-Content -Path $SETTINGS_FILE -Value "`$WINDOWS_VERSION = '$legacyVariant'" -Encoding ascii + Add-Content -Path $SETTINGS_FILE -Value "`n`$WINDOWS_VERSION = '$legacyVariant'" -Encoding ascii $script:WINDOWS_VERSION = $legacyVariant } return $legacyVariant diff --git a/tests/pwsh/Install.Tests.ps1 b/tests/pwsh/Install.Tests.ps1 index 675a31d..fad0a3e 100644 --- a/tests/pwsh/Install.Tests.ps1 +++ b/tests/pwsh/Install.Tests.ps1 @@ -139,13 +139,14 @@ Describe 'install.ps1' { Get-VersionFile | Should -Be '1.2.8' } - It 'uses -WindowsVersion instead of detecting the host, even on an unsupported build' { + It 'normalizes -WindowsVersion and uses it instead of detecting the host, even on an unsupported build' { $global:ProxyAgentStub.OperatingSystem = [pscustomobject]@{ ProductType = 3; BuildNumber = 99999; Caption = 'Windows Server vNext' } $arguments = New-InstallArgs - $arguments.WindowsVersion = 'ltsc2025' - $null = Invoke-Installer $arguments + $arguments.WindowsVersion = 'LTSC2025' + $output = Invoke-Installer $arguments $LASTEXITCODE | Should -Be 0 Get-VersionFile | Should -Be '1.2.11' + $output | Should -Match 'Using Windows image variant ltsc2025' Get-Content -LiteralPath (Join-Path $script:InstallDir 'settings.ps1') -Raw | Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) } diff --git a/tests/pwsh/Manager.Tests.ps1 b/tests/pwsh/Manager.Tests.ps1 index 86c509a..4989688 100644 --- a/tests/pwsh/Manager.Tests.ps1 +++ b/tests/pwsh/Manager.Tests.ps1 @@ -358,6 +358,19 @@ Describe 'proxy-agent-manager.ps1' { Get-SettingsFile | Should -Match ([regex]::Escape("`$WINDOWS_VERSION = 'ltsc2025'")) } + It 'migrates a legacy Windows image tag when settings.ps1 has no final newline' { + $manager = New-AgentDir -Version '1.2.9-windows-ltsc2025' + [System.IO.File]::WriteAllText((Join-Path $script:AgentDir 'settings.ps1'), '$MEMORY_ALLOCATION_MB = 2048') + $null = Invoke-Manager $manager start + $LASTEXITCODE | Should -Be 0 + Get-SettingsFile | Should -Match '2048\r?\n\$WINDOWS_VERSION' + Get-VersionFile | Should -Be '1.2.9' + + $null = Invoke-Manager $manager restart + $LASTEXITCODE | Should -Be 0 + $global:ProxyAgentStub.LastImage | Should -Be "$($script:ImageRepo):1.2.9-windows-ltsc2025" + } + It 'treats a legacy Ubuntu image tag as its plain version, with a warning' { $manager = New-AgentDir -Version '1.2.9-ubuntu-26.04' $output = Invoke-Manager $manager start