From 926455c1e69a1282342e9e0b83f84de7b89360fd Mon Sep 17 00:00:00 2001 From: Manas Srivastava Date: Wed, 13 May 2026 08:40:02 +0530 Subject: [PATCH] infra: bake jq fixes into NR dashboard/alert JSON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit F's apply.sh in the infra repo patched 3 schema gaps at apply time via jq. Terraform reads the same JSON but wouldn't run the adapter — the source JSON drifted from one applier to the other. Bake the fixes into the source so apply.sh + Terraform consume identical clean structures. Fixes baked: 1. Dashboards: accountIds:[0] → accountIds:["${NEW_RELIC_ACCOUNT_ID}"] substitution token across all 4 dashboards (28 total occurrences). 2. Alerts: drop "type": "NRQL" — NerdGraph rejects it on NrqlConditionStaticInput; the mutation name encodes the type. 3. Alert policy: add policies/instant-api.json describing the umbrella policy + add policyName="instant-api alerts" linking field to each alert JSON, replacing apply.sh's inline find-or-create logic. apply.sh already handles this defensively — its jq walk() rewrites .accountIds regardless of the previous value, and del(.type) is a no-op when the field is absent. No companion infra PR needed. Tests (infra/newrelic/tests/bake_test.sh): 31 assertions covering JSON parse, accountIds token presence + count per dashboard, absence of "type" on alerts, policyName cross-ref between alerts and policy file, and policy file shape. All pass. Co-Authored-By: Claude Opus 4.7 (1M context) --- infra/newrelic/README.md | 35 ++++-- infra/newrelic/alerts/error-rate-high.json | 2 +- infra/newrelic/alerts/nats-down.json | 2 +- infra/newrelic/alerts/p95-latency-high.json | 2 +- infra/newrelic/alerts/worker-stalled.json | 2 +- infra/newrelic/dashboards/api-overview.json | 14 +-- infra/newrelic/dashboards/deploy.json | 14 +-- infra/newrelic/dashboards/provisioning.json | 12 +- infra/newrelic/dashboards/worker.json | 16 +-- infra/newrelic/policies/instant-api.json | 4 + infra/newrelic/tests/bake_test.sh | 133 ++++++++++++++++++++ 11 files changed, 197 insertions(+), 39 deletions(-) create mode 100644 infra/newrelic/policies/instant-api.json create mode 100755 infra/newrelic/tests/bake_test.sh diff --git a/infra/newrelic/README.md b/infra/newrelic/README.md index dae279e..92fcaa7 100644 --- a/infra/newrelic/README.md +++ b/infra/newrelic/README.md @@ -18,12 +18,23 @@ infra/newrelic/ p95-latency-high.json# p95 > 500ms over 5m worker-stalled.json # no jobs processed in 10m nats-down.json # >=3 NATS error logs in 5m + policies/ + instant-api.json # umbrella policy alerts attach to via policyName + tests/ + bake_test.sh # schema-shape regression tests (run pre-merge) ``` Each JSON file is a stand-alone dashboard or alert condition payload in the shape -NR's NerdGraph schema expects. The `accountIds: [0]` placeholders in dashboard -queries are rewritten by the apply tooling (see below) to the real account ID -before the API call. +NR's NerdGraph schema expects. The `accountIds: ["${NEW_RELIC_ACCOUNT_ID}"]` +substitution token in dashboard queries is rewritten by the apply tooling (see +below) to the real account ID before the API call. Both `apply.sh` and the +Terraform path read the same source files — neither needs a special pre-flight +adapter step. + +Alert JSON files include a `policyName` field that links them to the umbrella +policy declared in `policies/instant-api.json`. There is no `type` discriminator +on alert JSON — the mutation name (`alertsNrqlConditionStaticCreate`) encodes +that, and including `"type": "NRQL"` causes NerdGraph to reject the payload. ## Required env @@ -59,8 +70,8 @@ provider "newrelic" { resource "newrelic_one_dashboard_json" "api_overview" { json = replace( file("${path.module}/dashboards/api-overview.json"), - "\"accountIds\": [0]", - "\"accountIds\": [${var.account_id}]", + "\"${NEW_RELIC_ACCOUNT_ID}\"", + tostring(var.account_id), ) } @@ -163,13 +174,23 @@ see `infra/k8s/README.md` (owned by track 6) for that procedure. ## Validation ```bash -# Every JSON file must parse -find infra/newrelic -name '*.json' -exec jq empty {} \; +# Every JSON file must parse + the schema-shape bake assertions must hold +bash infra/newrelic/tests/bake_test.sh # NRQL queries are not lintable offline — copy a query into the NR UI's # "Query your data" tool to sanity-check syntax after any edit. ``` +`bake_test.sh` enforces: + +- Every dashboard/alert/policy file parses as JSON. +- Dashboards use the `"${NEW_RELIC_ACCOUNT_ID}"` substitution token, never + the legacy `[0]` placeholder. +- Alerts have no top-level `"type"` field — NerdGraph rejects it on the + `NrqlConditionStaticInput` mutation. +- Every alert has `policyName: "instant-api alerts"` linking to + `policies/instant-api.json`. + ## Dependencies These payloads assume the metrics/log fields wired up by: diff --git a/infra/newrelic/alerts/error-rate-high.json b/infra/newrelic/alerts/error-rate-high.json index bfcbeaf..0a02090 100644 --- a/infra/newrelic/alerts/error-rate-high.json +++ b/infra/newrelic/alerts/error-rate-high.json @@ -1,6 +1,6 @@ { "name": "instant-api — error rate > 1% (5m)", - "type": "NRQL", + "policyName": "instant-api alerts", "description": "Page when instant-api error rate exceeds 1% sustained for 5 minutes. Sourced from Transaction events emitted by track 3's Fiber NR middleware.", "enabled": true, "nrql": { diff --git a/infra/newrelic/alerts/nats-down.json b/infra/newrelic/alerts/nats-down.json index 925cc30..f09e4aa 100644 --- a/infra/newrelic/alerts/nats-down.json +++ b/infra/newrelic/alerts/nats-down.json @@ -1,6 +1,6 @@ { "name": "instant-api — NATS connection failures", - "type": "NRQL", + "policyName": "instant-api alerts", "description": "Page when the api logs NATS connection failures. Triggers on any error log mentioning NATS — covers JetStream unreachable, auth failure, or stream not found. Threshold deliberately low (>=3 in 5min) to catch real outages without paging on transient blips.", "enabled": true, "nrql": { diff --git a/infra/newrelic/alerts/p95-latency-high.json b/infra/newrelic/alerts/p95-latency-high.json index 86ac452..0c62d15 100644 --- a/infra/newrelic/alerts/p95-latency-high.json +++ b/infra/newrelic/alerts/p95-latency-high.json @@ -1,6 +1,6 @@ { "name": "instant-api — p95 latency > 500ms (5m)", - "type": "NRQL", + "policyName": "instant-api alerts", "description": "Page when instant-api p95 latency exceeds 500ms sustained for 5 minutes. Tracks user-visible slowness on provisioning + dashboard read paths.", "enabled": true, "nrql": { diff --git a/infra/newrelic/alerts/worker-stalled.json b/infra/newrelic/alerts/worker-stalled.json index 0ff27c7..8d71246 100644 --- a/infra/newrelic/alerts/worker-stalled.json +++ b/infra/newrelic/alerts/worker-stalled.json @@ -1,6 +1,6 @@ { "name": "instant-worker — no jobs processed in 10m", - "type": "NRQL", + "policyName": "instant-api alerts", "description": "Page when the worker has processed zero jobs for 10 minutes. Catches stalled River pollers, deadlocks against the platform DB, and pod crash loops missed by k8s readiness probes. Source: Log records emitted by River job middleware in track 4.", "enabled": true, "nrql": { diff --git a/infra/newrelic/dashboards/api-overview.json b/infra/newrelic/dashboards/api-overview.json index 9e7d5b7..b593129 100644 --- a/infra/newrelic/dashboards/api-overview.json +++ b/infra/newrelic/dashboards/api-overview.json @@ -14,7 +14,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT rate(count(*), 1 minute) FROM Transaction WHERE appName LIKE 'instant-api%' TIMESERIES AUTO" } ], @@ -28,7 +28,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentage(count(*), WHERE error IS true) AS 'error rate' FROM Transaction WHERE appName LIKE 'instant-api%' TIMESERIES AUTO" } ], @@ -42,7 +42,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentile(duration, 95) * 1000 AS 'p95 ms', percentile(duration, 99) * 1000 AS 'p99 ms' FROM Transaction WHERE appName LIKE 'instant-api%' TIMESERIES AUTO" } ], @@ -56,7 +56,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) AS 'requests', percentile(duration, 95) * 1000 AS 'p95 ms', percentage(count(*), WHERE error IS true) AS 'err %' FROM Transaction WHERE appName LIKE 'instant-api%' FACET name LIMIT 25" } ], @@ -70,7 +70,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Transaction WHERE appName LIKE 'instant-api%' AND httpResponseCode >= '500' FACET name LIMIT 20" } ], @@ -84,7 +84,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT apdex(duration, t: 0.5) FROM Transaction WHERE appName LIKE 'instant-api%'" } ], @@ -102,7 +102,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'api' AND level = 'ERROR' FACET commit_id TIMESERIES AUTO LIMIT 5" } ], diff --git a/infra/newrelic/dashboards/deploy.json b/infra/newrelic/dashboards/deploy.json index 8909dbc..d4b3989 100644 --- a/infra/newrelic/dashboards/deploy.json +++ b/infra/newrelic/dashboards/deploy.json @@ -14,7 +14,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT filter(count(*), WHERE deploy_status = 'success') AS 'success', filter(count(*), WHERE deploy_status = 'fail') AS 'fail' FROM Log WHERE service = 'api' AND deploy_status IS NOT NULL SINCE 24 hours ago" } ], @@ -28,7 +28,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentile(build_duration_seconds, 50) AS 'p50 s', percentile(build_duration_seconds, 95) AS 'p95 s' FROM Log WHERE service = 'api' AND build_duration_seconds IS NOT NULL TIMESERIES AUTO" } ], @@ -42,7 +42,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentile(duration, 95) * 1000 AS 'p95 ms' FROM Transaction WHERE appName LIKE 'instant-api%' AND name LIKE '%/deploy%' FACET name TIMESERIES AUTO" } ], @@ -56,7 +56,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'api' AND deploy_status = 'fail' FACET fail_reason LIMIT 10" } ], @@ -70,7 +70,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT uniqueCount(deploy_id) FROM Log WHERE service = 'api' AND deploy_status = 'running' SINCE 5 minutes ago" } ], @@ -84,7 +84,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'api' AND message LIKE '%deploy.created%' FACET tier SINCE 7 days ago" } ], @@ -98,7 +98,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Transaction WHERE appName LIKE 'instant-api%' AND name LIKE '%/redeploy' TIMESERIES 1 day" } ], diff --git a/infra/newrelic/dashboards/provisioning.json b/infra/newrelic/dashboards/provisioning.json index 3c443f6..7c4b57b 100644 --- a/infra/newrelic/dashboards/provisioning.json +++ b/infra/newrelic/dashboards/provisioning.json @@ -14,7 +14,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT (sum(newrelic.timeslice.value) / (sum(newrelic.timeslice.value) + filter(sum(newrelic.timeslice.value), WHERE metricTimesliceName = 'Custom/Provision/Fail'))) * 100 AS 'success %' FROM Metric WHERE metricTimesliceName = 'Custom/Provision/Success' SINCE 1 hour ago" } ], @@ -32,7 +32,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT sum(newrelic.timeslice.value) AS 'count' FROM Metric WHERE metricTimesliceName IN ('Custom/Provision/Success', 'Custom/Provision/Fail') FACET metricTimesliceName TIMESERIES AUTO" } ], @@ -46,7 +46,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentile(duration, 50) * 1000 AS 'median ms', percentile(duration, 95) * 1000 AS 'p95 ms' FROM Transaction WHERE appName LIKE 'instant-api%' AND name IN ('POST /db/new', 'POST /cache/new', 'POST /nosql/new', 'POST /queue/new', 'POST /storage/new', 'POST /webhook/new') FACET name TIMESERIES AUTO" } ], @@ -60,7 +60,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'api' AND level = 'ERROR' AND message LIKE '%provision%' FACET resource_type LIMIT 10" } ], @@ -74,7 +74,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT sum(newrelic.timeslice.value) FROM Metric WHERE metricTimesliceName = 'Custom/Resource/Expired' TIMESERIES AUTO" } ], @@ -88,7 +88,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'api' AND message LIKE '%provisioned%' FACET tier" } ], diff --git a/infra/newrelic/dashboards/worker.json b/infra/newrelic/dashboards/worker.json index 4a3a68e..5b07acc 100644 --- a/infra/newrelic/dashboards/worker.json +++ b/infra/newrelic/dashboards/worker.json @@ -14,7 +14,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT rate(count(*), 1 minute) FROM Log WHERE service = 'worker' AND message = 'job.completed' TIMESERIES AUTO" } ], @@ -28,7 +28,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'worker' AND message IN ('job.completed', 'job.failed') FACET message TIMESERIES AUTO" } ], @@ -42,7 +42,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT rate(count(*), 1 minute) FROM Log WHERE service = 'worker' AND message = 'job.retried' TIMESERIES AUTO" } ], @@ -56,7 +56,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'worker' AND message = 'job.completed' FACET job_kind TIMESERIES AUTO" } ], @@ -70,7 +70,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT percentile(duration_ms, 95) FROM Log WHERE service = 'worker' AND message = 'job.completed' AND duration_ms IS NOT NULL FACET job_kind TIMESERIES AUTO" } ], @@ -84,7 +84,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT (max(timestamp) - min(timestamp)) / 1000 AS 'seconds since expire run' FROM Log WHERE service = 'worker' AND job_kind = 'expire' AND message = 'job.completed' SINCE 1 hour ago" } ], @@ -102,7 +102,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'worker' AND message = 'job.failed' FACET job_kind SINCE 24 hours ago LIMIT 10" } ], @@ -116,7 +116,7 @@ "rawConfiguration": { "nrqlQueries": [ { - "accountIds": [0], + "accountIds": ["${NEW_RELIC_ACCOUNT_ID}"], "query": "SELECT count(*) FROM Log WHERE service = 'worker' AND level = 'ERROR' TIMESERIES AUTO" } ], diff --git a/infra/newrelic/policies/instant-api.json b/infra/newrelic/policies/instant-api.json new file mode 100644 index 0000000..409fc80 --- /dev/null +++ b/infra/newrelic/policies/instant-api.json @@ -0,0 +1,4 @@ +{ + "name": "instant-api alerts", + "incidentPreference": "PER_CONDITION_AND_TARGET" +} diff --git a/infra/newrelic/tests/bake_test.sh b/infra/newrelic/tests/bake_test.sh new file mode 100755 index 0000000..b76c198 --- /dev/null +++ b/infra/newrelic/tests/bake_test.sh @@ -0,0 +1,133 @@ +#!/usr/bin/env bash +# +# bake_test.sh — verify that the dashboards/*.json, alerts/*.json, and +# policies/*.json source files have F's three jq adapter fixes baked in: +# +# 1. Dashboards: accountIds use the "${NEW_RELIC_ACCOUNT_ID}" token, +# not the literal [0] placeholder. +# 2. Alerts: no top-level "type": "NRQL" discriminator — NerdGraph +# rejects it on the NrqlConditionStaticInput mutation. +# 3. Alert policy: policies/instant-api.json exists with the expected +# fields, and every alert in alerts/ links to it via "policyName". +# +# Run from anywhere — the script discovers its own infra/newrelic root. +# +# Exit codes: +# 0 all assertions pass +# 1 one or more assertions failed + +set -uo pipefail + +SCRIPT_DIR="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )" +ROOT="$( cd -- "$SCRIPT_DIR/.." &> /dev/null && pwd )" +DASHBOARDS_DIR="$ROOT/dashboards" +ALERTS_DIR="$ROOT/alerts" +POLICIES_DIR="$ROOT/policies" +POLICY_FILE="$POLICIES_DIR/instant-api.json" + +PASS=0 +FAIL=0 + +ok() { PASS=$((PASS + 1)); printf ' ok %s\n' "$1"; } +fail() { FAIL=$((FAIL + 1)); printf ' FAIL %s\n' "$1" >&2; } + +command -v jq >/dev/null 2>&1 || { echo "missing dep: jq" >&2; exit 2; } + +DASHBOARDS=(api-overview deploy provisioning worker) +ALERTS=(error-rate-high nats-down p95-latency-high worker-stalled) + +echo "==> Dashboards parse + no accountIds:[0] residue" +for name in "${DASHBOARDS[@]}"; do + f="$DASHBOARDS_DIR/$name.json" + if [ ! -f "$f" ]; then fail "$name.json missing"; continue; fi + if jq empty "$f" >/dev/null 2>&1; then + ok "$name.json parses" + else + fail "$name.json does not parse" + continue + fi + # Must not contain the literal [0] placeholder anywhere. + if grep -q '"accountIds": *\[ *0 *\]' "$f"; then + fail "$name.json still contains accountIds:[0] (bake not applied)" + else + ok "$name.json has no accountIds:[0] residue" + fi + # Must contain the substitution token for every nrqlQueries entry. + expected=$(jq '[.. | objects | select(has("nrqlQueries")) | .nrqlQueries | length] | add // 0' "$f") + actual=$(grep -c '"accountIds": *\["\${NEW_RELIC_ACCOUNT_ID}"\]' "$f" || true) + if [ "$expected" = "$actual" ] && [ "$actual" -gt 0 ]; then + ok "$name.json has $actual accountIds tokens (matches nrqlQueries count)" + else + fail "$name.json token count mismatch: expected=$expected actual=$actual" + fi +done + +echo +echo "==> Alerts parse + no type:NRQL + policyName link" +for name in "${ALERTS[@]}"; do + f="$ALERTS_DIR/$name.json" + if [ ! -f "$f" ]; then fail "$name.json missing"; continue; fi + if jq empty "$f" >/dev/null 2>&1; then + ok "$name.json parses" + else + fail "$name.json does not parse" + continue + fi + has_type=$(jq -r 'has("type")' "$f") + if [ "$has_type" = "false" ]; then + ok "$name.json has no \"type\" field (NRQL discriminator removed)" + else + fail "$name.json still has \"type\" field" + fi + policyName=$(jq -r '.policyName // ""' "$f") + if [ "$policyName" = "instant-api alerts" ]; then + ok "$name.json has policyName=\"instant-api alerts\"" + else + fail "$name.json missing/wrong policyName (got: \"$policyName\")" + fi +done + +echo +echo "==> Policy file" +if [ ! -f "$POLICY_FILE" ]; then + fail "policies/instant-api.json missing" +else + if jq empty "$POLICY_FILE" >/dev/null 2>&1; then + ok "policies/instant-api.json parses" + else + fail "policies/instant-api.json does not parse" + fi + policy_name=$(jq -r '.name // ""' "$POLICY_FILE") + if [ "$policy_name" = "instant-api alerts" ]; then + ok "policies/instant-api.json name = \"instant-api alerts\"" + else + fail "policies/instant-api.json name wrong (got: \"$policy_name\")" + fi + pref=$(jq -r '.incidentPreference // ""' "$POLICY_FILE") + if [ "$pref" = "PER_CONDITION_AND_TARGET" ]; then + ok "policies/instant-api.json incidentPreference = \"PER_CONDITION_AND_TARGET\"" + else + fail "policies/instant-api.json incidentPreference wrong (got: \"$pref\")" + fi +fi + +echo +echo "==> Cross-reference: every alert's policyName matches policy file's name" +policy_name=$(jq -r '.name // ""' "$POLICY_FILE" 2>/dev/null || echo "") +for name in "${ALERTS[@]}"; do + f="$ALERTS_DIR/$name.json" + [ -f "$f" ] || continue + alert_policy=$(jq -r '.policyName // ""' "$f") + if [ "$alert_policy" = "$policy_name" ] && [ -n "$policy_name" ]; then + ok "$name.json policyName links to policy file" + else + fail "$name.json policyName=\"$alert_policy\" does not match policy file name=\"$policy_name\"" + fi +done + +echo +echo "==> Summary: $PASS passed, $FAIL failed" +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi +exit 0