#!/usr/bin/env python3 """Chaque gabarit de sonde se rend en un script bash SYNTAXIQUEMENT valide. POURQUOI (2026-09-28). `sonde-edge.sh.j2` redefinit la syntaxe des commentaires Jinja dans son en-tete (`#jinja2: comment_start_string:'{=#', ...`). Un commentaire ecrit avec la syntaxe ordinaire est sorti tel quel dans le script : `bash` a refuse la ligne 35, et la sonde a rendu une erreur de syntaxe sur les trois edges — l'edge, lui, servait tres bien. Rien ne l'avait vu avant le deploiement. On rend donc chaque gabarit avec des variables FACTICES (tout ce qui manque vaut vide), en respectant l'en-tete `#jinja2:` s'il existe, et on demande a `bash -n` si le resultat se lit. On ne juge pas ce que la sonde FAIT — seulement qu'elle peut s'executer. """ from __future__ import annotations import re import subprocess import sys import tempfile from pathlib import Path import jinja2 ROLES = Path(__file__).resolve().parents[2] / "roles" class Vide(jinja2.ChainableUndefined): """Toute variable absente vaut une chaine vide, iterable et indexable.""" def __str__(self) -> str: return "" def __iter__(self): return iter([]) def rendre(texte: str) -> str: options = {} premiere, _, reste = texte.partition("\n") if premiere.startswith("#jinja2:"): for cle, val in re.findall(r"(\w+):'([^']*)'", premiere): options[cle] = val texte = reste env = jinja2.Environment(undefined=Vide, **options) env.filters.update({"to_json": lambda v, **k: "[]", "to_nice_json": lambda v, **k: "[]", "b64decode": lambda v: "", "from_yaml": lambda v: {}, "bool": bool, "regex_replace": lambda v, *a, **k: str(v), "quote": lambda v: f"'{v}'"}) # Les filtres d'ANSIBLE, absents de Jinja seul : des substituts neutres suffisent, on ne # juge que la syntaxe du resultat. for nom in ("flatten", "dict2items", "items2dict", "ternary", "basename", "dirname", "mandatory", "ipaddr", "combine", "product", "zip"): liste = nom in ("flatten", "dict2items", "zip", "product") env.filters.setdefault(nom, lambda v=None, *a, _liste=liste, **k: [] if _liste else (v or "")) return env.from_string(texte).render(hostvars={}, groups={}) def main() -> int: echecs = 0 gabarits = sorted(ROLES.glob("*/templates/sonde-*.sh.j2")) for g in gabarits: try: script = rendre(g.read_text(encoding="utf-8")) except jinja2.TemplateError as e: print(f"ECHEC {g.relative_to(ROLES)} : ne se rend pas — {e}") echecs += 1 continue with tempfile.NamedTemporaryFile("w", suffix=".sh", delete=False) as f: f.write(script) r = subprocess.run(["bash", "-n", f.name], capture_output=True, text=True) Path(f.name).unlink() if r.returncode != 0: print(f"ECHEC {g.relative_to(ROLES)} : bash refuse — {r.stderr.strip()[:160]}") echecs += 1 if echecs: print(f"\n{echecs} gabarit(s) de sonde qui ne s'executeraient pas.") return 1 print(f"OK — {len(gabarits)} gabarits de sonde, tous rendus en bash valide.") return 0 if __name__ == "__main__": sys.exit(main())