2026-07-23 05:11:33 +00:00
|
|
|
name: Validate observability stack
|
|
|
|
|
|
|
|
|
|
# The observability DOMAIN deploys by hand (`docker compose up -d` on beta +
|
|
|
|
|
# the Alloy agent on each node) with no build step, so nothing caught a
|
|
|
|
|
# malformed compose file, a broken dashboard JSON, or an unparseable config
|
|
|
|
|
# until it failed live on the host. This is that missing guard: it parses
|
|
|
|
|
# every YAML/JSON artifact that ships to a node. It does NOT deploy, and
|
|
|
|
|
# deliberately needs no docker CLI (the node:20-bookworm job image doesn't
|
|
|
|
|
# ship one) -- a YAML parse catches the real breakage without it.
|
|
|
|
|
#
|
|
|
|
|
# Monorepo port: paths are prefixed observability/, the push trigger is
|
|
|
|
|
# path-filtered to the domain, and workflow_call lets pr-build.yml reuse this
|
|
|
|
|
# as the domain's PR check under its single `gate` required status.
|
|
|
|
|
#
|
2026-07-24 04:37:41 +00:00
|
|
|
# The Alloy config (observability/alloy/config.alloy, an HCL-like format) IS
|
|
|
|
|
# validated below -- the static `alloy` binary is downloaded straight from its
|
|
|
|
|
# GitHub release (pinned to the same v1.9.1 the fleet runs; see
|
|
|
|
|
# observability/alloy/docker-compose.agent.yml), no docker CLI needed.
|
|
|
|
|
#
|
|
|
|
|
# Not validated here: docker-compose *schema* (unknown-key checks, which would
|
|
|
|
|
# need the docker CLI). Add it if the churn warrants the extra tooling.
|
2026-07-23 05:11:33 +00:00
|
|
|
|
|
|
|
|
on:
|
|
|
|
|
workflow_call: {}
|
|
|
|
|
push:
|
|
|
|
|
branches: [main, dev]
|
|
|
|
|
paths: ['observability/**']
|
|
|
|
|
|
|
|
|
|
jobs:
|
|
|
|
|
validate:
|
|
|
|
|
runs-on: docker
|
|
|
|
|
steps:
|
|
|
|
|
- uses: actions/checkout@v4
|
|
|
|
|
|
|
|
|
|
- name: Dashboards are valid JSON
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
import json, pathlib, sys
|
|
|
|
|
bad = False
|
|
|
|
|
files = sorted(pathlib.Path("observability/grafana/dashboards").glob("*.json"))
|
|
|
|
|
if not files:
|
|
|
|
|
print("no dashboards found"); sys.exit(1)
|
|
|
|
|
for f in files:
|
|
|
|
|
try:
|
|
|
|
|
json.loads(f.read_text()); print("OK", f)
|
|
|
|
|
except Exception as e: # noqa: BLE001
|
|
|
|
|
print("INVALID JSON", f, "-", e); bad = True
|
|
|
|
|
sys.exit(1 if bad else 0)
|
|
|
|
|
PY
|
|
|
|
|
|
|
|
|
|
- name: YAML artifacts parse (compose + loki + grafana provisioning)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
apt-get update -qq && apt-get install -y -qq python3-yaml >/dev/null
|
|
|
|
|
python3 - <<'PY'
|
|
|
|
|
import pathlib, sys
|
|
|
|
|
import yaml
|
|
|
|
|
bad = False
|
|
|
|
|
root = pathlib.Path("observability")
|
|
|
|
|
targets = [
|
|
|
|
|
root / "docker-compose.yml",
|
|
|
|
|
root / "alloy/docker-compose.agent.yml",
|
|
|
|
|
root / "loki/config.yml",
|
|
|
|
|
*sorted((root / "grafana/provisioning").rglob("*.yml")),
|
|
|
|
|
]
|
|
|
|
|
for f in targets:
|
|
|
|
|
if not f.exists():
|
|
|
|
|
print("MISSING", f); bad = True; continue
|
|
|
|
|
try:
|
|
|
|
|
yaml.safe_load(f.read_text()); print("OK", f)
|
|
|
|
|
except Exception as e: # noqa: BLE001
|
|
|
|
|
print("INVALID YAML", f, "-", e); bad = True
|
|
|
|
|
sys.exit(1 if bad else 0)
|
|
|
|
|
PY
|
2026-07-24 04:37:41 +00:00
|
|
|
|
|
|
|
|
- name: Alloy config parses and validates (components, relabel/process wiring)
|
|
|
|
|
run: |
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
apt-get update -qq && apt-get install -y -qq unzip >/dev/null
|
|
|
|
|
curl -sSL -o /tmp/alloy.zip \
|
|
|
|
|
https://github.com/grafana/alloy/releases/download/v1.9.1/alloy-linux-amd64.zip
|
|
|
|
|
unzip -q /tmp/alloy.zip -d /tmp/alloybin
|
|
|
|
|
chmod +x /tmp/alloybin/alloy-linux-amd64
|
|
|
|
|
|
|
|
|
|
# `alloy validate` builds the real component graph (catches bad field
|
|
|
|
|
# names, dangling forward_to/receiver refs, malformed relabel/process
|
|
|
|
|
# stages) but doesn't recognize the top-level `livedebugging {}` singleton
|
|
|
|
|
# block as a component -- it only knows named components, even though
|
|
|
|
|
# `alloy run` loads that block fine. Known gap in the `validate`
|
|
|
|
|
# subcommand, not a config error, so strip that one line before checking.
|
|
|
|
|
grep -v '^livedebugging ' observability/alloy/config.alloy > /tmp/config-for-validate.alloy
|
|
|
|
|
/tmp/alloybin/alloy-linux-amd64 validate /tmp/config-for-validate.alloy
|