From c8969d1fdeb2af54076d7a6006da374c5bb8c106 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Mon, 11 May 2026 01:03:17 +0300 Subject: ci: backport update-version-tags hardening to sagitta (rolls up #1953 + #1958) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Brings sagitta's `.github/workflows/update-version-tags.yml` to the final post-#1958 rolling-side state. This rolls up the entire hardening series in one shot because Mergify's per-PR backport could not cherry-pick #1958 cleanly onto a base that never received #1953 (the prior backport, #1955, was closed). Final workflow shape: - Two-job pipeline. `check_head` resolves the per-branch tag name and filters stale "Re-run jobs" replays via a HEAD-equivalence check against the live branch HEAD; sets `is_current` output. - `retag` depends on `check_head`, runs only when `is_current=='true'`, carries job-level `concurrency: {group: version-tag-, cancel-in-progress: false}` so back-to-back pushes serialize in commit order. - `retag` re-validates HEAD inside its own job before PATCH so that GitHub's "Re-run failed jobs" (which can re-execute retag in isolation) cannot move the tag to a stale `github.sha`. - Tag mutation: PATCH-first, fallback to POST only on HTTP 404, fail loud on any other gh-api error. Verbatim copy of `origin/rolling:.github/workflows/update-version-tags.yml` substantive content, with the LTS branch's "keep all three copies in sync" header preserved (rolling has a context7-refresh-specific header because that workflow is rolling-only). 🤖 Generated by [robots](https://vyos.io) --- .github/workflows/update-version-tags.yml | 86 +++++++++++++++++++++++++------ 1 file changed, 71 insertions(+), 15 deletions(-) (limited to '.github/workflows') diff --git a/.github/workflows/update-version-tags.yml b/.github/workflows/update-version-tags.yml index 9cefef61..8a5b838f 100644 --- a/.github/workflows/update-version-tags.yml +++ b/.github/workflows/update-version-tags.yml @@ -14,19 +14,16 @@ on: permissions: contents: write -concurrency: - group: version-tag-${{ github.ref_name }} - cancel-in-progress: true - jobs: - retag: + check_head: runs-on: ubuntu-latest + outputs: + is_current: ${{ steps.head_check.outputs.is_current }} + tag: ${{ steps.branch_tag.outputs.tag }} steps: - - name: Move version tag to pushed SHA + - name: Resolve version tag for branch + id: branch_tag env: - GH_TOKEN: ${{ github.token }} - REPO: ${{ github.repository }} - SHA: ${{ github.sha }} BRANCH: ${{ github.ref_name }} run: | set -euo pipefail @@ -36,11 +33,70 @@ jobs: sagitta) TAG=1.4 ;; *) echo "Unexpected branch: $BRANCH" >&2; exit 1 ;; esac + echo "tag=$TAG" >> "$GITHUB_OUTPUT" + + - name: Check event SHA matches current branch HEAD + id: head_check + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + SHA: ${{ github.sha }} + BRANCH: ${{ github.ref_name }} + run: | + set -euo pipefail + HEAD_SHA="$(gh api "repos/$REPO/branches/$BRANCH" --jq '.commit.sha')" + if [ "$HEAD_SHA" != "$SHA" ]; then + echo "Skipping stale run: event SHA=$SHA, current $BRANCH HEAD=$HEAD_SHA" + echo "is_current=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "is_current=true" >> "$GITHUB_OUTPUT" + + retag: + needs: check_head + if: needs.check_head.outputs.is_current == 'true' + runs-on: ubuntu-latest + concurrency: + # Per-branch job concurrency serializes tag moves while letting stale + # re-runs exit before they contend for the single pending slot. + group: version-tag-${{ github.ref_name }} + cancel-in-progress: false + steps: + - name: Move version tag to pushed SHA + env: + GH_TOKEN: ${{ github.token }} + REPO: ${{ github.repository }} + SHA: ${{ github.sha }} + BRANCH: ${{ github.ref_name }} + TAG: ${{ needs.check_head.outputs.tag }} + run: | + set -euo pipefail + + # Re-validate HEAD inside retag too: GitHub's "Re-run failed jobs" + # can re-execute retag in isolation without re-running check_head, + # and the branch HEAD may have advanced since the original run. + # Without this guard, a re-run of just retag would PATCH the tag + # to a stale github.sha. + HEAD_SHA="$(gh api "repos/$REPO/branches/$BRANCH" --jq '.commit.sha')" + if [ "$HEAD_SHA" != "$SHA" ]; then + echo "Skipping stale retag: event SHA=$SHA, current $BRANCH HEAD=$HEAD_SHA" + exit 0 + fi + echo "Pointing tag '$TAG' at $SHA (branch $BRANCH)" - if gh api "repos/$REPO/git/ref/tags/$TAG" >/dev/null 2>&1; then - gh api -X PATCH "repos/$REPO/git/refs/tags/$TAG" \ - -f sha="$SHA" -F force=true - else - gh api -X POST "repos/$REPO/git/refs" \ - -f ref="refs/tags/$TAG" -f sha="$SHA" + + # PATCH the tag if it exists; create on 404; fail loud on any other + # gh-api error (auth, rate-limit, 5xx) instead of silently falling + # through to POST. + patch_err="" + if ! patch_err="$(gh api -X PATCH "repos/$REPO/git/refs/tags/$TAG" \ + -f sha="$SHA" -F force=true 2>&1)"; then + if grep -q "HTTP 404" <<<"$patch_err"; then + gh api -X POST "repos/$REPO/git/refs" \ + -f ref="refs/tags/$TAG" -f sha="$SHA" + else + echo "$patch_err" >&2 + exit 1 + fi fi -- cgit v1.2.3