docs(durable): align local execution guidance (#226) #154
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |