name: Validate observability stack # This repo 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 (bad indent / unparseable file) without it. # # Not validated here: the Alloy config (alloy/config.alloy, an HCL-like format, # needs the `alloy` binary) and docker-compose *schema* (unknown-key checks, which # would need the docker CLI). Add either if the churn warrants the extra tooling. on: push: branches: [main] pull_request: branches: [main] 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("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 targets = [ pathlib.Path("docker-compose.yml"), pathlib.Path("alloy/docker-compose.agent.yml"), pathlib.Path("loki/config.yml"), *sorted(pathlib.Path("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