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. # # Not validated here: the Alloy config (observability/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: 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