81 lines
3.2 KiB
Python
81 lines
3.2 KiB
Python
|
|
#!/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())
|