Le harnais ne verifiait qu'UNE instance et le seul modele socle. Tout ce qui vit a cote du moteur echappait au controle. Trois preuves ferment ces angles morts : - P17 (scripts/modeles.py) : TOUS les modeles valident, pas seulement socle. SETOPS_MODELES=../Set-OPS-Modeles inclut les modeles assembles prives. A trouve 6 modeles invalides sur 7 (corriges dans Set-OPS-Modeles). - P18 (scripts/voute.py) : le gabarit vault.yml.example couvre EXACTEMENT les secrets que le plan exige (bases + roles actifs + group_vars). Ne dechiffre jamais la vraie voute : compare des noms. - P19 (scripts/couverture_gui.py) : tout champ present dans un plan reel est editable par le GUI. A trouve applications.websocket (comble). Nomenclature toleree (trou connu). GUI : - champ « Liens (bindings) » dans l'inspecteur d'application : role -> cible en listes deroulantes, les roles proposes = ceux que le role porteur accepte (meta/liens.yml). Comble un manque : les bindings ne se declaraient qu'en editant le YAML a la main. - champ « WebSocket » (Collabora). - CHAMPS_ECRITS_PAR_GUI : declaration de ce que le GUI sait ecrire, verifiee par P19. Garde-fou de fond : valider_applications refuse une application posee sur un hote non declare (l'hote fantome exact qu'integral portait). Cable partout + POST du GUI. liens_acceptes()/catalogue_liens() dans inventory_rules : source unique partagee par le validateur, le GUI et instancier.py (dont la copie locale est retiree). Valide : make verifier rc=0, CONFORME 19/19, ansible-lint 0 echec, 7 modeles valident, DIFF VIDE, node --check du GUI OK. Piece justificative : docs/audit/preuve-2026-07-22.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
122 lines
4.2 KiB
Python
122 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Valide TOUS les modeles d'instance, pas seulement le socle public.
|
|
|
|
Un modele est un dossier portant un `plan/` (serveurs, applications, bases,
|
|
domaines, nomenclature). Ce script rejoue sur chacun les MEMES validateurs que
|
|
sur une instance reelle — il ne reimplemente aucune regle.
|
|
|
|
Pourquoi : les modeles assembles vivent dans un depot separe (actif commercial).
|
|
Rien ne les verifiait, et `integral` a porte pendant des semaines une application
|
|
posee sur un hote jamais declare. Un modele defectueux se propage a chaque
|
|
locataire cree depuis lui.
|
|
|
|
Decouverte des modeles :
|
|
- `exemples/modeles/*` du depot (le socle public) ;
|
|
- plus les chemins de la variable d'environnement SETOPS_MODELES
|
|
(separes par ':'). Un chemin peut designer un modele OU un dossier de
|
|
modeles. Absent = ignore en silence (le depot prive n'est pas toujours la).
|
|
|
|
Usage :
|
|
python3 scripts/modeles.py lister
|
|
python3 scripts/modeles.py verifier
|
|
SETOPS_MODELES=../Set-OPS-Modeles python3 scripts/modeles.py verifier
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[1]
|
|
MODELES_DEPOT = RACINE / "exemples" / "modeles"
|
|
VALIDATEURS = ("domaines", "serveurs", "applications", "bases_donnees")
|
|
|
|
|
|
def _est_modele(p: Path) -> bool:
|
|
return p.is_dir() and (p / "plan").is_dir()
|
|
|
|
|
|
def decouvrir() -> list[Path]:
|
|
"""Modeles du depot + ceux designes par SETOPS_MODELES. Tries, dedoublonnes."""
|
|
trouves: list[Path] = []
|
|
if MODELES_DEPOT.is_dir():
|
|
trouves += [p for p in sorted(MODELES_DEPOT.iterdir()) if _est_modele(p)]
|
|
for brut in (os.environ.get("SETOPS_MODELES") or "").split(":"):
|
|
brut = brut.strip()
|
|
if not brut:
|
|
continue
|
|
chemin = Path(brut)
|
|
if not chemin.is_absolute():
|
|
chemin = (RACINE / chemin).resolve()
|
|
if not chemin.is_dir():
|
|
continue
|
|
if _est_modele(chemin):
|
|
trouves.append(chemin)
|
|
else:
|
|
trouves += [p for p in sorted(chemin.iterdir()) if _est_modele(p)]
|
|
vus: dict[str, Path] = {}
|
|
for p in trouves:
|
|
vus.setdefault(str(p.resolve()), p)
|
|
return list(vus.values())
|
|
|
|
|
|
def verifier_un(modele: Path) -> list[str]:
|
|
"""Rejoue les 4 validateurs sur un modele. Renvoie la liste des erreurs."""
|
|
erreurs: list[str] = []
|
|
env = {**os.environ, "SETOPS_INSTANCE": str(modele)}
|
|
env.pop("SETOPS_INVENTAIRE", None)
|
|
for nom in VALIDATEURS:
|
|
r = subprocess.run([sys.executable, f"scripts/{nom}.py", "verifier"],
|
|
cwd=RACINE, env=env, capture_output=True, text=True)
|
|
if r.returncode != 0:
|
|
lignes = (r.stdout + r.stderr).strip().splitlines()
|
|
erreurs.append(f"{nom}: {lignes[-1] if lignes else 'rc!=0'}")
|
|
return erreurs
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Validation des modeles d'instance Set-OPS.")
|
|
sub = parser.add_subparsers(dest="commande", required=True)
|
|
sub.add_parser("lister", help="Liste les modeles decouverts.")
|
|
sub.add_parser("verifier", help="Valide les registres de chaque modele.")
|
|
args = parser.parse_args()
|
|
|
|
modeles = decouvrir()
|
|
if args.commande == "lister":
|
|
if not modeles:
|
|
print("Aucun modele decouvert.")
|
|
return 0
|
|
for m in modeles:
|
|
try:
|
|
rel = m.relative_to(RACINE)
|
|
except ValueError:
|
|
rel = m
|
|
print(f"{m.name} ({rel})")
|
|
return 0
|
|
|
|
if not modeles:
|
|
print("Aucun modele decouvert : rien a verifier.")
|
|
return 0
|
|
|
|
en_faute = 0
|
|
for m in modeles:
|
|
erreurs = verifier_un(m)
|
|
if erreurs:
|
|
en_faute += 1
|
|
print(f"MODELE {m.name} : INVALIDE", file=sys.stderr)
|
|
for e in erreurs:
|
|
print(f" - {e}", file=sys.stderr)
|
|
else:
|
|
print(f"MODELE {m.name} : valide")
|
|
if en_faute:
|
|
print(f"erreur: {en_faute} modele(s) invalide(s) sur {len(modeles)}.", file=sys.stderr)
|
|
return 2
|
|
print(f"Les {len(modeles)} modele(s) decouvert(s) valident.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|