diff --git a/deploy/README.md b/deploy/README.md index f202d75..1fac03a 100644 --- a/deploy/README.md +++ b/deploy/README.md @@ -66,6 +66,11 @@ and backup check in with Sentry Crons when /root/.waving-hands-sentry-cron-rollu and /root/.waving-hands-sentry-cron hold their check-in URLs (the ingest URL with the project's cron path and public key), so a missed night is noticed. +To have every new issue, regression and reappearance posted to Slack the way +wizwar's are, run `deploy/sentry-slack-alert.sh [#channel]` once from your own +shell with `SENTRY_TOKEN` (an org auth token with alerts:write) and +`SLACK_CHANNEL_ID` set; the token never leaves the shell. + Before every deploy, `deploy/verify-ledgers.sh ` fetches every production ledger and replays it with the local engine, comparing each room with what the server shows. A ledger the new engine refuses or replays differently stops the diff --git a/deploy/sentry-slack-alert.sh b/deploy/sentry-slack-alert.sh new file mode 100755 index 0000000..8172b09 --- /dev/null +++ b/deploy/sentry-slack-alert.sh @@ -0,0 +1,73 @@ +#!/bin/bash +# Route every Waving Hands 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] +# 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:-waving-hands}" +CHANNEL="${1:-#waving-hands-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}" +CHANNEL_ID="${SLACK_CHANNEL_ID:?SLACK_CHANNEL_ID=C... is required (the Slack ID of the channel)}" +PROJECT_ID="${SENTRY_PROJECT_ID:-4512133232787456}" +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": "Waving Hands → 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/"