From ada33bcabd1ff725839224d10a633397c3397a7b Mon Sep 17 00:00:00 2001 From: anvesh-loft Date: Mon, 19 Oct 2026 12:53:23 -0300 Subject: [PATCH 1/3] fix(prerelease-setup): never resolve a pre-release from an abandoned line An empty upgrade target picks the most recently published pre-release whose final release has not shipped. Once every current line has gone GA, nothing recent qualifies and the resolver fell through to the newest pre-release left on the list, a 2023 alpha of a line that was never finished. The run then died in Verify release assets because install-standalone.sh predates it (loft-enterprise run 35222301790). Only pre-releases on or above the previous stable minor line qualify now. A maintenance RC on the previous line and an alpha of the next major still resolve. When nothing qualifies the step fails and names the input to pass. Part of ENGQA-1777. Co-Authored-By: Claude Fable 5.1 --- .github/actions/prerelease-setup/README.md | 11 +++- .github/actions/prerelease-setup/action.yml | 4 +- .../prerelease-setup/src/resolve-versions.sh | 19 ++++-- .../test/resolve-versions.bats | 59 +++++++++++++++++++ 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/.github/actions/prerelease-setup/README.md b/.github/actions/prerelease-setup/README.md index 81778282..2832d0da 100644 --- a/.github/actions/prerelease-setup/README.md +++ b/.github/actions/prerelease-setup/README.md @@ -22,7 +22,12 @@ The action performs, in order: upgrade target (`standalone-vcluster-upgrade-version`, `platform-rc-version`) resolves to the most recently published pre-release whose final release has not shipped yet, excluding `-next` - internal builds. An empty base + internal builds and anything below the previous stable minor line. A + pre-release that old belongs to a line that was never finished, and once + every current line has gone GA those are all that is left: a manual run + once resolved `0.18.2-alpha.0` that way and died on a missing asset. When + nothing qualifies the step fails and asks for the version explicitly. An + empty base (`standalone-vcluster-version`, `platform-base-version`) resolves to the highest stable release strictly below its target, so a maintenance release is tested against the release before it rather than against @@ -55,9 +60,9 @@ provisioning (`aws-test-infra`) and the Ginkgo test execution |-------------------------------------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | github-token | string | true | | GitHub token with contents:read on loft-sh/loft-enterprise.
Required because the platform release resolvers
call the GitHub API for a
private repo; unauthenticated calls return 404.
Pass the calling job's github.token from
a job whose permissions grant contents:read. | | platform-base-version | string | false | | Platform version for the initial install
(e.g. 4.9.0). Empty resolves to the highest
stable release of loft-sh/loft-enterprise below the
RC target. | -| platform-rc-version | string | false | | Platform RC version for upgrade (e.g. 4.10.0-alpha.6).
Empty resolves to the most recently
published platform pre-release whose final release
has not shipped, excluding -next builds. | +| platform-rc-version | string | false | | Platform RC version for upgrade (e.g. 4.10.0-alpha.6).
Empty resolves to the most recently
published platform pre-release whose final release
has not shipped, excluding -next builds
and anything below the previous stable
minor line; fails when nothing qualifies. | | role-session-name | string | true | | AWS STS role-session-name. Each consumer job
passes a distinct value (e.g. prerelease-vcluster-, prerelease-aicloud-). | -| standalone-vcluster-upgrade-version | string | false | | vCluster version to upgrade standalone to
(e.g. 0.35.0-alpha.7). Empty resolves to the most
recently published vCluster pre-release whose final
release has not shipped, excluding -next
builds. Must differ from the resolved
base version. | +| standalone-vcluster-upgrade-version | string | false | | vCluster version to upgrade standalone to
(e.g. 0.35.0-alpha.7). Empty resolves to the most
recently published vCluster pre-release whose final
release has not shipped, excluding -next
builds and anything below the previous
stable minor line; fails when nothing
qualifies. Must differ from the resolved
base version. | | standalone-vcluster-version | string | false | | vCluster version to install for standalone
(e.g. 0.34.0). Empty resolves to the highest
stable release of loft-sh/vcluster below the
upgrade target. | diff --git a/.github/actions/prerelease-setup/action.yml b/.github/actions/prerelease-setup/action.yml index f585ca17..fa3088d0 100644 --- a/.github/actions/prerelease-setup/action.yml +++ b/.github/actions/prerelease-setup/action.yml @@ -13,7 +13,7 @@ inputs: required: false default: '' standalone-vcluster-upgrade-version: - description: 'vCluster version to upgrade standalone to (e.g. 0.35.0-alpha.7). Empty resolves to the most recently published vCluster pre-release whose final release has not shipped, excluding -next builds. Must differ from the resolved base version.' + description: 'vCluster version to upgrade standalone to (e.g. 0.35.0-alpha.7). Empty resolves to the most recently published vCluster pre-release whose final release has not shipped, excluding -next builds and anything below the previous stable minor line; fails when nothing qualifies. Must differ from the resolved base version.' required: false default: '' platform-base-version: @@ -21,7 +21,7 @@ inputs: required: false default: '' platform-rc-version: - description: 'Platform RC version for upgrade (e.g. 4.10.0-alpha.6). Empty resolves to the most recently published platform pre-release whose final release has not shipped, excluding -next builds.' + description: 'Platform RC version for upgrade (e.g. 4.10.0-alpha.6). Empty resolves to the most recently published platform pre-release whose final release has not shipped, excluding -next builds and anything below the previous stable minor line; fails when nothing qualifies.' required: false default: '' diff --git a/.github/actions/prerelease-setup/src/resolve-versions.sh b/.github/actions/prerelease-setup/src/resolve-versions.sh index dedfbe7c..05be41e3 100755 --- a/.github/actions/prerelease-setup/src/resolve-versions.sh +++ b/.github/actions/prerelease-setup/src/resolve-versions.sh @@ -1,8 +1,8 @@ #!/usr/bin/env bash # Resolves and validates the four version inputs for the pre-release checks. # An empty upgrade target becomes the most recently published pre-release whose -# final release has not shipped yet; an empty base becomes the highest release -# below that target. +# final release has not shipped yet, on or above the previous stable minor line; +# an empty base becomes the highest release below that target. # # Needs GNU coreutils: `sort -V` must rank '~' below everything, which BSD and # macOS sort do not. The runners are Ubuntu; `make test` on a Mac will disagree. @@ -85,16 +85,23 @@ stable_tags() { | sed 's/^v//' | { grep -E "$STABLE_RE" || true; } } +oldest_live_line() { + stable_tags "$1" | sed -E 's/\.[0-9]+$//' | sort -uV | tail -n2 | head -n1 +} + # The most recently published pre-release whose final release is not out yet. # /releases is not returned in any dependable order, so publish time is read # from the payload and sorted here rather than trusting the first row. newest_unreleased_pre_release() { - local repo="$1" released + local repo="$1" released floor released="$(stable_tags "$repo")" - local ranked first + floor="$(oldest_live_line "$repo")" + local ranked first line ranked="$(pre_release_tags "$repo" | while IFS=$'\t' read -r published version; do [ -n "$version" ] || continue if grep -qxF "${version%%-*}" <<<"$released"; then continue; fi + line="${version%%-*}"; line="${line%.*}" + if [ -n "$floor" ] && is_below "$line" "$floor"; then continue; fi printf '%s\t%s\t%s\n' "$published" "$(sort_key "$version")" "$version" done | sort -t "$(printf '\t')" -k1,1r -k2,2Vr)" [ -n "$ranked" ] || return 0 @@ -118,7 +125,7 @@ UP="${STANDALONE_VCLUSTER_UPGRADE_VERSION_INPUT:-}" UP="${UP#v}" if [ -z "$UP" ]; then UP="$(newest_unreleased_pre_release vcluster)" - [ -n "$UP" ] || { echo "no vCluster pre-release found in loft-sh/vcluster"; exit 1; } + [ -n "$UP" ] || { echo "no unreleased vCluster pre-release on or above the $(oldest_live_line vcluster) line in loft-sh/vcluster; pass standalone-vcluster-upgrade-version explicitly"; exit 1; } echo "Resolved newest unreleased vCluster pre-release: $UP" fi [[ "$UP" =~ $VERSION_RE ]] || { echo "invalid standalone-vcluster-upgrade-version: $UP"; exit 1; } @@ -141,7 +148,7 @@ RC="${PLATFORM_RC_VERSION_INPUT:-}" RC="${RC#v}" if [ -z "$RC" ]; then RC="$(newest_unreleased_pre_release loft-enterprise)" - [ -n "$RC" ] || { echo "no platform pre-release found in loft-sh/loft-enterprise"; exit 1; } + [ -n "$RC" ] || { echo "no unreleased platform pre-release on or above the $(oldest_live_line loft-enterprise) line in loft-sh/loft-enterprise; pass platform-rc-version explicitly"; exit 1; } echo "Resolved newest unreleased platform pre-release: $RC" fi [[ "$RC" =~ $VERSION_RE ]] || { echo "invalid platform-rc-version: $RC"; exit 1; } diff --git a/.github/actions/prerelease-setup/test/resolve-versions.bats b/.github/actions/prerelease-setup/test/resolve-versions.bats index dbf1d821..b321a0db 100644 --- a/.github/actions/prerelease-setup/test/resolve-versions.bats +++ b/.github/actions/prerelease-setup/test/resolve-versions.bats @@ -359,3 +359,62 @@ out() { grep "^$1=" "$GITHUB_OUTPUT" | cut -d= -f2-; } [ "$status" -ne 0 ] [[ "$output" == *"no stable vCluster release below 0.35.3-rc.1"* ]] } + +# --- abandoned pre-releases -------------------------------------------------- + +@test "a pre-release from a line that was never finished is not a target" { + # run 35222301790: every 0.37 pre-release had shipped, so the resolver fell + # through to a 2023 alpha whose line was abandoned before install-standalone.sh existed + set_releases vcluster 1 "[ + $(release v0.37.1-rc.2 true 2026-09-10T10:00:00Z), + $(release v0.37.1 false 2026-09-15T10:00:00Z), + $(release v0.37.0 false 2026-09-01T10:00:00Z), + $(release v0.36.2 false 2026-08-20T10:00:00Z), + $(release v0.18.2-alpha.0 true 2023-12-28T13:30:54Z), + $(release v0.18.1 false 2023-12-01T10:00:00Z) + ]" + run "$SCRIPT" + [ "$status" -ne 0 ] + [[ "$output" != *"0.18.2-alpha.0"* ]] + [[ "$output" == *"pass standalone-vcluster-upgrade-version explicitly"* ]] +} + +@test "a maintenance pre-release on the previous line is still a target" { + set_releases vcluster 1 "[ + $(release v0.36.4-rc.1 true 2026-10-10T10:00:00Z), + $(release v0.37.3 false 2026-10-05T10:00:00Z), + $(release v0.36.3 false 2026-09-20T10:00:00Z), + $(release v0.18.2-alpha.0 true 2023-12-28T13:30:54Z) + ]" + run "$SCRIPT" + [ "$status" -eq 0 ] + [ "$(out standalone-vcluster-upgrade-version)" = "0.36.4-rc.1" ] + [ "$(out standalone-vcluster-version)" = "0.36.3" ] +} + +@test "a pre-release of the next major is a target" { + export STANDALONE_VCLUSTER_UPGRADE_VERSION_INPUT="0.36.1-rc.3" + set_releases loft-enterprise 1 "[ + $(release v5.0.0-alpha.1 true 2026-10-08T20:41:44Z), + $(release v4.12.3 false 2026-10-01T10:00:00Z), + $(release v4.11.3 false 2026-09-01T10:00:00Z), + $(release v2.3.1-alpha.0 true 2022-01-01T10:00:00Z) + ]" + run "$SCRIPT" + [ "$status" -eq 0 ] + [ "$(out platform-rc-version)" = "5.0.0-alpha.1" ] + [ "$(out platform-base-version)" = "4.12.3" ] +} + +@test "the platform target is rejected the same way" { + export STANDALONE_VCLUSTER_UPGRADE_VERSION_INPUT="0.36.1-rc.3" + set_releases loft-enterprise 1 "[ + $(release v4.12.3 false 2026-10-01T10:00:00Z), + $(release v4.11.3 false 2026-09-01T10:00:00Z), + $(release v2.3.1-alpha.0 true 2022-01-01T10:00:00Z) + ]" + run "$SCRIPT" + [ "$status" -ne 0 ] + [[ "$output" != *"2.3.1-alpha.0"* ]] + [[ "$output" == *"pass platform-rc-version explicitly"* ]] +} From 17104e6552a1fa0637980a8ad12fc2bd86f09339 Mon Sep 17 00:00:00 2001 From: anvesh-loft Date: Mon, 19 Oct 2026 13:31:39 -0300 Subject: [PATCH 2/3] docs(prerelease-setup): regenerate the input table Co-Authored-By: Claude Fable 5.1 --- .github/actions/prerelease-setup/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/actions/prerelease-setup/README.md b/.github/actions/prerelease-setup/README.md index 2832d0da..e008cf85 100644 --- a/.github/actions/prerelease-setup/README.md +++ b/.github/actions/prerelease-setup/README.md @@ -56,14 +56,14 @@ provisioning (`aws-test-infra`) and the Ginkgo test execution -| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION | -|-------------------------------------|--------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| github-token | string | true | | GitHub token with contents:read on loft-sh/loft-enterprise.
Required because the platform release resolvers
call the GitHub API for a
private repo; unauthenticated calls return 404.
Pass the calling job's github.token from
a job whose permissions grant contents:read. | -| platform-base-version | string | false | | Platform version for the initial install
(e.g. 4.9.0). Empty resolves to the highest
stable release of loft-sh/loft-enterprise below the
RC target. | -| platform-rc-version | string | false | | Platform RC version for upgrade (e.g. 4.10.0-alpha.6).
Empty resolves to the most recently
published platform pre-release whose final release
has not shipped, excluding -next builds
and anything below the previous stable
minor line; fails when nothing qualifies. | -| role-session-name | string | true | | AWS STS role-session-name. Each consumer job
passes a distinct value (e.g. prerelease-vcluster-, prerelease-aicloud-). | -| standalone-vcluster-upgrade-version | string | false | | vCluster version to upgrade standalone to
(e.g. 0.35.0-alpha.7). Empty resolves to the most
recently published vCluster pre-release whose final
release has not shipped, excluding -next
builds and anything below the previous
stable minor line; fails when nothing
qualifies. Must differ from the resolved
base version. | -| standalone-vcluster-version | string | false | | vCluster version to install for standalone
(e.g. 0.34.0). Empty resolves to the highest
stable release of loft-sh/vcluster below the
upgrade target. | +| INPUT | TYPE | REQUIRED | DEFAULT | DESCRIPTION | +|-------------------------------------|--------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| github-token | string | true | | GitHub token with contents:read on loft-sh/loft-enterprise.
Required because the platform release resolvers
call the GitHub API for a
private repo; unauthenticated calls return 404.
Pass the calling job's github.token from
a job whose permissions grant contents:read. | +| platform-base-version | string | false | | Platform version for the initial install
(e.g. 4.9.0). Empty resolves to the highest
stable release of loft-sh/loft-enterprise below the
RC target. | +| platform-rc-version | string | false | | Platform RC version for upgrade (e.g. 4.10.0-alpha.6).
Empty resolves to the most recently
published platform pre-release whose final release
has not shipped, excluding -next builds
and anything below the previous stable
minor line; fails when nothing qualifies. | +| role-session-name | string | true | | AWS STS role-session-name. Each consumer job
passes a distinct value (e.g. prerelease-vcluster-, prerelease-aicloud-). | +| standalone-vcluster-upgrade-version | string | false | | vCluster version to upgrade standalone to
(e.g. 0.35.0-alpha.7). Empty resolves to the most
recently published vCluster pre-release whose final
release has not shipped, excluding -next
builds and anything below the previous
stable minor line; fails when nothing
qualifies. Must differ from the resolved
base version. | +| standalone-vcluster-version | string | false | | vCluster version to install for standalone
(e.g. 0.34.0). Empty resolves to the highest
stable release of loft-sh/vcluster below the
upgrade target. | From 4a400770fb4cd13e1cc82c1da8ecf5a177e0a52e Mon Sep 17 00:00:00 2001 From: anvesh-loft Date: Mon, 19 Oct 2026 14:13:33 -0300 Subject: [PATCH 3/3] refactor(prerelease-setup): name the line floor after the docs and test the current line previous_stable_line is the term the README uses; line_of replaces the two ways the script computed a version's minor line. One more bats test: a patch pre-release on the current line resolves. Co-Authored-By: Claude Fable 5.1 --- .../prerelease-setup/src/resolve-versions.sh | 17 +++++++++-------- .../prerelease-setup/test/resolve-versions.bats | 12 ++++++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/actions/prerelease-setup/src/resolve-versions.sh b/.github/actions/prerelease-setup/src/resolve-versions.sh index 05be41e3..b2afa4ec 100755 --- a/.github/actions/prerelease-setup/src/resolve-versions.sh +++ b/.github/actions/prerelease-setup/src/resolve-versions.sh @@ -85,8 +85,10 @@ stable_tags() { | sed 's/^v//' | { grep -E "$STABLE_RE" || true; } } -oldest_live_line() { - stable_tags "$1" | sed -E 's/\.[0-9]+$//' | sort -uV | tail -n2 | head -n1 +line_of() { local v="${1%%-*}"; printf '%s' "${v%.*}"; } + +previous_stable_line() { + stable_tags "$1" | while read -r v; do line_of "$v"; echo; done | sort -uV | tail -n2 | head -n1 } # The most recently published pre-release whose final release is not out yet. @@ -95,13 +97,12 @@ oldest_live_line() { newest_unreleased_pre_release() { local repo="$1" released floor released="$(stable_tags "$repo")" - floor="$(oldest_live_line "$repo")" - local ranked first line + floor="$(previous_stable_line "$repo")" + local ranked first ranked="$(pre_release_tags "$repo" | while IFS=$'\t' read -r published version; do [ -n "$version" ] || continue if grep -qxF "${version%%-*}" <<<"$released"; then continue; fi - line="${version%%-*}"; line="${line%.*}" - if [ -n "$floor" ] && is_below "$line" "$floor"; then continue; fi + if [ -n "$floor" ] && is_below "$(line_of "$version")" "$floor"; then continue; fi printf '%s\t%s\t%s\n' "$published" "$(sort_key "$version")" "$version" done | sort -t "$(printf '\t')" -k1,1r -k2,2Vr)" [ -n "$ranked" ] || return 0 @@ -125,7 +126,7 @@ UP="${STANDALONE_VCLUSTER_UPGRADE_VERSION_INPUT:-}" UP="${UP#v}" if [ -z "$UP" ]; then UP="$(newest_unreleased_pre_release vcluster)" - [ -n "$UP" ] || { echo "no unreleased vCluster pre-release on or above the $(oldest_live_line vcluster) line in loft-sh/vcluster; pass standalone-vcluster-upgrade-version explicitly"; exit 1; } + [ -n "$UP" ] || { echo "no unreleased vCluster pre-release on or above the $(previous_stable_line vcluster) line in loft-sh/vcluster; pass standalone-vcluster-upgrade-version explicitly"; exit 1; } echo "Resolved newest unreleased vCluster pre-release: $UP" fi [[ "$UP" =~ $VERSION_RE ]] || { echo "invalid standalone-vcluster-upgrade-version: $UP"; exit 1; } @@ -148,7 +149,7 @@ RC="${PLATFORM_RC_VERSION_INPUT:-}" RC="${RC#v}" if [ -z "$RC" ]; then RC="$(newest_unreleased_pre_release loft-enterprise)" - [ -n "$RC" ] || { echo "no unreleased platform pre-release on or above the $(oldest_live_line loft-enterprise) line in loft-sh/loft-enterprise; pass platform-rc-version explicitly"; exit 1; } + [ -n "$RC" ] || { echo "no unreleased platform pre-release on or above the $(previous_stable_line loft-enterprise) line in loft-sh/loft-enterprise; pass platform-rc-version explicitly"; exit 1; } echo "Resolved newest unreleased platform pre-release: $RC" fi [[ "$RC" =~ $VERSION_RE ]] || { echo "invalid platform-rc-version: $RC"; exit 1; } diff --git a/.github/actions/prerelease-setup/test/resolve-versions.bats b/.github/actions/prerelease-setup/test/resolve-versions.bats index b321a0db..a7f82f98 100644 --- a/.github/actions/prerelease-setup/test/resolve-versions.bats +++ b/.github/actions/prerelease-setup/test/resolve-versions.bats @@ -392,6 +392,18 @@ out() { grep "^$1=" "$GITHUB_OUTPUT" | cut -d= -f2-; } [ "$(out standalone-vcluster-version)" = "0.36.3" ] } +@test "a patch pre-release on the current line is a target" { + set_releases vcluster 1 "[ + $(release v0.37.4-rc.1 true 2026-10-16T13:11:47Z), + $(release v0.37.3 false 2026-10-05T10:00:00Z), + $(release v0.36.3 false 2026-09-20T10:00:00Z) + ]" + run "$SCRIPT" + [ "$status" -eq 0 ] + [ "$(out standalone-vcluster-upgrade-version)" = "0.37.4-rc.1" ] + [ "$(out standalone-vcluster-version)" = "0.37.3" ] +} + @test "a pre-release of the next major is a target" { export STANDALONE_VCLUSTER_UPGRADE_VERSION_INPUT="0.36.1-rc.3" set_releases loft-enterprise 1 "[