Saltar al contenido
mypipelines
Shared (cross-cutting)·Reusable workflow·on: workflow_call

Shared Notifications

Shared - Notifications

.github/workflows/shared-notifications.yml

.github/workflows/shared-notifications.yml
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_CHANNEL
on:
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"')"
fi