Gardener - Notify Slack #140
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: Gardener - Notify Slack | |
| # Runs after `Gardener - Notify Event` completes. Posts a summary to Slack, | |
| # and for Dependabot-opened PRs also applies the `devtools-gardener` label | |
| # (so Dependabot PRs show up in the gardener flow without a human having | |
| # to label them). | |
| # | |
| # The workflow_run trigger runs this job in the default-branch context with | |
| # full GITHUB_TOKEN permissions and Actions secret access — this is what | |
| # lets it succeed for Dependabot-opened PRs, where the upstream event | |
| # workflow can't label or reach secrets directly. Labels applied here via | |
| # GITHUB_TOKEN do not trigger another `labeled` run (loop prevention), so | |
| # Slack only gets one post per PR. | |
| on: | |
| workflow_run: | |
| workflows: ['Gardener - Notify Event'] | |
| types: [completed] | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| actions: read | |
| jobs: | |
| notify: | |
| # `conclusion == success` also covers runs where the capture job was | |
| # skipped by its `if` gate (no matching label, etc.) — in that case | |
| # no artifact was uploaded, so the download step below no-ops. | |
| if: github.event.workflow_run.conclusion == 'success' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download event payload | |
| id: download | |
| continue-on-error: true | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: gardener-event | |
| run-id: ${{ github.event.workflow_run.id }} | |
| github-token: ${{ secrets.GITHUB_TOKEN }} | |
| - name: Add devtools-gardener label to Dependabot PR | |
| if: steps.download.outcome == 'success' | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_REPO: ${{ github.repository }} | |
| run: | | |
| ACTION=$(jq -r '.action' event.json) | |
| LOGIN=$(jq -r '.pull_request.user.login // ""' event.json) | |
| # Only the Dependabot-opened path needs labeling here. On manual | |
| # `labeled` events the label is already on the PR/issue. | |
| if [ "$ACTION" != "opened" ] || [ "$LOGIN" != "dependabot[bot]" ]; then | |
| exit 0 | |
| fi | |
| NUMBER=$(jq -r '.pull_request.number' event.json) | |
| gh pr edit "$NUMBER" --add-label devtools-gardener | |
| - name: Post to Slack | |
| if: steps.download.outcome == 'success' | |
| continue-on-error: true | |
| env: | |
| SLACK_BOT_TOKEN: ${{ secrets.SLACK_GARDENER_BOT_TOKEN }} | |
| SLACK_CHANNEL_ID: ${{ vars.GARDENER_SLACK_CHANNEL_ID }} | |
| run: | | |
| KIND=$(jq -r 'if has("pull_request") then "PR" else "Issue" end' event.json) | |
| # Pull the body out, truncate, then convert GitHub Markdown to | |
| # Slack mrkdwn. Links and fenced code blocks are stashed before | |
| # the HTML-escape pass so their contents survive verbatim (a `&` | |
| # inside a URL must stay raw, and code content shouldn't be | |
| # mangled). Blockquote `> ` markers are also stashed so the | |
| # `>` → `>` escape doesn't break them. Everything else is | |
| # HTML-escaped so user-supplied `<`, `>`, `&` can't collide | |
| # with Slack link syntax or injected mentions like <!channel>. | |
| BODY=$(jq -r '(.issue // .pull_request).body // ""' event.json) | |
| if [ ${#BODY} -gt 1000 ]; then | |
| BODY="${BODY:0:1000}…" | |
| fi | |
| BODY=$(printf '%s' "$BODY" | perl -0777 -pe ' | |
| my @u; | |
| s{\[([^\]]+)\]\(([^)]+)\)}{push @u, $2; "\x01$#u\x02$1\x03"}ge; | |
| my @c; | |
| s{^```[^\n]*\n(.*?)\n```$}{push @c, $1; "\x04$#c\x05"}gems; | |
| s/^> /\x06/gm; | |
| s/^#{1,6}\s+(.+)$/*$1*/gm; | |
| s/\*\*(.+?)\*\*/*$1*/g; | |
| s/^(\s*)- \[x\]\s+/$1✓ /gm; | |
| s/^(\s*)[-*]\s+/$1• /gm; | |
| s/&/&/g; | |
| s/</</g; | |
| s/>/>/g; | |
| s/\x06/> /g; | |
| s{\x01(\d+)\x02(.*?)\x03}{"<$u[$1]|$2>"}ge; | |
| s{\x04(\d+)\x05}{"```\n$c[$1]\n```"}ge; | |
| ') | |
| jq \ | |
| --arg channel "$SLACK_CHANNEL_ID" \ | |
| --arg kind "$KIND" \ | |
| --arg body "$BODY" \ | |
| ' | |
| def escape: gsub("&";"&") | gsub("<";"<") | gsub(">";">"); | |
| (.issue // .pull_request) as $i | |
| | ([$i.labels[]?.name | select(. != "devtools-gardener")] | |
| | map("`\(.)`") | join(" ")) as $labels | |
| | (if $kind == "PR" | |
| then " · \($i.changed_files) files, +\($i.additions)/-\($i.deletions)" | |
| + (if $i.draft then " · draft" else "" end) | |
| else "" end) as $meta | |
| | [ "*<\($i.html_url)|\($kind) #\($i.number)>* — \(($i.title | escape))", | |
| "_opened by \($i.user.login)\($meta)_" ] | |
| + (if $body != "" then [$body] else [] end) | |
| + (if $labels != "" then [$labels] else [] end) | |
| | join("\n") as $msg | |
| | { channel: $channel, text: "\($kind) #\($i.number): \($i.title)", | |
| blocks: [{ type: "section", text: { type: "mrkdwn", text: $msg } }] } | |
| ' event.json | curl -sf -X POST \ | |
| -H "Authorization: Bearer $SLACK_BOT_TOKEN" \ | |
| -H 'Content-type: application/json; charset=utf-8' \ | |
| -d @- https://slack.com/api/chat.postMessage |