Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/scripts/checkout-release-codebase.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ set -euo pipefail
SCRIPT_NAME="$(basename "$0")"
readonly SCRIPT_NAME
readonly RELEASE_DIR="current_release"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
# shellcheck source=.github/scripts/release-version.sh
source "${SCRIPT_DIR}/release-version.sh"

usage() {
echo "Usage: ${SCRIPT_NAME}"
Expand Down Expand Up @@ -75,10 +79,10 @@ main() {
exit 1
fi

# Validate version format (should be semver like X.Y.Z or X.Y.Z-rcN)
if ! [[ "${release_version}" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]; then
# Historical rcN releases remain valid checkout targets.
if ! is_radius_release_version "${release_version}"; then
echo "Error: Invalid version format '${release_version}'"
echo "Expected semantic version format (e.g., '0.54.0' or '0.54.0-rc1')"
echo "Expected semantic version format (e.g., '0.61.0' or '0.61.0-rc.1')"
exit 1
fi

Expand Down
4 changes: 2 additions & 2 deletions .github/scripts/get_release_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
# REL_CHANNEL is:
# 'edge': for most builds
# 'edge': for PR builds
# '1.0.0-rc1' (the full version): for a tagged prerelease
# '1.0.0-rc.1' (the full version): for a tagged prerelease
# '1.0' (major.minor): for a tagged release

# We set the environment variable UPDATE_RELEASE if it's a full release (tagged and not prerelease)
Expand All @@ -35,7 +35,7 @@
#
# '0.42.42-dev' for most builds
# '0.42.42-pr-<pull request #>' for PR builds
# '1.0.0-rc1' (the full version): for a tagged prerelease
# '1.0.0-rc.1' (the full version): for a tagged prerelease
# '1.0.0' (major.minor.patch): for a tagged release
#
# note: we always install the helm chart using the tilde-range syntax to match our behavior
Expand Down
22 changes: 22 additions & 0 deletions .github/scripts/release-get-version.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,32 @@

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
# shellcheck source=.github/scripts/release-version.sh
source "${SCRIPT_DIR}/release-version.sh"

fail() {
echo "Error: $*" >&2
exit 1
}

validate_release_version() {
local version="$1"
local version_number="${version#v}"
local canonical_version

if [[ "${version}" != v* ]] || ! is_radius_release_version "${version_number}"; then
fail "unsupported release version ${version}; expected vX.Y.Z or vX.Y.Z-rc.N"
fi

if is_legacy_rc_version "${version_number}"; then
canonical_version="$(canonical_radius_rc_version "${version_number}")"
printf 'Warning: %s uses the historical RC form; use v%s for new releases.\n' \
"${version}" "${canonical_version}" >&2
Comment on lines +40 to +43
fi
}

tag_exists() {
local repository="$1"
local tag="$2"
Expand Down Expand Up @@ -59,6 +80,7 @@ main() {

IFS=',' read -r -a versions <<<"${versions_csv}"
for version in "${versions[@]}"; do
validate_release_version "${version}"
missing=()
for repository in "${repositories[@]}"; do
if ! tag_exists "${repository}" "${version}"; then
Expand Down
28 changes: 27 additions & 1 deletion .github/scripts/release-get-version_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,30 @@ test_rc_branch_and_channel() {
[[ "$(output_value release-channel)" == "0.62.0-rc.1" ]] || fail_test "RC release channel was parsed incorrectly"
}

test_legacy_rc_remains_accepted() {
local rc="v0.62.0-rc2"

run_selector "${rc}" "${REPOSITORIES[@]}"
[[ "${LAST_STATUS}" -eq 0 ]] || fail_test "legacy RC selection failed: ${LAST_OUTPUT}"
[[ "$(output_value release-branch-name)" == "release/0.62" ]] || fail_test "legacy RC release branch was parsed incorrectly"
[[ "$(output_value release-channel)" == "0.62.0-rc2" ]] || fail_test "legacy RC release channel was parsed incorrectly"
[[ "${LAST_OUTPUT}" == *"use v0.62.0-rc.2 for new releases"* ]] || fail_test "legacy RC selection did not recommend the dotted form"
}

test_rejects_unsupported_release_versions() {
run_selector "v0.62.0-rc.01" "${REPOSITORIES[@]}"
[[ "${LAST_STATUS}" -ne 0 ]] || fail_test "RC identifiers with leading zeroes should fail"
[[ "${LAST_OUTPUT}" == *"unsupported release version"* ]] || fail_test "invalid dotted RC failed for the wrong reason"

run_selector "v0.62.0-rc.0" "${REPOSITORIES[@]}"
[[ "${LAST_STATUS}" -ne 0 ]] || fail_test "RC zero should fail"
[[ "${LAST_OUTPUT}" == *"unsupported release version"* ]] || fail_test "RC zero failed for the wrong reason"

run_selector "v0.62.0-beta.1" "${REPOSITORIES[@]}"
[[ "${LAST_STATUS}" -ne 0 ]] || fail_test "unsupported prerelease identifiers should fail"
[[ "${LAST_OUTPUT}" == *"unsupported release version"* ]] || fail_test "unsupported prerelease failed for the wrong reason"
}

test_rejects_multiple_incomplete_versions() {
run_selector "v0.63.0,v0.62.0" "${REPOSITORIES[@]}"
[[ "${LAST_STATUS}" -ne 0 ]] || fail_test "multiple incomplete versions should fail"
Expand Down Expand Up @@ -239,13 +263,15 @@ main() {
test_selects_version_missing_from_any_repository
test_skips_version_complete_in_every_repository
test_rc_branch_and_channel
test_legacy_rc_remains_accepted
test_rejects_unsupported_release_versions
test_rejects_multiple_incomplete_versions
test_requires_repository_and_output
test_remote_query_errors_are_not_treated_as_missing_tags
test_main_waits_until_trigger_commit_is_cherry_picked
test_main_resumes_branch_created_before_tag
test_release_branch_trigger_never_waits_for_cherry_pick
echo "release version selection and resume tests passed (9 tests)"
echo "release version selection and resume tests passed (11 tests)"
}

main "$@"
11 changes: 8 additions & 3 deletions .github/scripts/release-verification.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
# shellcheck source=.github/scripts/release-version.sh
source "${SCRIPT_DIR}/release-version.sh"

# Configuration
readonly NAMESPACE="radius-system"
readonly GITHUB_ORG="radius-project"
Expand Down Expand Up @@ -129,9 +134,9 @@ if [[ -z "$RELEASE_VERSION_NUMBER" ]]; then
exit 1
fi

# Validate version format
if [[ ! "$RELEASE_VERSION_NUMBER" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-rc[0-9]+)?$ ]]; then
echo "Error: Invalid version format. Expected format: X.Y.Z or X.Y.Z-rcN" >&2
# Validate version format. Historical rcN releases remain verifiable.
if ! is_radius_release_version "${RELEASE_VERSION_NUMBER}"; then
echo "Error: Invalid version format. Expected format: X.Y.Z or X.Y.Z-rc.N" >&2
exit 1
fi

Expand Down
168 changes: 168 additions & 0 deletions .github/scripts/release-version-format_test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#!/bin/bash

# ------------------------------------------------------------
# Copyright 2026 The Radius Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
readonly SCRIPT_DIR
readonly VALIDATOR="${SCRIPT_DIR}/validate_semver.py"
readonly TAG_PARSER="${SCRIPT_DIR}/get_release_version.py"
readonly RUNBOOK="${SCRIPT_DIR}/../../docs/contributing/contributing-releases/README.md"
# shellcheck source=.github/scripts/release-version.sh
source "${SCRIPT_DIR}/release-version.sh"

if [[ -z "${PYTHON:-}" ]]; then
if command -v python3 > /dev/null; then
PYTHON="python3"
else
PYTHON="python"
fi
fi
readonly PYTHON

TEMP_DIR=""

cleanup() {
if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then
rm -rf "${TEMP_DIR}"
fi
}
trap cleanup EXIT

fail_test() {
echo "FAIL: $*" >&2
exit 1
}

assert_valid() {
local version="$1"

if ! "${PYTHON}" "${VALIDATOR}" "${version}" > /dev/null; then
fail_test "expected valid SemVer: ${version}"
fi
}

assert_invalid() {
local version="$1"

if "${PYTHON}" "${VALIDATOR}" "${version}" > /dev/null 2>&1; then
fail_test "expected invalid SemVer: ${version}"
fi
}

assert_tag_parser() {
local version="$1"
local environment_file="${TEMP_DIR}/github-env"

: > "${environment_file}"
GITHUB_REF="refs/tags/v${version}" GITHUB_ENV="${environment_file}" \
"${PYTHON}" "${TAG_PARSER}" > /dev/null

if ! grep -Fxq "REL_VERSION=${version}" "${environment_file}"; then
fail_test "tag parser did not preserve release version ${version}"
fi
if ! grep -Fxq "REL_CHANNEL=${version}" "${environment_file}"; then
fail_test "tag parser did not preserve release channel ${version}"
fi
if ! grep -Fxq "CHART_VERSION=${version}" "${environment_file}"; then
fail_test "tag parser did not preserve chart version ${version}"
fi
}

assert_radius_valid() {
local version="$1"

if ! is_radius_release_version "${version}"; then
fail_test "expected valid Radius release version: ${version}"
fi
}

assert_radius_invalid() {
local version="$1"

if is_radius_release_version "${version}"; then
fail_test "expected invalid Radius release version: ${version}"
fi
}

assert_policy_callers() {
local script

for script in \
release-get-version.sh \
release-verification.sh \
checkout-release-codebase.sh; do
if ! grep -Fq "source \"\${SCRIPT_DIR}/release-version.sh\"" \
"${SCRIPT_DIR}/${script}"; then
fail_test "${script} does not use the shared release-version policy"
fi
done
}

assert_runbook_uses_dotted_rc() {
local legacy_lines
local version

legacy_lines="$(grep -nE 'rc[0-9]' "${RUNBOOK}" || true)"
if [[ -n "${legacy_lines}" ]]; then
fail_test "release runbook shows legacy RC identifiers:"$'\n'"${legacy_lines}"
fi

while read -r version; do
assert_radius_valid "${version#v}"
if is_legacy_rc_version "${version#v}"; then
fail_test "release runbook example uses a legacy RC version: ${version}"
fi
done < <(grep -oE "version: 'v[0-9][^']*'" "${RUNBOOK}" | grep -oE "v[0-9][^']*")
}

main() {
TEMP_DIR="$(mktemp -d "${TMPDIR:-/tmp}/release-version-format-test-XXXXXX")"

assert_valid "0.61.0"
assert_valid "0.61.0-rc.1"
assert_valid "0.61.0-rc1"
assert_valid "0.61.0-beta.1"
assert_valid "0.61.0-rc.0"
assert_valid "0.61.0-rc.2+build.7"
assert_invalid "v0.61.0-rc.1"
assert_invalid "0.61.0-rc.01"
assert_invalid "0.61.0-rc..1"
assert_invalid "0.61.0-rc_1"

assert_tag_parser "0.61.0-rc.1"
assert_tag_parser "0.60.0-rc3"

assert_radius_valid "0.61.0"
assert_radius_valid "0.61.0-rc.1"
assert_radius_valid "0.60.0-rc3"
assert_radius_invalid "v0.61.0-rc.1"
assert_radius_invalid "0.61.0-beta.1"
assert_radius_invalid "0.61.0-rc.0"
assert_radius_invalid "0.61.0-rc0"
assert_radius_invalid "0.61.0-rc.01"
if [[ "$(canonical_radius_rc_version "0.60.0-rc3")" != "0.60.0-rc.3" ]]; then
fail_test "legacy RC canonicalization failed"
fi
assert_policy_callers
assert_runbook_uses_dotted_rc

echo "release version format tests passed (24 tests)"
}

main "$@"
54 changes: 54 additions & 0 deletions .github/scripts/release-version.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#!/bin/bash

# ------------------------------------------------------------
# Copyright 2026 The Radius Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ------------------------------------------------------------

# Shared Radius release-version policy; source this file from release scripts.
# The constants below are readonly, so guard against being sourced twice.
if [[ -n "${RADIUS_RELEASE_VERSION_POLICY_LOADED:-}" ]]; then
return 0
fi
RADIUS_RELEASE_VERSION_POLICY_LOADED=1

readonly RADIUS_SEMVER_NUMBER='(0|[1-9][0-9]*)'
readonly RADIUS_RC_NUMBER='[1-9][0-9]*'
RADIUS_RELEASE_VERSION_PATTERN="^${RADIUS_SEMVER_NUMBER}\\.${RADIUS_SEMVER_NUMBER}"
RADIUS_RELEASE_VERSION_PATTERN+="\\.${RADIUS_SEMVER_NUMBER}"
RADIUS_RELEASE_VERSION_PATTERN+="(-rc(\\.${RADIUS_RC_NUMBER}|${RADIUS_RC_NUMBER}))?$"
readonly RADIUS_RELEASE_VERSION_PATTERN
readonly RADIUS_LEGACY_RC_PATTERN="-rc${RADIUS_RC_NUMBER}$"

is_radius_release_version() {
local version="$1"

[[ "${version}" =~ ${RADIUS_RELEASE_VERSION_PATTERN} ]]
}

is_legacy_rc_version() {
local version="$1"

[[ "${version}" =~ ${RADIUS_LEGACY_RC_PATTERN} ]]
}

canonical_radius_rc_version() {
local version="$1"

if is_legacy_rc_version "${version}"; then
printf '%s\n' "${version/-rc/-rc.}"
else
printf '%s\n' "${version}"
fi
}
Loading
Loading