|
13 | 13 | # See the License for the specific language governing permissions and |
14 | 14 | # limitations under the License. |
15 | 15 |
|
| 16 | +# Maintains one draft GitHub Release per active release branch (7.0.x, 7.1.x, |
| 17 | +# 7.2.x, 8.0.x, ...). Each branch produces an independent draft because the |
| 18 | +# release-drafter config in .github/release-drafter.yml combines |
| 19 | +# `filter-by-commitish: true`, `filter-by-range: ~MAJOR.MINOR.0`, and |
| 20 | +# `tag-prefix: v` so that drafts created on one branch never leak into another. |
| 21 | +# |
| 22 | +# Companion config: .github/release-drafter.yml |
16 | 23 | name: "Release - Drafter" |
17 | 24 | on: |
18 | | - issues: |
19 | | - types: [closed,reopened] |
| 25 | + # Runs on every push to a release branch so the draft for that branch is |
| 26 | + # always up to date with the latest merged PRs. |
20 | 27 | push: |
21 | 28 | branches: |
22 | 29 | - '[0-9]+.[0-9]+.x' |
| 30 | + # Runs on PRs whose BASE branch is a release branch so the autolabeler can |
| 31 | + # apply labels (bug/feature/docs/...) and the draft picks up new PRs as soon |
| 32 | + # as they are opened. Feature-to-feature PRs (e.g. fix/foo -> feat/bar) |
| 33 | + # are intentionally excluded - they cannot affect any release. |
23 | 34 | pull_request: |
24 | 35 | types: [opened, reopened, synchronize, labeled] |
| 36 | + branches: |
| 37 | + - '[0-9]+.[0-9]+.x' |
| 38 | + # Manual recovery: rerun against any branch (e.g. to recreate a draft after |
| 39 | + # one was accidentally deleted, or to seed an initial draft on a new branch). |
25 | 40 | workflow_dispatch: |
26 | | -# queue jobs and only allow 1 run per branch due to the likelihood of hitting GitHub resource limits |
| 41 | + |
| 42 | +# Per-branch concurrency. Critically: this group MUST NOT collide with the |
| 43 | +# `release-pipeline-${branch}` group used by .github/workflows/release.yml. |
| 44 | +# The release pipeline has manual approval gates (`environment: release`, |
| 45 | +# `environment: docs`, `environment: sdkman`) which routinely keep a release |
| 46 | +# run in `waiting` state for HOURS or DAYS until a maintainer approves the |
| 47 | +# next stage. When the drafter shared that group, every push to a release |
| 48 | +# branch queued behind those waiting runs - producing drafter runs of |
| 49 | +# 1400-2000+ minutes that ultimately got cancelled, leaving drafts stale. |
| 50 | +# |
| 51 | +# Drafter and release.yml never touch the same release object: the drafter |
| 52 | +# maintains a DRAFT for the *next* version (e.g. v7.0.12), while release.yml |
| 53 | +# uploads assets to the *current published* tag (e.g. v7.0.11). Splitting the |
| 54 | +# concurrency groups is therefore safe. |
| 55 | +# |
| 56 | +# `cancel-in-progress: true`: if multiple pushes land on the same branch in |
| 57 | +# quick succession, only the latest matters - the latest run sees every PR |
| 58 | +# the older one would have seen, so cancelling pending runs is correct. |
27 | 59 | concurrency: |
28 | | - group: release-pipeline-${{ github.event.pull_request.base.ref || github.ref_name }} |
29 | | - cancel-in-progress: false |
| 60 | + group: release-drafter-${{ github.event.pull_request.base.ref || github.ref_name }} |
| 61 | + cancel-in-progress: true |
| 62 | + |
30 | 63 | jobs: |
31 | 64 | update_release_draft: |
| 65 | + name: "Update Release Draft" |
32 | 66 | permissions: |
33 | | - # write permission is required to create a github release |
| 67 | + # Required to create or update the draft GitHub Release |
34 | 68 | contents: write |
35 | | - # write permission is required for autolabeler |
| 69 | + # Required for the autolabeler to add labels to PRs |
36 | 70 | pull-requests: write |
37 | 71 | runs-on: ubuntu-latest |
38 | 72 | steps: |
| 73 | + # release-drafter's `filter-by-range` keeps the action looking only at |
| 74 | + # releases whose tag falls inside this branch's MAJOR.MINOR series. This |
| 75 | + # is what prevents 7.0.x's draft from being computed against 7.1.x's |
| 76 | + # tags (or vice versa). It is derived dynamically from the branch name |
| 77 | + # so this workflow file works identically on every release branch. |
39 | 78 | - name: "🔢 Derive semver range from branch" |
40 | 79 | id: version |
41 | 80 | run: | |
| 81 | + set -euo pipefail |
42 | 82 | BRANCH="${{ github.event.pull_request.base.ref || github.ref_name }}" |
43 | | - if [[ "$BRANCH" =~ ^[0-9]+\.[0-9]+\.x$ ]]; then |
44 | | - echo "range=~${BRANCH%.x}.0" >> "$GITHUB_OUTPUT" |
| 83 | + if [[ "$BRANCH" =~ ^([0-9]+)\.([0-9]+)\.x$ ]]; then |
| 84 | + RANGE="~${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.0" |
| 85 | + echo "Branch $BRANCH -> filter-by-range $RANGE" |
| 86 | + echo "range=$RANGE" >> "$GITHUB_OUTPUT" |
| 87 | + else |
| 88 | + echo "Branch $BRANCH is not a release branch; skipping range filter (will only match the configured prerelease/commitish filters)" |
| 89 | + echo "range=" >> "$GITHUB_OUTPUT" |
45 | 90 | fi |
| 91 | +
|
| 92 | + # Pinned to v7.3.1 by commit SHA per the ASF security policy (matches |
| 93 | + # the pinning convention already used by other ASF-approved actions in |
| 94 | + # this repo). v7.2.1 or later is required because it ships PR #1593 - the |
| 95 | + # bug fix for `initial-commits-since` being silently ignored when set only |
| 96 | + # in the release-drafter.yml config (not also as a workflow input). |
| 97 | + # Our release-drafter.yml relies on that exact config-only path to |
| 98 | + # bound history walking on brand-new release branches like 7.2.x. |
| 99 | + # |
| 100 | + # Earlier minor releases also contributed key options we depend on: |
| 101 | + # - v7.1.0 (PR #1451): adds the `initial-commits-since` config option. |
| 102 | + # - v7.0.0 (PR #1470): adds the `history-limit` config option. |
| 103 | + # |
| 104 | + # Bump checklist: when updating, verify the new tag is signed, read the |
| 105 | + # release notes for any breaking changes to the config schema, and |
| 106 | + # update both the SHA and the `# v...` comment together. Resolve the |
| 107 | + # commit SHA via: |
| 108 | + # gh api repos/release-drafter/release-drafter/git/tags/<tag-sha> \ |
| 109 | + # --jq '.object.sha' |
46 | 110 | - name: "📝 Update Release Draft" |
| 111 | + id: drafter |
47 | 112 | uses: release-drafter/release-drafter@e1247478eabc9f6d9cf5ec2b3547469b0e1d2767 # v7.3.1 |
| 113 | + # Drafting release notes is a best-effort, non-critical task - a |
| 114 | + # transient GitHub API hiccup must not turn every PR check red. The |
| 115 | + # explicit verification step below catches the case where the action |
| 116 | + # silently produced no draft (e.g. rate-limit exhaustion). |
48 | 117 | continue-on-error: true |
49 | 118 | with: |
| 119 | + # Explicit `commitish` is critical on `pull_request` events: without |
| 120 | + # it, release-drafter would default to `refs/pull/N/merge` (a |
| 121 | + # virtual ref) which the GitHub API rejects when creating a release, |
| 122 | + # producing the "Validation Failed: target_commitish invalid" error |
| 123 | + # historically seen on PRs (see INFRA-27602). |
50 | 124 | commitish: ${{ github.event.pull_request.base.ref || github.ref_name }} |
51 | 125 | filter-by-range: ${{ steps.version.outputs.range }} |
52 | 126 | env: |
53 | 127 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 128 | + |
| 129 | + # Surface failures that `continue-on-error` would otherwise hide. The |
| 130 | + # release-drafter action exposes the resulting release id as an output; |
| 131 | + # an empty value means it failed to create or update a draft. We log a |
| 132 | + # loud warning (visible in the workflow summary and as a GitHub Actions |
| 133 | + # annotation) but do NOT fail the job - PR checks must stay green for |
| 134 | + # transient API issues, while still alerting maintainers something is |
| 135 | + # wrong if drafts go missing for multiple consecutive runs. |
| 136 | + - name: "🔎 Verify draft was created or updated" |
| 137 | + if: always() |
| 138 | + env: |
| 139 | + DRAFT_ID: ${{ steps.drafter.outputs.id }} |
| 140 | + DRAFT_TAG: ${{ steps.drafter.outputs.tag_name }} |
| 141 | + DRAFT_NAME: ${{ steps.drafter.outputs.name }} |
| 142 | + DRAFT_URL: ${{ steps.drafter.outputs.html_url }} |
| 143 | + DRAFT_OUTCOME: ${{ steps.drafter.outcome }} |
| 144 | + BRANCH: ${{ github.event.pull_request.base.ref || github.ref_name }} |
| 145 | + run: | |
| 146 | + set -euo pipefail |
| 147 | + { |
| 148 | + echo "## Release Drafter Result" |
| 149 | + echo "" |
| 150 | + echo "- Branch: \`${BRANCH}\`" |
| 151 | + echo "- Step outcome: \`${DRAFT_OUTCOME}\`" |
| 152 | + } >> "$GITHUB_STEP_SUMMARY" |
| 153 | + if [[ -z "${DRAFT_ID:-}" ]]; then |
| 154 | + { |
| 155 | + echo "- Status: ⚠️ **No draft created/updated**" |
| 156 | + echo "" |
| 157 | + echo "release-drafter ran but did not produce a draft release id." |
| 158 | + echo "Common causes: GitHub API rate limit exhausted, no prior" |
| 159 | + echo "release matched the commitish/range/tag-prefix filters, or" |
| 160 | + echo "the action errored. Check the previous step's logs." |
| 161 | + } >> "$GITHUB_STEP_SUMMARY" |
| 162 | + echo "::warning title=Release draft missing::release-drafter produced no draft for branch ${BRANCH}. Step outcome was '${DRAFT_OUTCOME}'. See workflow summary." |
| 163 | + else |
| 164 | + { |
| 165 | + echo "- Status: ✅ Draft maintained" |
| 166 | + echo "- Tag: \`${DRAFT_TAG}\`" |
| 167 | + echo "- Name: ${DRAFT_NAME}" |
| 168 | + echo "- URL: ${DRAFT_URL}" |
| 169 | + } >> "$GITHUB_STEP_SUMMARY" |
| 170 | + echo "Draft ${DRAFT_TAG} (id ${DRAFT_ID}) maintained for branch ${BRANCH}: ${DRAFT_URL}" |
| 171 | + fi |
0 commit comments