diff --git a/.github/actions/prerelease-setup/README.md b/.github/actions/prerelease-setup/README.md
index 81778282..e008cf85 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
@@ -51,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. |
-| 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-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. |
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..b2afa4ec 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,24 @@ stable_tags() {
| sed 's/^v//' | { grep -E "$STABLE_RE" || true; }
}
+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.
# /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")"
+ 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
+ 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
@@ -118,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 vCluster pre-release found in loft-sh/vcluster"; 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; }
@@ -141,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 platform pre-release found in loft-sh/loft-enterprise"; 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 dbf1d821..a7f82f98 100644
--- a/.github/actions/prerelease-setup/test/resolve-versions.bats
+++ b/.github/actions/prerelease-setup/test/resolve-versions.bats
@@ -359,3 +359,74 @@ 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 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 "[
+ $(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"* ]]
+}