ci(release): infer optional workflow tags (#4707)

* ci(release): infer optional workflow tags

Signed-off-by: Simon Scatton <sscatton@nvidia.com>

* fix(release): use resolved tag in success notifications

Signed-off-by: Simon Scatton <sscatton@nvidia.com>

* fix(release): pin prerelease notes to resolved candidate

Signed-off-by: Simon Scatton <sscatton@nvidia.com>

---------

Signed-off-by: Simon Scatton <sscatton@nvidia.com>
This commit is contained in:
Simon Scatton
2026-10-26 12:50:45 +00:00
committed by GitHub
parent c4688aa871
commit 72bc3d3668
9 changed files with 270 additions and 68 deletions
+17 -43
View File
@@ -21,69 +21,43 @@ jobs:
create-tag:
runs-on: ubuntu-latest
timeout-minutes: 5
concurrency:
group: release-prerelease-tags
cancel-in-progress: false
queue: max
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: "0.10.12"
python-version: "3.13"
enable-cache: false
- name: Determine next pre-release version
id: version
run: |
set -euo pipefail
tags=$(git tag -l)
latest=$(printf '%s\n' "$tags" | \
{ grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-pre\.[1-9][0-9]*)?$' || true; } | \
sort -V | tail -1)
if [[ ! "$latest" =~ ^(v[0-9]+\.[0-9]+\.[0-9]+)-pre\.([1-9][0-9]*)$ ]]; then
echo "No active pre-release series — skipping tag creation"
next=$(uv run --no-project python tasks/scripts/next-prerelease-tag.py --scheduled)
if [[ -z "${next}" ]]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
base=${BASH_REMATCH[1]}
number=${BASH_REMATCH[2]}
if ! git show-ref --verify --quiet "refs/tags/${base}-pre.1"; then
echo "No ${base}-pre.1 seed — skipping tag creation"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if git show-ref --verify --quiet "refs/tags/${base}"; then
echo "$base is already stable — skipping tag creation"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! git merge-base --is-ancestor "$latest" HEAD; then
echo "::error::Latest pre-release $latest is not an ancestor of main"
exit 1
fi
echo "Latest tag: $latest"
# Skip if no new commits since the latest tag
commit_count=$(git rev-list "${latest}..HEAD" --count)
echo "Commits since $latest: $commit_count"
if [ "$commit_count" -eq 0 ]; then
echo "No new commits since $latest — skipping tag creation"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
next="${base}-pre.$((number + 1))"
if git tag -l "$next" | grep -q .; then
echo "::error::Tag $next already exists"
exit 1
fi
echo "next=$next" >> "$GITHUB_OUTPUT"
echo "Next tag: $next"
- name: Create and push tag
- name: Create tag
if: steps.version.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
NEXT_TAG: ${{ steps.version.outputs.next }}
run: |
git tag "$NEXT_TAG"
git push origin "refs/tags/$NEXT_TAG"
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \
-f "ref=refs/tags/${NEXT_TAG}" -f "sha=$(git rev-parse HEAD)"
- name: Trigger Pre-release workflow
if: steps.version.outputs.skip != 'true'
+2 -2
View File
@@ -11,7 +11,7 @@ on:
required: true
type: string
expected-source-sha:
description: Qualified source commit that the release tag must resolve to
description: Expected source commit that the release tag must resolve to
required: false
default: ""
type: string
@@ -89,7 +89,7 @@ jobs:
run: |
set -euo pipefail
if [[ -n "${EXPECTED_SOURCE_SHA}" && "$(git rev-parse HEAD)" != "${EXPECTED_SOURCE_SHA}" ]]; then
echo "Release tag ${RELEASE_TAG} does not match the qualified commit ${EXPECTED_SOURCE_SHA}." >&2
echo "Release tag ${RELEASE_TAG} does not match the expected commit ${EXPECTED_SOURCE_SHA}." >&2
exit 1
fi
if [[ "${RELEASE_TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[1-9][0-9]*$ ]]; then
+78 -14
View File
@@ -10,43 +10,107 @@ on:
workflow_dispatch:
inputs:
tag:
description: "Pre-release tag to publish and qualify (e.g. v0.0.4-pre.1)"
required: true
description: "Existing prerelease tag (e.g. v0.0.4-pre.1); omit to create the next candidate on this branch"
required: false
type: string
permissions:
contents: read
jobs:
resolve:
name: Resolve Prerelease Tag
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: write
concurrency:
group: release-prerelease-tags
cancel-in-progress: false
queue: max
outputs:
tag: ${{ steps.tag.outputs.tag }}
source-sha: ${{ steps.tag.outputs.source_sha }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
version: "0.10.12"
python-version: "3.13"
enable-cache: false
- name: Resolve or create candidate tag
id: tag
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
EVENT_NAME: ${{ github.event_name }}
REF_TYPE: ${{ github.ref_type }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
tag="${RELEASE_TAG}"
if [[ "${EVENT_NAME}" == "push" ]]; then
tag="${REF_NAME}"
fi
if [[ -z "${tag}" ]]; then
test "${REF_TYPE}" = branch
tag=$(uv run --no-project python tasks/scripts/next-prerelease-tag.py)
source_sha=$(git rev-parse HEAD)
gh api --method POST "repos/${GITHUB_REPOSITORY}/git/refs" \
-f "ref=refs/tags/${tag}" -f "sha=${source_sha}"
else
if [[ ! "${tag}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+-pre\.[1-9][0-9]*$ ]]; then
echo "Unsupported prerelease tag: ${tag}" >&2
exit 1
fi
source_sha=$(git rev-parse "refs/tags/${tag}^{commit}")
fi
{
echo "tag=${tag}"
echo "source_sha=${source_sha}"
} >> "${GITHUB_OUTPUT}"
{
echo "## Prerelease candidate"
echo "- Tag: \`${tag}\`"
echo "- Source commit: \`${source_sha}\`"
} >> "${GITHUB_STEP_SUMMARY}"
build:
name: Build
needs: resolve
permissions:
actions: read
contents: read
packages: write
uses: ./.github/workflows/release-build.yml
with:
release-tag: ${{ inputs.tag || github.ref_name }}
release-tag: ${{ needs.resolve.outputs.tag }}
expected-source-sha: ${{ needs.resolve.outputs.source-sha }}
is-prerelease: true
secrets:
CACHIX_AUTH_TOKEN: ${{ secrets.CACHIX_AUTH_TOKEN }}
release-notes:
name: Preview Release Notes
needs: build
needs: [resolve, build]
permissions:
contents: read
pull-requests: read
uses: ./.github/workflows/release-notes.yml
with:
release-tag: ${{ inputs.tag || github.ref_name }}
release-tag: ${{ needs.resolve.outputs.tag }}
advisory: true
secrets:
LLM_INFERENCE_API_KEY: ${{ secrets.LLM_INFERENCE_API_KEY }}
publish:
name: Publish
needs: build
needs: [resolve, build]
permissions:
actions: read
contents: write
@@ -57,7 +121,7 @@ jobs:
artifact-metadata: write
uses: ./.github/workflows/release-publish.yml
with:
release-tag: ${{ inputs.tag || github.ref_name }}
release-tag: ${{ needs.resolve.outputs.tag }}
source-sha: ${{ needs.build.outputs.source_sha }}
semver: ${{ needs.build.outputs.semver }}
python-version: ${{ needs.build.outputs.python_version }}
@@ -68,7 +132,7 @@ jobs:
qualify:
name: Qualify
needs: build
needs: [resolve, build]
permissions:
actions: read
contents: read
@@ -77,7 +141,7 @@ jobs:
pull-requests: read
uses: ./.github/workflows/release-qualification.yml
with:
release-tag: ${{ inputs.tag || github.ref_name }}
release-tag: ${{ needs.resolve.outputs.tag }}
source-sha: ${{ needs.build.outputs.source_sha }}
is-prerelease: true
secrets:
@@ -86,7 +150,7 @@ jobs:
notify-prerelease-success:
name: Notify Prerelease Success
needs: [build, publish, qualify]
needs: [resolve, build, publish, qualify]
if: success()
runs-on: ubuntu-latest
timeout-minutes: 2
@@ -95,10 +159,10 @@ jobs:
- name: Send Slack notification
continue-on-error: true
env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
RELEASE_TAG: ${{ needs.resolve.outputs.tag }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_OPENSHELL_TRIAGE_WEBHOOK_URL }}
RUN_URL: ${{ format('{0}/{1}/actions/runs/{2}/attempts/{3}', github.server_url, github.repository, github.run_id, github.run_attempt) }}
TAG_URL: ${{ format('{0}/{1}/tree/{2}', github.server_url, github.repository, inputs.tag || github.ref_name) }}
TAG_URL: ${{ format('{0}/{1}/tree/{2}', github.server_url, github.repository, needs.resolve.outputs.tag) }}
run: |
set -euo pipefail
if [[ -z "${SLACK_WEBHOOK_URL}" ]]; then
@@ -114,7 +178,7 @@ jobs:
notify-prerelease-failure:
name: Notify Prerelease Failure
needs: [build, publish, qualify]
needs: [resolve, build, publish, qualify]
if: failure()
runs-on: ubuntu-latest
timeout-minutes: 2
@@ -127,7 +191,7 @@ jobs:
- name: Send Slack notification
continue-on-error: true
env:
RELEASE_TAG: ${{ inputs.tag || github.ref_name }}
RELEASE_TAG: ${{ needs.resolve.outputs.tag || inputs.tag || github.ref_name }}
PUBLICATION_RESULT: ${{ needs.publish.result }}
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_OPENSHELL_TRIAGE_WEBHOOK_URL }}
SLACK_MENTION: ${{ secrets.SLACK_OPENSHELL_TRIAGE_MENTION }}
+3 -3
View File
@@ -7,8 +7,8 @@ on:
workflow_dispatch:
inputs:
tag:
description: "Stable version to promote from its latest passing prerelease (e.g. v0.0.4)"
required: true
description: "Stable version (e.g. v0.0.4); omit to detect the highest prerelease version on this branch"
required: false
type: string
notes-only:
description: "Generate a release notes artifact without promoting or publishing"
@@ -87,7 +87,7 @@ jobs:
- name: Retain promotion record
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: stable-promotion-${{ inputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}
name: stable-promotion-${{ steps.candidate.outputs.tag }}-${{ github.run_id }}-${{ github.run_attempt }}
path: promotion.json
retention-days: 90
if-no-files-found: error