Add post-merge PR comment with merge-specific run links. #1
Workflow file for this run
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: Comment Post-Merge Run Links | ||
| on: | ||
| pull_request_target: | ||
| types: | ||
| - closed | ||
| permissions: | ||
| contents: read | ||
| pull-requests: write | ||
| jobs: | ||
| comment-run-links: | ||
| name: Comment links after merge | ||
| if: github.event.pull_request.merged == true && (github.event.pull_request.base.ref == 'main' || github.event.pull_request.base.ref == 'master') | ||
| runs-on: ${{ fromJSON(vars.GHA_RUNS_ON_JSON || '["self-hosted","linux"]') }} | ||
| steps: | ||
| - name: Resolve run links for this merge commit | ||
| id: resolve-links | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| CIRCLECI_TOKEN: ${{ secrets.CIRCLECI_TOKEN }} | ||
| REPO_OWNER: ${{ github.repository_owner }} | ||
| REPO_NAME: ${{ github.event.repository.name }} | ||
| RAW_MERGE_SHA: ${{ github.event.pull_request.merge_commit_sha }} | ||
| RAW_BASE_REF: ${{ github.event.pull_request.base.ref }} | ||
| MAX_ATTEMPTS: "36" | ||
| SLEEP_SECONDS: "10" | ||
| WORKFLOW_NAME_REGEX: ${{ vars.WORKFLOW_NAME_REGEX || 'pulumi|deploy|build' }} | ||
| WORKFLOW_OWNER_REPO: ${{ vars.WORKFLOW_OWNER_REPO || github.repository }} | ||
| CIRCLECI_PROJECT_SLUG: ${{ vars.CIRCLECI_PROJECT_SLUG || '' }} | ||
| run: | | ||
| set -euo pipefail | ||
| echo "workflow_url=" >> "$GITHUB_OUTPUT" | ||
| echo "circleci_url=" >> "$GITHUB_OUTPUT" | ||
| echo "notes=" >> "$GITHUB_OUTPUT" | ||
| echo "merge_sha=n/a" >> "$GITHUB_OUTPUT" | ||
| echo "base_ref=n/a" >> "$GITHUB_OUTPUT" | ||
| # Validate event values before using them in API requests. | ||
| if [[ "${RAW_BASE_REF}" != "main" && "${RAW_BASE_REF}" != "master" ]]; then | ||
| echo "notes=Unsupported base branch; expected main/master." >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| if [[ ! "${RAW_MERGE_SHA}" =~ ^[0-9a-f]{40}$ ]]; then | ||
| echo "notes=No valid merge commit SHA available on this PR event." >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| BASE_REF="${RAW_BASE_REF}" | ||
| MERGE_SHA="${RAW_MERGE_SHA}" | ||
| echo "merge_sha=${MERGE_SHA}" >> "$GITHUB_OUTPUT" | ||
| echo "base_ref=${BASE_REF}" >> "$GITHUB_OUTPUT" | ||
| attempt=1 | ||
| workflow_url="" | ||
| while [[ $attempt -le ${MAX_ATTEMPTS} ]]; do | ||
| runs_json="$(gh api "/repos/${WORKFLOW_OWNER_REPO}/actions/runs?event=push&head_sha=${MERGE_SHA}&per_page=50" 2>/dev/null || true)" | ||
| workflow_url="$(RUNS_JSON="${runs_json}" BASE_REF="${BASE_REF}" WORKFLOW_NAME_REGEX="${WORKFLOW_NAME_REGEX}" python3 - <<'PY_INNER' | ||
| import json | ||
| import os | ||
| import re | ||
| raw = os.environ.get("RUNS_JSON") or "{}" | ||
| base_ref = os.environ.get("BASE_REF") or "" | ||
| name_re = os.environ.get("WORKFLOW_NAME_REGEX") or "pulumi|deploy|build" | ||
| try: | ||
| data = json.loads(raw) | ||
| except Exception: | ||
| print("") | ||
| raise SystemExit(0) | ||
| runs = data.get("workflow_runs") or [] | ||
| if not runs: | ||
| print("") | ||
| raise SystemExit(0) | ||
| def branch_ok(run): | ||
| branch = (run.get("head_branch") or "") | ||
| return (not base_ref) or (branch == base_ref) | ||
| regex = re.compile(name_re, re.IGNORECASE) | ||
| filtered = [r for r in runs if branch_ok(r)] | ||
| preferred = [r for r in filtered if regex.search(r.get("name") or "")] | ||
| candidate = None | ||
| if preferred: | ||
| candidate = sorted(preferred, key=lambda x: x.get("run_number", 0), reverse=True)[0] | ||
| elif filtered: | ||
| candidate = sorted(filtered, key=lambda x: x.get("run_number", 0), reverse=True)[0] | ||
| print((candidate or {}).get("html_url", "")) | ||
| PY_INNER | ||
| )" | ||
| if [[ -n "${workflow_url}" ]]; then | ||
| break | ||
| fi | ||
| sleep "${SLEEP_SECONDS}" | ||
| attempt=$(( attempt + 1 )) | ||
| done | ||
| if [[ -n "${workflow_url}" ]]; then | ||
| echo "workflow_url=${workflow_url}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| circleci_slug="${CIRCLECI_PROJECT_SLUG}" | ||
| if [[ -z "${circleci_slug}" ]]; then | ||
| circleci_slug="${REPO_OWNER}/${REPO_NAME}" | ||
| fi | ||
| if [[ -z "${CIRCLECI_TOKEN:-}" ]]; then | ||
| echo "notes=CircleCI token not configured; skipping CircleCI run lookup." >> "$GITHUB_OUTPUT" | ||
| exit 0 | ||
| fi | ||
| attempt=1 | ||
| circleci_url="" | ||
| while [[ $attempt -le ${MAX_ATTEMPTS} ]]; do | ||
| pipelines_json="$(curl -fsSL -H "Circle-Token: ${CIRCLECI_TOKEN}" "https://circleci.com/api/v2/project/gh/${circleci_slug}/pipeline?branch=${BASE_REF}" 2>/dev/null || true)" | ||
| circleci_url="$(PIPELINES_JSON="${pipelines_json}" CIRCLECI_SLUG="${circleci_slug}" MERGE_SHA="${MERGE_SHA}" python3 - <<'PY_INNER' | ||
| import json | ||
| import os | ||
| raw = os.environ.get("PIPELINES_JSON") or "{}" | ||
| slug = os.environ.get("CIRCLECI_SLUG") or "" | ||
| merge_sha = os.environ.get("MERGE_SHA") or "" | ||
| try: | ||
| data = json.loads(raw) | ||
| except Exception: | ||
| print("") | ||
| raise SystemExit(0) | ||
| for item in data.get("items") or []: | ||
| vcs = item.get("vcs") or {} | ||
| if (vcs.get("revision") or "") == merge_sha: | ||
| number = item.get("number") | ||
| if number is not None: | ||
| print(f"https://app.circleci.com/pipelines/github/{slug}/{number}") | ||
| raise SystemExit(0) | ||
| print("") | ||
| PY_INNER | ||
| )" | ||
| if [[ -n "${circleci_url}" ]]; then | ||
| break | ||
| fi | ||
| sleep "${SLEEP_SECONDS}" | ||
| attempt=$(( attempt + 1 )) | ||
| done | ||
| if [[ -n "${circleci_url}" ]]; then | ||
| echo "circleci_url=${circleci_url}" >> "$GITHUB_OUTPUT" | ||
| fi | ||
| - name: Add run links to merged PR | ||
| uses: peter-evans/create-or-update-comment@v4 | ||
| with: | ||
| issue-number: ${{ github.event.pull_request.number }} | ||
| body: | | ||
| Merged to `${{ steps.resolve-links.outputs.base_ref }}`. | ||
| Follow-on runs for merge commit `${{ steps.resolve-links.outputs.merge_sha }}`: | ||
| - CircleCI: ${{ steps.resolve-links.outputs.circleci_url || 'Not found within polling window.' }} | ||
| - GitHub Actions workflow: ${{ steps.resolve-links.outputs.workflow_url || 'Not found within polling window.' }} | ||
| ${{ steps.resolve-links.outputs.notes }} | ||