#!/usr/bin/env bash # Route every Hnefatafl issue to a Slack channel: new issues (any level or # priority), regressions, and reappearances. Creates one Sentry workflow # via the alerts API. The token never leaves your shell: # SENTRY_TOKEN=sntryu_... SLACK_CHANNEL_ID=C... deploy/sentry-slack-alert.sh [#channel] # SLACK_CHANNEL_ID is the channel's Slack ID; the name defaults to # #hnefatafl-notifications and is shown in Sentry beside the ID. # The token needs the alerts:write scope (an org auth token from # https://locallygrownnet.sentry.io/settings/auth-tokens/), and Slack must # already be connected to the org under Settings → Integrations. set -euo pipefail : "${SENTRY_TOKEN:?SENTRY_TOKEN=sntryu_... is required (alerts:write)}" ORG="${SENTRY_ORG:-locallygrownnet}" PROJECT="${SENTRY_PROJECT:-hnefatafl}" CHANNEL="${1:-#hnefatafl-notifications}" API="https://us.sentry.io/api/0/organizations/$ORG" AUTH="Authorization: Bearer $SENTRY_TOKEN" # The Slack action takes the channel's ID as well as its name; the # project's error detector is looked up by project id; a workflow is bound # to its detectors by PUT after creation. SLACK_ID="${SENTRY_SLACK_INTEGRATION:-345334}" : "${SLACK_CHANNEL_ID:?SLACK_CHANNEL_ID=C... is required (the Slack ID of the channel)}" CHANNEL_ID="$SLACK_CHANNEL_ID" PROJECT_ID="${SENTRY_PROJECT_ID:-4512136791130112}" echo "Slack integration $SLACK_ID; project $PROJECT_ID; channel $CHANNEL ($CHANNEL_ID)" DETECTOR_ID=$(curl -s "$API/detectors/?project=$PROJECT_ID" -H "$AUTH" | python3 -c ' import json, sys rows = json.load(sys.stdin) for d in (rows if isinstance(rows, list) else []): if str(d.get("projectId")) == sys.argv[1] and d.get("type") == "error": print(d["id"]); break' "$PROJECT_ID") [ -n "$DETECTOR_ID" ] || { echo "no error detector for project $PROJECT_ID" >&2; exit 1; } PAYLOAD=$(CHANNEL="$CHANNEL" CHANNEL_ID="$CHANNEL_ID" SLACK_ID="$SLACK_ID" DETECTOR_ID="$DETECTOR_ID" python3 -c ' import json, os print(json.dumps({ "name": "Hnefatafl → Slack", "enabled": True, "environment": None, "config": {"frequency": 0}, "triggers": { "logicType": "any-short", "conditions": [ {"type": "first_seen_event", "comparison": True, "conditionResult": True}, {"type": "regression_event", "comparison": True, "conditionResult": True}, {"type": "reappeared_event", "comparison": True, "conditionResult": True}, ], "actions": [], }, "actionFilters": [{ "logicType": "all", "conditions": [], "actions": [{ "type": "slack", "integrationId": int(os.environ["SLACK_ID"]), "data": {}, "config": {"targetType": "specific", "targetIdentifier": os.environ["CHANNEL_ID"], "targetDisplay": os.environ["CHANNEL"]}, "status": "active", }], }], "detectorIds": [int(os.environ["DETECTOR_ID"])], }))') echo "creating the workflow…" RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$API/workflows/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD") CODE=$(printf '%s' "$RESPONSE" | tail -1) BODY=$(printf '%s' "$RESPONSE" | sed '$d') if [ "$CODE" != "201" ]; then echo "Sentry answered $CODE:" >&2; echo "$BODY" >&2; exit 1 fi WORKFLOW_ID=$(printf '%s' "$BODY" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("id",""))') echo "created workflow $WORKFLOW_ID" # Binding to the detector is done by PUT, the whole workflow resent. curl -s -o /dev/null -w "bound to detector $DETECTOR_ID: %{http_code}\n" -X PUT "$API/workflows/$WORKFLOW_ID/" -H "$AUTH" -H "Content-Type: application/json" -d "$PAYLOAD" echo "done: https://$ORG.sentry.io/monitors/alerts/$WORKFLOW_ID/"