diff --git a/.github/workflows/cerberus-notify.yml b/.github/workflows/cerberus-notify.yml new file mode 100644 index 00000000..4f68cc76 --- /dev/null +++ b/.github/workflows/cerberus-notify.yml @@ -0,0 +1,105 @@ +name: Cerberus notify + +# Ported from the `notify-on-master-fail` job in the retired .gitlab-ci.yml as +# part of the GitLab -> GitHub migration (STAC-25142 / STAC-25519). The GitHub +# migration in STAC-25420 dropped it, which left a failing master completely +# silent -- image publishing broke on 2026-07-23 and went unnoticed for 12 days +# (STAC-25510). +# +# Called by ci.yml as a terminal job so a failed master build reaches the team's +# Slack CI channel. Cerberus is the internal notify/block Lambda (source: +# https://gitlab.com/stackvista/devops/cerberus); the calling convention here +# follows `cerberus-block-on-master-fail` in StackVista/stackstate's +# .github/workflows/ci.yml, with `platform: github` in the context so Cerberus +# builds GitHub pipeline/commit URLs rather than GitLab ones. +# +# `action: notify`, never `action: block`. Policy for migrated repos is notify by +# default; blocking locks the branch (`lock_branch`) on every master failure, +# additionally requires the Cerberus GitHub App to be installed on the repo, and +# mutates Pulumi-managed branch protection out from under it. +# +# The repo's own .cerberus/cerberus_notify_failure.sh is not reused -- it was +# deleted with the GitLab files, was built around GitLab's CI_* variables, and +# predates the `platform` field. +# +# Prerequisite: CERBERUS_LAMBDA_URL must reach this repo as a REPO-level secret. +# The org-level copy is visibility=private, which excludes this PUBLIC repo, and +# widening it is not an option: the Cerberus endpoint is unauthenticated, so the +# URL is the whole capability. Until the pulumi-infra change applies, this +# workflow warns and exits 0 rather than adding a second red job to an already +# failed run -- the annotation is the signal. +# +# The Slack channel is deliberately not sent. Cerberus resolves it as +# `util.GetOrDefault(req.Context, "channel", s.Channel)`, and GetOrDefault treats +# an empty or whitespace value as absent, so omitting `channel` falls back to the +# Lambda's own SLACK_CHANNEL. + +on: + workflow_call: + inputs: + suite: + description: Suite label shown in the Slack message, e.g. build. + required: true + type: string + secrets: + # `required: false`. A caller passing `${{ secrets.X }}` for a secret the + # repo does not hold yields an empty string, which GitHub rejects as "not + # provided" against a required secret and fails the call before the run + # step's guard can warn -- the failure mode this workflow exists to avoid. + CERBERUS_LAMBDA_URL: + required: false + +# Nothing here reads the repository; the payload is built entirely from the +# github context. +permissions: {} + +jobs: + notify: + name: Notify Slack via Cerberus + runs-on: ubuntu-24.04 + timeout-minutes: 5 + steps: + - name: Post the failure to Cerberus + env: + CERBERUS_LAMBDA_URL: ${{ secrets.CERBERUS_LAMBDA_URL }} + REPOSITORY: ${{ github.repository }} + BRANCH: ${{ github.ref_name }} + PIPELINE: ${{ github.run_id }} + COMMIT_SHA: ${{ github.sha }} + COMMIT_MESSAGE: ${{ github.event.head_commit.message }} + SUITE: ${{ inputs.suite }} + run: | + set -euo pipefail + + if [ -z "${CERBERUS_LAMBDA_URL}" ]; then + echo "::warning title=Cerberus not configured::CERBERUS_LAMBDA_URL is not visible to this repo, so the ${SUITE} failure was not reported to Slack. Needs the repo-level secret from pulumi-infra (STAC-25519)." + exit 0 + fi + + COMMIT_TITLE=$(printf '%s' "${COMMIT_MESSAGE}" | head -n1) + + curl --verbose --fail \ + -X POST "${CERBERUS_LAMBDA_URL}" \ + -H "Content-Type: application/json" \ + -d "$(jq -n \ + --arg repo "${REPOSITORY}" \ + --arg branch "${BRANCH}" \ + --arg pipeline "${PIPELINE}" \ + --arg sha "${COMMIT_SHA}" \ + --arg title "${COMMIT_TITLE}" \ + --arg suite "${SUITE}" \ + '{ + action: "notify", + context: { + platform: "github", + "project.id": $repo, + "project.slug": $repo, + "project.name": "StackState Process Agent", + branch: $branch, + pipeline: $pipeline, + "commit.sha": $sha, + "commit.title": $title, + suite: $suite + } + }' + )" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index fbf5b60a..6879fa09 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -331,3 +331,20 @@ jobs: exit 1 fi echo "All required process-agent jobs passed." + + # Terminal job, master only. ci-success aggregates every other job, so hanging + # the notification off it means one funnel for all failures rather than a + # notify job per pipeline job. Replaces `notify-on-master-fail` from the + # retired .gitlab-ci.yml (STAC-25519). + cerberus-notify: + name: Report failure to Slack (Cerberus) + needs: ci-success + if: >- + failure() && + github.ref == 'refs/heads/master' && + github.event_name == 'push' + uses: ./.github/workflows/cerberus-notify.yml + with: + suite: build + secrets: + CERBERUS_LAMBDA_URL: ${{ secrets.CERBERUS_LAMBDA_URL }}