feat(inventory): one service inventory drives planner, allow-lists, and seams
rank 3. shared/services.yml is the single source of truth for every deployable service (hosts, unit dirs, units, path claims, tests, scrape/ dns/probe facts, aliases, stacks); scripts/service-map.py is its only reader (hosts/services/allows/plan/seams/owner/units/stack/get/render/ check). deploy-plan.py becomes a thin CLI wrapper; both --service allow-lists (deploy.sh, deploy-remote.sh) and the changed-test mapper are the inventory. Selection semantics: host scoping (a host's tree selects only its own services), the quadlet netns/network coupling graph resolved across directories (gluetun's riders live in their own dirs), stacks, and canonical restart order. Against the pinned old-planner fixture: 921 raw deltas reduced to 17, all intentional (anubis now maps; cross-host paths select nothing; the retired qwen-sharp template plans nothing and the live froggeric template plans frank-archon; dankserver/romm/romm.container, missed by the old filename rule, plans the romm trio). The fixture is regenerated as the new pin. Integrity gates: schema, unit claims, quadlet-file ownership, path-glob liveness, Pi-hole hosts / Prometheus jobs / DNS / homepage probes drift. tests/service_inventory.bats pins all of it plus the plan fixture and the deploy-plan CLI. Allow-list grep seams in hermes_webui/observability_quadlet become behavioral allows calls.
This commit is contained in:
1 parent
156ea71879
commit
bdead570e2
17 files changed
+3435
-699
No files matched your search
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bats
|
||||
# Seam: compose validity.
|
||||
# Every compose.yaml in the repo must parse as valid YAML and define services
|
||||
# with at least one image. This catches broken YAML, empty service maps, and
|
||||
# compose files that would fail `podman-compose config` on the target.
|
||||
|
||||
load helpers
|
||||
|
||||
@test "every compose.yaml parses as valid YAML" {
|
||||
local found=0
|
||||
while IFS= read -r f; do
|
||||
found=1
|
||||
yaml_parse_ok "$f"
|
||||
done < <(find "$REPO_ROOT" -name 'compose.yaml' -not -path '*/.git/*' | sort)
|
||||
[ "$found" -eq 1 ] || skip "no compose.yaml files found yet"
|
||||
}
|
||||
|
||||
@test "every compose.yaml defines services with images" {
|
||||
while IFS= read -r f; do
|
||||
python3 - "$f" <<'PYEOF'
|
||||
import sys, yaml
|
||||
with open(sys.argv[1]) as fh:
|
||||
data = yaml.safe_load(fh)
|
||||
services = (data or {}).get("services", {}) or {}
|
||||
assert services, f"{sys.argv[1]}: no services defined"
|
||||
for name, svc in services.items():
|
||||
svc = svc or {}
|
||||
assert svc.get("image"), f"{sys.argv[1]}: service '{name}' has no image"
|
||||
PYEOF
|
||||
done < <(find "$REPO_ROOT" -name 'compose.yaml' -not -path '*/.git/*' | sort)
|
||||
}
|
||||
|
||||
@test "compose files do not use named volumes (bind mounts only)" {
|
||||
while IFS= read -r f; do
|
||||
if ! python3 - "$f" <<'PYEOF'
|
||||
import sys, yaml
|
||||
with open(sys.argv[1]) as fh:
|
||||
data = yaml.safe_load(fh)
|
||||
vols = ((data or {}).get("volumes", {}) or {})
|
||||
# exit 0 when there is no named-volumes section; exit 1 when there is
|
||||
sys.exit(1 if vols else 0)
|
||||
PYEOF
|
||||
then
|
||||
echo "compose file $f declares a named 'volumes:' section; policy is bind mounts only"
|
||||
return 1
|
||||
fi
|
||||
done < <(find "$REPO_ROOT" -name 'compose.yaml' -not -path '*/.git/*' | sort)
|
||||
}
|
||||
Reference in new issue
Block a user