name: Shared - Notifications
# In-pipeline Slack notifier. Called as the final job of a *-main-pipeline with the# aggregate `status`; everything else is auto-derived from context.## Delivery: Slack bot token via chat.postMessage (secret SLACK_BOT_TOKEN, propagated# with `secrets: inherit`). Channel is chosen by run kind from org-level `vars`:# - PR runs -> vars.SLACK_CHANNEL_PR (failures only)# - deploy runs -> vars.SLACK_CHANNEL_DEPLOY (every outcome)# - fallback -> vars.SLACK_CHANNELon: workflow_call: inputs: status: description: 'Aggregate pipeline status: success, failure, cancelled' required: true type: string environment: description: 'Deployment environment (develop, cert, prod)' required: false type: string default: ''
# Least privilege for what this workflow actually does. Without it the# job inherits whatever the calling repository's default happens to be,# which on older repositories is write-all.permissions: contents: read
jobs: notify: name: Slack Notification runs-on: ubuntu-latest timeout-minutes: 5
steps: - name: Send Slack notification env: SLACK_BOT_TOKEN: ${{ secrets.SLACK_BOT_TOKEN }} CHANNEL_PR: ${{ vars.SLACK_CHANNEL_PR }} CHANNEL_DEPLOY: ${{ vars.SLACK_CHANNEL_DEPLOY }} CHANNEL_DEFAULT: ${{ vars.SLACK_CHANNEL }} GH_TOKEN: ${{ github.token }} STATUS: ${{ inputs.status }} ENVIRON: ${{ inputs.environment }} REPO: ${{ github.repository }} SERVER_URL: ${{ github.server_url }} EVENT_NAME: ${{ github.event_name }} BRANCH: ${{ github.ref_name }} ACTOR: ${{ github.actor }} RUN_ID: ${{ github.run_id }} COMMIT_MSG: ${{ github.event.head_commit.message }} PR_NUMBER: ${{ github.event.pull_request.number }} PR_URL: ${{ github.event.pull_request.html_url }} PR_TITLE: ${{ github.event.pull_request.title }} PR_AUTHOR: ${{ github.event.pull_request.user.login }} PR_REVIEWERS: ${{ join(github.event.pull_request.requested_reviewers.*.login, ', ') }} PR_TEAMS: ${{ join(github.event.pull_request.requested_teams.*.slug, ', ') }} run: | set -euo pipefail
if [ -z "$SLACK_BOT_TOKEN" ]; then echo "::warning::SLACK_BOT_TOKEN secret not set, skipping notification" exit 0 fi
# --- classify PR vs deploy, pick channel --- if [ "$EVENT_NAME" = pull_request ]; then KIND="pr"; CHANNEL="${CHANNEL_PR:-$CHANNEL_DEFAULT}"; EMOJI="🔀" else KIND="deploy"; CHANNEL="${CHANNEL_DEPLOY:-$CHANNEL_DEFAULT}"; EMOJI="" fi
# PR runs post only when they fail. Every push to a PR re-runs the pipeline, # so a green post per push buries the one that needs attention; a cancelled # run is the stale one a newer push replaced. Deploy runs post on any # outcome: that channel is the record of what shipped. if [ "$KIND" = pr ] && [ "$STATUS" != failure ]; then echo "PR run finished with status=${STATUS}; PR runs notify on failure only." exit 0 fi if [ -z "$CHANNEL" ]; then echo "::warning::No Slack channel variable set for kind=$KIND, skipping" exit 0 fi
# --- status → emoji / color --- case "$STATUS" in success) EMOJI="${EMOJI:-✅}"; COLOR="#36a64f" ;; failure) EMOJI="❌"; COLOR="#E01E5A" ;; cancelled) EMOJI="⚠️"; COLOR="#ECB22E" ;; *) EMOJI="${EMOJI:-ℹ️}"; COLOR="#1264A3" ;; esac
if [ "$KIND" = pr ]; then TITLE="Pull Request #${PR_NUMBER} — ${REPO##*/}" else TITLE="Pipeline ${STATUS} — ${REPO##*/}" fi
# --- on failure, list the stage(s) that failed (best effort) --- FAILED_JOBS="" if [ "$STATUS" = failure ]; then FAILED_JOBS=$(gh api "repos/${REPO}/actions/runs/${RUN_ID}/jobs" --paginate 2>/dev/null \ | jq -r '[.jobs[] | select(.conclusion=="failure") | .name] | join(", ")' 2>/dev/null || echo "") fi
# --- header + info fields --- BLOCKS=$(jq -n --arg h "${EMOJI} ${TITLE}" \ '[{"type":"header","text":{"type":"plain_text","text":$h,"emoji":true}}]')
FIELDS=$(jq -n \ --arg repo "<${SERVER_URL}/${REPO}|${REPO}>" \ --arg branch "\`${BRANCH}\`" \ --arg status "${STATUS}" \ --arg actor "${ACTOR}" \ '[{"type":"mrkdwn","text":("*Repository:*\n"+$repo)}, {"type":"mrkdwn","text":("*Branch:*\n"+$branch)}, {"type":"mrkdwn","text":("*Status:*\n"+$status)}, {"type":"mrkdwn","text":("*Actor:*\n"+$actor)}]') if [ -n "$ENVIRON" ]; then FIELDS=$(echo "$FIELDS" | jq --arg e "$ENVIRON" '. += [{"type":"mrkdwn","text":("*Environment:*\n"+$e)}]') fi BLOCKS=$(echo "$BLOCKS" | jq --argjson f "$FIELDS" '. += [{"type":"section","fields":$f}]')
# --- PR details / commit description --- if [ "$KIND" = pr ]; then REVS="${PR_REVIEWERS}" [ -n "$PR_TEAMS" ] && REVS="${REVS:+$REVS, }${PR_TEAMS}" BLOCKS=$(echo "$BLOCKS" | jq \ --arg pr "*Pull Request:*\n<${PR_URL}|#${PR_NUMBER} ${PR_TITLE}>" \ --arg author "*Author:*\n${PR_AUTHOR:-N/A}" \ --arg rev "*Reviewers:*\n${REVS:-—}" \ '. += [{"type":"section","fields":[ {"type":"mrkdwn","text":$pr}, {"type":"mrkdwn","text":$author}, {"type":"mrkdwn","text":$rev} ]}]') else DESC=$(printf '%s' "$COMMIT_MSG" | head -n1) [ -n "$DESC" ] && BLOCKS=$(echo "$BLOCKS" | jq --arg d "*Description:*\n${DESC}" \ '. += [{"type":"section","text":{"type":"mrkdwn","text":$d}}]') fi
# --- failed stage(s) --- [ -n "$FAILED_JOBS" ] && BLOCKS=$(echo "$BLOCKS" | jq --arg s "*Failed stage(s):*\n${FAILED_JOBS}" \ '. += [{"type":"section","text":{"type":"mrkdwn","text":$s}}]')
# --- action button --- if [ "$KIND" = pr ]; then BTN_TEXT="View PR"; BTN_URL="$PR_URL" else BTN_TEXT="View Run"; BTN_URL="${SERVER_URL}/${REPO}/actions/runs/${RUN_ID}" fi BLOCKS=$(echo "$BLOCKS" | jq --arg url "$BTN_URL" --arg t "$BTN_TEXT" \ '. += [{"type":"actions","elements":[{"type":"button","text":{"type":"plain_text","text":$t},"url":$url}]}]')
PAYLOAD=$(jq -n --arg ch "$CHANNEL" --arg text "${EMOJI} ${TITLE}" \ --arg color "$COLOR" --argjson blocks "$BLOCKS" \ '{channel: $ch, text: $text, attachments: [{color: $color, blocks: $blocks}]}')
RESP=$(curl -s -X POST https://slack.com/api/chat.postMessage \ -H "Authorization: Bearer ${SLACK_BOT_TOKEN}" \ -H "Content-Type: application/json; charset=utf-8" \ -d "$PAYLOAD")
if [ "$(echo "$RESP" | jq -r '.ok')" = "true" ]; then echo "Slack notification sent (kind=$KIND, channel=$CHANNEL)" else echo "::warning::Slack chat.postMessage failed: $(echo "$RESP" | jq -r '.error // "unknown"')" fiShared (cross-cutting)·Reusable workflow·on: workflow_call
Shared Notifications
Shared - Notifications
.github/workflows/shared-notifications.yml