2026-07-22 21:32:42 -04:00
|
|
|
#!/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]
|
modèles : l'underlay devient modélisable, et P17 le valide
Un modèle décrivait un tenant — ses services, ses zones, ses bases. Tous les
hébergeurs n'ont pas le même matériel : l'infrastructure physique mérite le
même traitement.
Le modèle public gagne un underlay volontairement minimal (un commutateur,
pas de fabric de stockage séparée), point de départ honnête d'un petit
hébergeur. Les montages plus riches sont d'autres modèles, conformément à la
doctrine : un générique public, les étoffés en privé.
Le modèle contient désormais deux moitiés qui ne vont pas au même endroit :
`plan/` et `inventories/` chez le tenant, `underlay.yml` chez l'hébergeur.
`modeles.py verifier` le valide (P17), facultativement et sur sa cohérence
INTERNE seulement — pas contre les tenants fédérés réels, un modèle étant un
gabarit et non un site déployé. Il a fallu rendre paramétrables deux
hypothèses du validateur, qui lisait la nomenclature de l'instance active et
globait les dépôts frères ; comportement par défaut inchangé.
Cinq cas de rejet exercés : VLAN empiétant sur la plage tenant, passerelle au
mauvais dernier octet (lue dans la nomenclature du modèle), routeur inconnu,
sortie hors du lien, port déclaré deux fois.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 17:11:18 -04:00
|
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
2026-07-22 21:32:42 -04:00
|
|
|
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())
|
|
|
|
|
|
|
|
|
|
|
modèles : l'underlay devient modélisable, et P17 le valide
Un modèle décrivait un tenant — ses services, ses zones, ses bases. Tous les
hébergeurs n'ont pas le même matériel : l'infrastructure physique mérite le
même traitement.
Le modèle public gagne un underlay volontairement minimal (un commutateur,
pas de fabric de stockage séparée), point de départ honnête d'un petit
hébergeur. Les montages plus riches sont d'autres modèles, conformément à la
doctrine : un générique public, les étoffés en privé.
Le modèle contient désormais deux moitiés qui ne vont pas au même endroit :
`plan/` et `inventories/` chez le tenant, `underlay.yml` chez l'hébergeur.
`modeles.py verifier` le valide (P17), facultativement et sur sa cohérence
INTERNE seulement — pas contre les tenants fédérés réels, un modèle étant un
gabarit et non un site déployé. Il a fallu rendre paramétrables deux
hypothèses du validateur, qui lisait la nomenclature de l'instance active et
globait les dépôts frères ; comportement par défaut inchangé.
Cinq cas de rejet exercés : VLAN empiétant sur la plage tenant, passerelle au
mauvais dernier octet (lue dans la nomenclature du modèle), routeur inconnu,
sortie hors du lien, port déclaré deux fois.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 17:11:18 -04:00
|
|
|
def verifier_underlay(modele: Path) -> list[str]:
|
|
|
|
|
"""Valide l'underlay du modele, s'il en porte un. Liste vide sinon.
|
|
|
|
|
|
|
|
|
|
Un modele d'underlay decrit le MATERIEL d'un hebergeur : tous n'ont pas les memes
|
|
|
|
|
equipements, d'ou l'interet d'en avoir plusieurs. On le valide pour sa coherence
|
|
|
|
|
INTERNE (VLAN sous la plage tenant, sous-reseaux disjoints, passerelle dans son
|
|
|
|
|
reseau, routeur declare, ports non dupliques, dernier octet partage) — mais PAS
|
|
|
|
|
contre les tenants federes reels : un modele est un gabarit, pas un site deploye.
|
|
|
|
|
D'ou les listes de tenants vides, et la nomenclature du modele plutot que celle de
|
|
|
|
|
l'instance active.
|
|
|
|
|
"""
|
|
|
|
|
fichier = modele / "underlay.yml"
|
|
|
|
|
if not fichier.is_file():
|
|
|
|
|
return []
|
|
|
|
|
import underlay as underlay_mod
|
|
|
|
|
u = underlay_mod.charger_depuis(fichier)
|
|
|
|
|
if u is None:
|
|
|
|
|
return ["underlay: fichier present mais bloc `underlay:` absent ou vide"]
|
|
|
|
|
erreurs = underlay_mod.valider(
|
|
|
|
|
u, tenants_vlans=set(), tenants_supernets=[],
|
|
|
|
|
plan_nomenclature=modele / "plan" / "nomenclature.yml",
|
|
|
|
|
)
|
|
|
|
|
return [f"underlay: {e}" for e in erreurs]
|
|
|
|
|
|
|
|
|
|
|
2026-07-22 21:32:42 -04:00
|
|
|
def verifier_un(modele: Path) -> list[str]:
|
modèles : l'underlay devient modélisable, et P17 le valide
Un modèle décrivait un tenant — ses services, ses zones, ses bases. Tous les
hébergeurs n'ont pas le même matériel : l'infrastructure physique mérite le
même traitement.
Le modèle public gagne un underlay volontairement minimal (un commutateur,
pas de fabric de stockage séparée), point de départ honnête d'un petit
hébergeur. Les montages plus riches sont d'autres modèles, conformément à la
doctrine : un générique public, les étoffés en privé.
Le modèle contient désormais deux moitiés qui ne vont pas au même endroit :
`plan/` et `inventories/` chez le tenant, `underlay.yml` chez l'hébergeur.
`modeles.py verifier` le valide (P17), facultativement et sur sa cohérence
INTERNE seulement — pas contre les tenants fédérés réels, un modèle étant un
gabarit et non un site déployé. Il a fallu rendre paramétrables deux
hypothèses du validateur, qui lisait la nomenclature de l'instance active et
globait les dépôts frères ; comportement par défaut inchangé.
Cinq cas de rejet exercés : VLAN empiétant sur la plage tenant, passerelle au
mauvais dernier octet (lue dans la nomenclature du modèle), routeur inconnu,
sortie hors du lien, port déclaré deux fois.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 17:11:18 -04:00
|
|
|
"""Rejoue les validateurs de registres sur un modele, + son underlay s'il en a un."""
|
2026-07-22 21:32:42 -04:00
|
|
|
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'}")
|
modèles : l'underlay devient modélisable, et P17 le valide
Un modèle décrivait un tenant — ses services, ses zones, ses bases. Tous les
hébergeurs n'ont pas le même matériel : l'infrastructure physique mérite le
même traitement.
Le modèle public gagne un underlay volontairement minimal (un commutateur,
pas de fabric de stockage séparée), point de départ honnête d'un petit
hébergeur. Les montages plus riches sont d'autres modèles, conformément à la
doctrine : un générique public, les étoffés en privé.
Le modèle contient désormais deux moitiés qui ne vont pas au même endroit :
`plan/` et `inventories/` chez le tenant, `underlay.yml` chez l'hébergeur.
`modeles.py verifier` le valide (P17), facultativement et sur sa cohérence
INTERNE seulement — pas contre les tenants fédérés réels, un modèle étant un
gabarit et non un site déployé. Il a fallu rendre paramétrables deux
hypothèses du validateur, qui lisait la nomenclature de l'instance active et
globait les dépôts frères ; comportement par défaut inchangé.
Cinq cas de rejet exercés : VLAN empiétant sur la plage tenant, passerelle au
mauvais dernier octet (lue dans la nomenclature du modèle), routeur inconnu,
sortie hors du lien, port déclaré deux fois.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-02 17:11:18 -04:00
|
|
|
erreurs += verifier_underlay(modele)
|
2026-07-22 21:32:42 -04:00
|
|
|
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())
|