diff --git a/.github/workflows/webhook-check-test.yml b/.github/workflows/webhook-check-test.yml new file mode 100644 index 000000000..d485035f5 --- /dev/null +++ b/.github/workflows/webhook-check-test.yml @@ -0,0 +1,43 @@ +on: + push: + tags: + - v* + branches: + - master + paths: + - "**.go" + - "Makefile" + - "**.yaml" + - "**.yml" + - "test/**" + pull_request: + paths: + - "**.go" + - "Makefile" + - "**.yaml" + - "**.yml" + - "test/**" +name: Webhook Check Test +permissions: + contents: read +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Install Go + uses: actions/setup-go@bfdd3570ce990073878bf10f6b2d79082de49492 # v2.2.0 + with: + go-version: 1.20.x + - name: Checkout code + uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0 + - uses: actions/cache@8492260343ad570701412c2f464a5877dc76bace # v2 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + .bin + key: cache-${{ hashFiles('**/go.sum') }}-${{ hashFiles('.bin/*') }} + restore-keys: | + cache- + - name: Test + run: ./test/e2e-webook.sh diff --git a/fixtures/external/alertmanager.yaml b/fixtures/external/alertmanager.yaml index 6d6a1a6b3..d3b988ffd 100644 --- a/fixtures/external/alertmanager.yaml +++ b/fixtures/external/alertmanager.yaml @@ -8,6 +8,8 @@ spec: schedule: "@every 1m" webhook: name: my-webhook + token: + value: webhook-auth-token transform: expr: | results.json.alerts.map(r, @@ -17,6 +19,6 @@ spec: 'icon': 'alert', 'message': r.annotations.summary, 'description': r.annotations.description, - 'deletedAt': r.endsAt, + 'deletedAt': has(r.endsAt) ? r.endsAt : null, } ).toJSON() diff --git a/pkg/config.go b/pkg/config.go index 2770c6951..1cc299b5c 100644 --- a/pkg/config.go +++ b/pkg/config.go @@ -135,7 +135,7 @@ func ParseConfig(configfile string, datafile string) ([]v1.Canary, error) { return nil, err } - if len(config.Spec.GetAllChecks()) == 0 { + if len(config.Spec.GetAllChecks()) == 0 && config.Spec.Webhook == nil { // try just the specs: spec := v1.CanarySpec{} diff --git a/test/e2e-webook.sh b/test/e2e-webook.sh new file mode 100755 index 000000000..759904a8f --- /dev/null +++ b/test/e2e-webook.sh @@ -0,0 +1,166 @@ +#!/bin/bash + +set -e + +echo "::group::Prerequisites" +required_tools=("tr" "docker" "curl") +for tool in "${required_tools[@]}"; do + if ! command -v $tool &>/dev/null; then + echo "$tool is not installed. Please install it to run this script." + exit 1 + fi +done +echo "All the required tools are installed." +echo "::endgroup::" + +# https://cedwards.xyz/defer-for-shell/ +DEFER= +defer() { + DEFER="$*; ${DEFER}" + trap "{ $DEFER }" EXIT +} + +port=7676 +container_name=webhook_postgres + +## Summary +# - Fire up the canary checker HTTP server for webhook endpoint +# - Expect the checks to be created +# - Create resolved alert +# - Expect the checks to be deleted + +echo "::group::Provisioning" +echo "Starting up postgres database" +docker run --rm -p 5433:5432 --name $container_name -e POSTGRES_PASSWORD=mysecretpassword -d postgres:14 +defer docker container rm -f $container_name + +echo "Starting canary-checker in the background" +go run main.go serve --httpPort=$port \ + --db-migrations \ + --disable-postgrest -vvv \ + --db='postgres://postgres:mysecretpassword@localhost:5433/postgres?sslmode=disable' \ + --maxStatusCheckCount=1 \ + fixtures/external/alertmanager.yaml &>/dev/null & +PROC_ID=$! +echo "Started canary checker with PID $PROC_ID" + +timeout=30 +echo Waiting for the server to come up. timeout=$timeout seconds +for ((i = 1; i <= $timeout; i++)); do + if [ $(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$port/health") == "200" ]; then + echo "Server healthy (HTTP 200 OK)." + break + fi + + [ $i -eq $timeout ] && echo "Timeout: Server didn't return HTTP 200." && exit 1 + sleep 1 +done + +# Not sure why killing PROC_ID doesn't kill the HTTP server. +# So had to get the process id this way +process_id=$(lsof -nti:$port) +echo "Running on port $process_id" +defer "kill -9 $process_id" +echo "::endgroup::" + +echo "::group::Assertion" +echo Expect the check to be created by sync job +resp=$(docker exec $container_name psql "postgres://postgres:mysecretpassword@localhost:5432/postgres?sslmode=disable" -t -c "SELECT count(*) FROM checks WHERE name = 'my-webhook';" | tr -d '[:space:]') +if [ $resp -ne 1 ]; then + echo "Expected one webhook check to be created but $resp were created" + exit 1 +fi + +echo Attempt to call the webhook endpoint without the auth token +resp=$(curl -w "%{http_code}" -s -o /dev/null -X POST "localhost:$port/webhook/my-webhook") +if [ $resp -ne 401 ]; then + echo "Expected 401, got $resp" + exit 1 +fi + +echo Attempt to call the webhook endpoint with the auth token +resp=$(curl -w "%{http_code}" -s -o /dev/null -X POST "localhost:$port/webhook/my-webhook?token=webhook-auth-token") +if [ $resp -ne 200 ]; then + echo "Expected 200, got $resp" + exit 1 +fi + +echo Calling webhook endpoint with unresolved alert +curl -sL -o /dev/null -X POST -u 'admin@local:admin' --json '{ + "version": "4", + "status": "firing", + "alerts": [ + { + "status": "firing", + "name": "first", + "labels": { + "severity": "critical", + "alertName": "ServerDown", + "location": "DataCenterA" + }, + "annotations": { + "summary": "Server in DataCenterA is down", + "description": "This alert indicates that a server in DataCenterA is currently down." + }, + "startsAt": "2023-10-30T08:00:00Z", + "generatorURL": "http://example.com/generatorURL/serverdown", + "fingerprint": "a1b2c3d4e5f6" + }, + { + "status": "resolved", + "labels": { + "severity": "warning", + "alertName": "HighCPUUsage", + "location": "DataCenterB" + }, + "annotations": { + "summary": "High CPU Usage in DataCenterB", + "description": "This alert indicates that there was high CPU usage in DataCenterB, but it is now resolved." + }, + "startsAt": "2023-10-30T09:00:00Z", + "generatorURL": "http://example.com/generatorURL/highcpuusage", + "name": "second", + "fingerprint": "x1y2z3w4v5" + } + ] +}' localhost:$port/webhook/my-webhook?token=webhook-auth-token + +resp=$(docker exec $container_name psql 'postgres://postgres:mysecretpassword@localhost:5432/postgres?sslmode=disable' -t -c "SELECT count(*) FROM checks WHERE type = 'webhook' AND deleted_at IS NULL;" | tr -d '[:space:]') +if [ $resp -ne 3 ]; then + echo "Expected 2 new checks to be created but $resp were found" + exit 1 +fi + +echo Calling webhook endpoint with a resolved alert +curl -sL -o /dev/null -X POST -u 'admin@local:admin' --json '{ + "version": "4", + "status": "firing", + "alerts": [ + { + "status": "firing", + "name": "first", + "labels": { + "severity": "critical", + "alertName": "ServerDown", + "location": "DataCenterA" + }, + "annotations": { + "summary": "Server in DataCenterA is down", + "description": "This alert indicates that a server in DataCenterA is currently down." + }, + "startsAt": "2023-10-30T08:00:00Z", + "generatorURL": "http://example.com/generatorURL/serverdown", + "fingerprint": "a1b2c3d4e5f6", + "endsAt": "2023-10-30T09:15:00Z" + } + ] +}' localhost:$port/webhook/my-webhook?token=webhook-auth-token + +resp=$(docker exec $container_name psql 'postgres://postgres:mysecretpassword@localhost:5432/postgres?sslmode=disable' -t -c "SELECT name FROM checks WHERE type = 'webhook' AND deleted_at IS NOT NULL;" | tr -d '[:space:]') +if [ "$resp" != 'firsta1b2c3d4e5f6' ]; then + echo "Expected "firsta1b2c3d4e5f6" check to be deleted." + exit 1 +fi + +echo "::endgroup::" +exit 0