Set-OPS-Public/scripts/modeles.py
Daniel Allaire 7e190e0a57 Trois preuves qui regardent au-dela d'une seule instance + champ liens/websocket au GUI
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>
2026-07-22 21:32:42 -04:00

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())