fix: release cluster membership events on routing table convergence (… #72
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 | |
| on: | |
| push: | |
| branches: [main] | |
| paths: | |
| - changelogs/** | |
| workflow_dispatch: | |
| inputs: | |
| dry_run: | |
| description: "Parse changelogs/ and print the release notes, but skip tag/release/discussion creation." | |
| type: boolean | |
| default: true | |
| permissions: | |
| contents: write | |
| discussions: write | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| name: Cut release from changelogs | |
| runs-on: ubuntu-latest | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - name: Select changelog and parse release notes | |
| id: parse | |
| run: | | |
| set -euo pipefail | |
| version_file_re='^changelogs/v[0-9]+\.[0-9]+\.[0-9]+\.md$' | |
| header_re='^# (v[0-9]+\.[0-9]+\.[0-9]+) - ([0-9]{4}-[0-9]{2}-[0-9]{2})$' | |
| candidates_file=$(mktemp) | |
| trap 'rm -f "$candidates_file"' EXIT | |
| if [[ "${GITHUB_EVENT_NAME}" == "push" ]]; then | |
| before="${GITHUB_EVENT_BEFORE:-}" | |
| if [[ -z "$before" || "$before" =~ ^0+$ ]]; then | |
| before="$(git rev-parse "${GITHUB_SHA}^" 2>/dev/null || true)" | |
| fi | |
| if [[ -n "$before" ]] && git cat-file -e "${before}^{commit}" 2>/dev/null; then | |
| git diff --name-only --diff-filter=AM "$before" "$GITHUB_SHA" -- changelogs/ \ | |
| | grep -E "$version_file_re" > "$candidates_file" || true | |
| else | |
| # First commit / shallow history fallback: consider version files present at HEAD. | |
| git ls-files 'changelogs/v*.md' | grep -E "$version_file_re" > "$candidates_file" || true | |
| fi | |
| else | |
| # workflow_dispatch: consider every versioned changelog. | |
| git ls-files 'changelogs/v*.md' | grep -E "$version_file_re" > "$candidates_file" || true | |
| fi | |
| if [[ ! -s "$candidates_file" ]]; then | |
| echo "No versioned changelog candidates; nothing to do." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| selected="" | |
| selected_version="" | |
| while IFS= read -r file; do | |
| [[ -z "$file" || ! -f "$file" ]] && continue | |
| version="$(basename "$file" .md)" | |
| if git rev-parse --verify --quiet "refs/tags/${version}" >/dev/null; then | |
| echo "Skipping ${file}: tag ${version} already exists." | |
| continue | |
| fi | |
| header="$(awk 'NF { print; exit }' "$file")" | |
| if [[ ! "$header" =~ $header_re ]]; then | |
| echo "::error::${file}: first non-empty line must be '# ${version} - YYYY-MM-DD' (got: ${header})" | |
| exit 1 | |
| fi | |
| header_version="${BASH_REMATCH[1]}" | |
| if [[ "$header_version" != "$version" ]]; then | |
| echo "::error::${file}: header version ${header_version} does not match filename ${version}" | |
| exit 1 | |
| fi | |
| if [[ -z "$selected_version" ]] || \ | |
| printf '%s\n' "$selected_version" "$version" | sort -V | tail -n1 | grep -qx "$version"; then | |
| selected="$file" | |
| selected_version="$version" | |
| fi | |
| done < "$candidates_file" | |
| if [[ -z "$selected" ]]; then | |
| echo "All candidate changelog versions are already tagged; nothing to do." | |
| echo "skip=true" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| version="$selected_version" | |
| echo "Selected ${selected} for release ${version}" | |
| # Body after the H1 title; keep in-file ## / ### section headings as release notes. | |
| awk ' | |
| /^# / && !started { next } | |
| /^$/ && !started { next } | |
| { started = 1; buf = buf $0 "\n"; if (NF) final = buf } | |
| END { printf "%s", final } | |
| ' "$selected" > release-notes.md | |
| if [[ ! -s release-notes.md ]]; then | |
| echo "Extracted release notes from ${selected} are empty; refusing to publish." | |
| exit 1 | |
| fi | |
| prev=$(git tag --list 'v*' --sort=-v:refname | head -n 1 || true) | |
| if [[ -n "${prev}" ]]; then | |
| shas_file=$(mktemp) | |
| prs_file=$(mktemp) | |
| if ! gh api "repos/${GITHUB_REPOSITORY}/compare/${prev}...${GITHUB_SHA}" \ | |
| --jq '.commits[].sha' > "$shas_file"; then | |
| echo "::warning::compare ${prev}...${GITHUB_SHA} failed; Pull Requests section will be incomplete" | |
| fi | |
| while IFS= read -r sha; do | |
| [[ -z "$sha" ]] && continue | |
| if ! gh api "repos/${GITHUB_REPOSITORY}/commits/${sha}/pulls" \ | |
| --jq '.[] | select(.merged_at != null) | "\(.number)\t\(.title)\t\(.user.login)"' \ | |
| >> "$prs_file"; then | |
| echo "::warning::PR lookup failed for ${sha}; release notes may be missing entries" | |
| fi | |
| done < "$shas_file" | |
| rm -f "$shas_file" | |
| if [[ -s "$prs_file" ]]; then | |
| printf '\n## 🔀 Pull Requests\n\n' >> release-notes.md | |
| awk -F'\t' '!seen[$1]++ { printf "- #%s %s by @%s\n", $1, $2, $3 }' \ | |
| "$prs_file" >> release-notes.md | |
| fi | |
| rm -f "$prs_file" | |
| printf '\n**Full Changelog**: https://github.com/%s/compare/%s...%s\n' \ | |
| "${GITHUB_REPOSITORY}" "${prev}" "${version}" >> release-notes.md | |
| fi | |
| echo "version=${version}" >> "$GITHUB_OUTPUT" | |
| echo "changelog=${selected}" >> "$GITHUB_OUTPUT" | |
| echo "skip=false" >> "$GITHUB_OUTPUT" | |
| - name: Dry-run preview | |
| if: steps.parse.outputs.skip != 'true' && github.event.inputs.dry_run == 'true' | |
| run: | | |
| echo "::group::Would publish ${{ steps.parse.outputs.version }}" | |
| echo "Changelog file: ${{ steps.parse.outputs.changelog }}" | |
| echo "Target SHA: ${{ github.sha }}" | |
| echo "Tag / title: ${{ steps.parse.outputs.version }}" | |
| echo "Discussion category: Announcements" | |
| echo "----- release-notes.md -----" | |
| cat release-notes.md | |
| echo "----- end -----" | |
| echo "::endgroup::" | |
| echo "Dry run: no tag, no release, no discussion created." | |
| - name: Create GitHub release and announcement | |
| if: steps.parse.outputs.skip != 'true' && github.event.inputs.dry_run != 'true' | |
| run: | | |
| set -euo pipefail | |
| gh release create "${{ steps.parse.outputs.version }}" \ | |
| --target "${{ github.sha }}" \ | |
| --title "${{ steps.parse.outputs.version }}" \ | |
| --notes-file release-notes.md \ | |
| --discussion-category "Announcements" \ | |
| --verify-tag=false |