fix: validate metrics without shell-escaped python or grafana auth
Two bugs, both in validation only -- the deploy half worked and both scrape targets are up. The targets check used python3 -c with \" escapes inside an f-string, which Python reads as a line continuation: SyntaxError: unexpected character after line continuation character Rewritten as a quoted heredoc reading a file, so nothing is escaped through the shell. It now also reports per-target health rather than grepping for ' up '. The Grafana check called /api/search, which returns 401 -- the API requires auth and this job holds no Grafana credentials by design. Replaced with the unauthenticated /api/health, a file presence check for both dashboard and provider, and a scan of the grafana journal for dashboard provisioning errors, which is what actually distinguishes a dashboard that loaded from one that failed to parse.
This commit is contained in:
@@ -724,41 +724,70 @@ jobs:
|
||||
fail=0
|
||||
|
||||
echo "--- prometheus targets ---"
|
||||
# The point of this check: a scrape target that drifted out of the
|
||||
# matrices, or a firewalld rule that never landed, shows up here as a
|
||||
# missing or DOWN target instead of an empty graph nobody looks at.
|
||||
# The point of this check: a target that drifted out of the matrices,
|
||||
# or a firewalld rule that never landed, shows up here as missing or
|
||||
# DOWN instead of an empty graph nobody looks at.
|
||||
#
|
||||
# Written as a quoted heredoc reading a file, NOT `python3 -c '...'`:
|
||||
# escaping quotes through the shell into an f-string produced
|
||||
# "SyntaxError: unexpected character after line continuation character",
|
||||
# because Python reads \" inside the expression as a continuation.
|
||||
for i in $(seq 1 20); do
|
||||
body=$(run "curl -fsS 'http://127.0.0.1:${{ env.PROM_PORT }}/api/v1/targets?state=active'")
|
||||
got=$(python3 -c '
|
||||
import json,sys
|
||||
d=json.load(sys.stdin)
|
||||
for t in d["data"]["activeTargets"]:
|
||||
j=t["labels"].get("job","")
|
||||
if j.startswith("quantus-"):
|
||||
print(f"{j} {t[\"labels\"].get(\"instance\")} {t[\"health\"]} {t.get(\"lastError\",\"\")}")
|
||||
' <<<"$body" | sort)
|
||||
[ -n "$got" ] && break
|
||||
run "curl -fsS 'http://127.0.0.1:${{ env.PROM_PORT }}/api/v1/targets?state=active'" > targets.json
|
||||
if grep -q 'quantus-' targets.json; then break; fi
|
||||
sleep 3
|
||||
done
|
||||
if [ -z "${got:-}" ]; then
|
||||
echo " FAIL prometheus has no quantus-* targets — is scrape_config_files wired in?" >&2
|
||||
echo " (one-time: script/infra-setup.sh --metrics-hosts)" >&2
|
||||
exit 1
|
||||
|
||||
if python3 - <<'PY'
|
||||
import json, sys
|
||||
d = json.load(open("targets.json"))
|
||||
rows = [t for t in d["data"]["activeTargets"]
|
||||
if t["labels"].get("job", "").startswith("quantus-")]
|
||||
if not rows:
|
||||
print(" FAIL prometheus has no quantus-* targets", file=sys.stderr)
|
||||
print(" is scrape_config_files wired into prometheus.yml?", file=sys.stderr)
|
||||
print(" one-time: script/infra-setup.sh --metrics-hosts", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
bad = 0
|
||||
for t in sorted(rows, key=lambda x: (x["labels"].get("job", ""),
|
||||
x["labels"].get("instance", ""))):
|
||||
j = t["labels"].get("job")
|
||||
i = t["labels"].get("instance")
|
||||
h = t["health"]
|
||||
e = t.get("lastError", "")
|
||||
print((" ok " if h == "up" else " FAIL ") + " ".join(filter(None, [j, i, h, e])))
|
||||
if h != "up":
|
||||
bad += 1
|
||||
sys.exit(1 if bad else 0)
|
||||
PY
|
||||
then
|
||||
echo " all quantus targets up"
|
||||
else
|
||||
echo " FAIL not every quantus target is up" >&2; fail=1
|
||||
fi
|
||||
printf '%s\n' "$got" | sed 's/^/ /'
|
||||
printf '%s\n' "$got" | grep -qv " up " && { echo " FAIL not every quantus target is up" >&2; fail=1; } || true
|
||||
|
||||
echo "--- grafana dashboard ---"
|
||||
# Assert Grafana actually LOADED it, not merely that a file is on disk.
|
||||
# A provisioned dashboard that failed to parse leaves the JSON sitting
|
||||
# there looking fine while the folder stays empty.
|
||||
if run "curl -fsS 'http://127.0.0.1:${{ env.GRAFANA_PORT }}/api/search?query=Quantus'" \
|
||||
| grep -q 'quantus-node-miner'; then
|
||||
echo " ok grafana has loaded the dashboard (uid quantus-node-miner)"
|
||||
# Grafana's /api/search requires auth (401 unauthenticated), and this
|
||||
# job holds no Grafana credentials by design — so assert provisioning
|
||||
# from the journal instead, which gitea_ci can read via systemd-journal.
|
||||
run "curl -fsS http://127.0.0.1:${{ env.GRAFANA_PORT }}/api/health" > /dev/null \
|
||||
&& echo " ok grafana is healthy" \
|
||||
|| { echo " FAIL grafana health endpoint not responding" >&2; fail=1; }
|
||||
|
||||
if run "test -s /etc/grafana/provisioning/dashboards/quantus/quantus.json" \
|
||||
&& run "test -s /etc/grafana/provisioning/dashboards/quantus-dashboards.yaml"; then
|
||||
echo " ok dashboard and provider present on the host"
|
||||
else
|
||||
echo " FAIL grafana has not loaded the dashboard" >&2
|
||||
echo " provider present? check /etc/grafana/provisioning/dashboards/" >&2
|
||||
fail=1
|
||||
echo " FAIL dashboard JSON or provider missing" >&2; fail=1
|
||||
fi
|
||||
|
||||
# A dashboard that fails to parse is logged by the provisioner and then
|
||||
# silently absent from the UI, so treat any provisioning error as fatal.
|
||||
if run "journalctl -u grafana.service --since '-10min' --no-pager" \
|
||||
| grep -E 'logger=provisioning\.dashboard.*level=error'; then
|
||||
echo " FAIL grafana reported a dashboard provisioning error" >&2; fail=1
|
||||
else
|
||||
echo " ok no dashboard provisioning errors in the last 10 minutes"
|
||||
fi
|
||||
|
||||
exit $fail
|
||||
|
||||
@@ -100,6 +100,22 @@ immediate assertion reads a starting node as a broken one. Validate waits for
|
||||
readiness on a bounded poll. Do not replace that with a fixed `sleep`, and do not
|
||||
remove it because "it passed locally".
|
||||
|
||||
## Inline Python in workflow steps
|
||||
|
||||
Use a quoted heredoc (`python3 - <<'PY'`) reading a file or an env var. Do NOT
|
||||
use `python3 -c '...'` with escaped quotes: `\"` inside an f-string expression is
|
||||
read by Python as a line continuation and fails with "unexpected character after
|
||||
line continuation character". A quoted heredoc needs no shell escaping at all.
|
||||
|
||||
## Grafana checks cannot use its API
|
||||
|
||||
`/api/search` and most of Grafana's API require auth (401 unauthenticated), and
|
||||
this workflow deliberately holds no Grafana credentials. Assert provisioning from
|
||||
`journalctl -u grafana.service` instead — `gitea_ci` is in `systemd-journal`, and
|
||||
a dashboard that fails to parse logs `logger=provisioning.dashboard level=error`
|
||||
and is then silently absent from the UI. `/api/health` is unauthenticated and is
|
||||
fine for liveness.
|
||||
|
||||
## Pipes in validate steps
|
||||
|
||||
The workflow shell is `bash -e -o pipefail`. Any pipe whose *reader* exits early
|
||||
|
||||
Reference in New Issue
Block a user