Skip to content

docs(durable): align local execution guidance (#226) #154

docs(durable): align local execution guidance (#226)

docs(durable): align local execution guidance (#226) #154

name: Release Please
on:
push:
branches:
- main
workflow_dispatch:
# Serialize all release runs so manual and push events cannot race.
concurrency:
group: release-please
cancel-in-progress: false
# The default GITHUB_TOKEN reads PR checks. Release writes use the scoped
# GitHub App token minted below.
permissions:
checks: read
contents: read
pull-requests: read
statuses: read
jobs:
release-please:
if: ${{ github.ref == 'refs/heads/main' }}
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# Mint a short-lived GitHub App installation token. Release Please must
# push tags/releases with a non-default token so publish-cli.yml runs:
# refs created with the default GITHUB_TOKEN do not trigger follow-on
# workflows. A GitHub App token replaces the personal access token
# without depending on an individual's credentials (VOL-367). The app id
# is a non-sensitive repo variable; the PEM private key is a secret. The
# app must be installed on this repo with Contents and Pull requests
# read/write.
- uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3.2.0
id: app-token
with:
app-id: ${{ vars.VOLCANO_APP_ID }}
private-key: ${{ secrets.VOLCANO_APP_KEY }}
# Down-scope the minted token to only what Release Please needs
# (contents + pull requests to open/merge the release PR and push
# the tag/release; issues for its release labels), rather than the
# app installation's full grant.
permission-contents: write
permission-pull-requests: write
permission-issues: write
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
token: ${{ steps.app-token.outputs.token }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
# Merge only non-major release PRs. The app waits for required checks,
# then uses its pull-request ruleset bypass so the standard review rule
# does not block automated releases. Existing pending release PRs are
# included so a workflow fix can recover a stalled release.
- name: Merge non-major release PRs
env:
CHECKS_TOKEN: ${{ github.token }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
GH_REPO: ${{ github.repository }}
RELEASE_PRS: ${{ steps.release.outputs.prs }}
run: |
set -euo pipefail
# Last released version is the source of truth for the current major.
# Fail closed: if the baseline is missing or malformed, merge nothing.
current_version="$(gh api "repos/${GH_REPO}/contents/.release-please-manifest.json" --jq '.content' | base64 -d | jq -r '.["."] // ""')"
if ! printf '%s' "$current_version" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+$'; then
echo "Current version '${current_version}' is missing or not X.Y.Z; leaving all release PRs for manual merge."
exit 0
fi
current_major="${current_version%%.*}"
expected_checks='["Analyze (actions)","Analyze (go)","Analyze (javascript-typescript)","Analyze (python)","Analyze (ruby)","CodeQL","check / check","check / localmode-e2e","license/cla"]'
{
jq -r 'if type == "array" and all(.[]; (.number | type) == "number" and .number > 0 and .number == (.number | floor)) then .[].number else error("invalid release PR output") end' <<< "${RELEASE_PRS:-[]}"
gh pr list --state open --label 'autorelease: pending' --json number --jq '.[].number'
} | sort -nu | while read -r number; do
metadata="$(gh pr view "$number" --json author,headRefName,headRefOid,title)"
author="$(jq -r '.author.login' <<< "$metadata")"
head_ref="$(jq -r '.headRefName' <<< "$metadata")"
head_oid="$(jq -r '.headRefOid' <<< "$metadata")"
title="$(jq -r '.title' <<< "$metadata")"
if [ "$author" != 'app/kong-volcano-app' ] || [[ "$head_ref" != release-please--* ]]; then
echo "PR #${number} is not a Volcano app release PR; leaving it unchanged."
continue
fi
new_version="$(printf '%s' "$title" | sed -n 's/^release: \([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)$/\1/p')"
if [ -z "$new_version" ]; then
echo "Could not parse a version from PR #${number} title '${title}'; leaving it for manual merge."
continue
fi
new_major="${new_version%%.*}"
if [ "$new_major" -gt "$current_major" ]; then
echo "Major bump ${current_version} -> ${new_version}: leaving release PR #${number} for manual merge."
continue
fi
echo "Waiting for checks on release PR #${number} (${current_version} -> ${new_version})."
checks_passed=false
for attempt in {1..50}; do
check_runs="$(GH_TOKEN="$CHECKS_TOKEN" gh api "repos/${GH_REPO}/commits/${head_oid}/check-runs?filter=latest&per_page=100")"
commit_statuses="$(GH_TOKEN="$CHECKS_TOKEN" gh api "repos/${GH_REPO}/commits/${head_oid}/status")"
check_results="$(jq -n --argjson runs "$check_runs" --argjson statuses "$commit_statuses" '
[
$runs.check_runs[] | {
name,
state: (if .status != "completed" then "pending"
elif .conclusion == "success" or .conclusion == "neutral" or .conclusion == "skipped" then "pass"
else "fail"
end)
}
] + [
($statuses.statuses | group_by(.context)[] | max_by(.updated_at)) | {
name: .context,
state: (if .state == "success" then "pass"
elif .state == "pending" then "pending"
else "fail"
end)
}
]
')"
if jq -e 'any(.[]; .state == "fail")' <<< "$check_results" >/dev/null; then
echo "A check failed for release PR #${number}; merging nothing." >&2
printf '%s\n' "$check_results" >&2
exit 1
fi
if jq -e --argjson expected "$expected_checks" '
($expected - [.[].name] | length == 0) and
(length > 0 and all(.[]; .state == "pass"))
' <<< "$check_results" >/dev/null; then
checks_passed=true
break
fi
echo "Checks are pending or not all registered yet (attempt ${attempt}/50)."
sleep 10
done
if [ "$checks_passed" != true ]; then
echo "Checks did not pass for release PR #${number}." >&2
printf '%s\n' "$check_results" >&2
exit 1
fi
echo "Squash-merging release PR #${number} with the Volcano app ruleset bypass."
gh pr merge "$number" --admin --squash --match-head-commit "$head_oid"
done