Set-OPS-Public/scripts/tests/test_sondes_conditionnelles.py
Daniel Allaire f5adffb0de sondes conditionnelles : attendues seulement la ou elles sont posees
seulement_si dans meta/supervision.yml, lu par le gabarit Icinga des sondes ;
garde test_sondes_conditionnelles. journaux-frontiere ne rougit plus a vie chez
les locataires ; Technolibre sans critique.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 12:31:17 -04:00

92 lines
3.8 KiB
Python

#!/usr/bin/env python3
"""Une sonde posee sous condition est declaree sous la MEME condition.
UNE LISTE QUI EN SUIT UNE AUTRE PREND DU RETARD (2026-09-28). Un role pose sa sonde dans
`/usr/local/lib/setops/sondes/` avec ses taches ; Icinga l'attend d'apres
`meta/supervision.yml`. Quand le depot est conditionnel (`when:`) et la declaration ne
l'est pas, Icinga attend une sonde qui n'existera jamais : `journaux-frontiere` a ete
rouge a vie chez chaque locataire, dont la frontiere appartient au site.
Trois regles :
1. toute sonde deposee sous `when:` est declaree avec `seulement_si` ;
2. la variable de `seulement_si` figure dans ce `when:` ;
3. elle a une valeur par defaut LITTERALE dans le role qui la definit — Icinga la lit
telle quelle, il ne rend pas le Jinja d'un autre role.
"""
from __future__ import annotations
import sys
from pathlib import Path
import yaml
ROLES = Path(__file__).resolve().parents[2] / "roles"
def depots_conditionnels() -> dict[str, str]:
"""{nom de sonde : texte du `when:`} pour chaque sonde deposee sous condition."""
out = {}
for f in sorted(ROLES.glob("*/tasks/*.yml")):
try:
taches = yaml.safe_load(f.read_text(encoding="utf-8")) or []
except yaml.YAMLError:
continue
for t in taches if isinstance(taches, list) else []:
if not isinstance(t, dict):
continue
module = next((v for v in t.values() if isinstance(v, dict) and "dest" in v), None)
dest = str((module or {}).get("dest", ""))
if "/setops/sondes/" in dest and t.get("when"):
quand = t["when"] if isinstance(t["when"], str) else " ".join(map(str, t["when"]))
out[dest.rsplit("/", 1)[-1].removesuffix(".sh")] = quand
return out
def declarations() -> dict[str, tuple[str, dict]]:
"""{nom de sonde : (role declarant, declaration)}."""
out = {}
for f in sorted(ROLES.glob("*/meta/supervision.yml")):
for s in (yaml.safe_load(f.read_text(encoding="utf-8")) or {}).get("sondes") or []:
out[s["nom"]] = (f.parents[1].name, s)
return out
def main() -> int:
depots, decl = depots_conditionnels(), declarations()
echecs = 0
for nom, quand in sorted(depots.items()):
role, s = decl.get(nom, (None, {}))
var = s.get("seulement_si")
if not var:
print(f"ECHEC « {nom} » est posee sous `when: {quand}` mais declaree sans "
f"`seulement_si` : Icinga l'attendrait la ou elle n'existe pas")
echecs += 1
continue
if var not in quand:
print(f"ECHEC « {nom} » : `seulement_si: {var}` n'est pas la condition du depot "
f"(`when: {quand}`)")
echecs += 1
continue
definisseur = s.get("defini_par") or role
defauts = yaml.safe_load((ROLES / definisseur / "defaults" / "main.yml")
.read_text(encoding="utf-8")) or {}
val = defauts.get(var, "<absente>")
if val == "<absente>" or (isinstance(val, str) and "{{" in val):
print(f"ECHEC « {nom} » : `{var}` doit avoir un defaut LITTERAL dans "
f"`{definisseur}/defaults/main.yml` (trouve : {val!r})")
echecs += 1
continue
print(f"ok {nom:<20} seulement_si {var} (defaut {val!r}, {definisseur})")
for nom, (role, s) in sorted(decl.items()):
if s.get("seulement_si") and nom not in depots:
print(f"ECHEC « {nom} » ({role}) declare `seulement_si` sans depot conditionnel")
echecs += 1
if echecs:
print(f"\n{echecs} echec(s).")
return 1
print("OK — chaque sonde conditionnelle est attendue sous sa propre condition.")
return 0
if __name__ == "__main__":
sys.exit(main())