scripts/contexte.py : Ecosysteme, Site, Locataire, contexte actif (fichier `contexte`, sinon les indices d avant via instance_courante et underlay.chemin). 40 controles dans make test. Corrige aussi P34 et P48, rouges depuis le commit de la page de conception. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
172 lines
8.3 KiB
Python
172 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""`contexte.py` : un tronc commun, deux classes — et le contexte actif, nomme ou devine.
|
|
|
|
Sur une arborescence FABRIQUEE (un site, deux locataires, un depot ambigu), on verifie :
|
|
- qu'un depot dit ce qu'il est (site, locataire), et qu'un depot qui dit les deux est refuse ;
|
|
- ce que chaque classe surcharge : index, voute, inventaire ;
|
|
- les relations : `site.locataires()` (et les absents, nommes), `locataire.site()` ;
|
|
- le contexte actif : le fichier `contexte` fait foi ; sans lui, les indices d'avant, et le
|
|
locataire l'emporte quand les deux sont montes (le poste d'aujourd'hui) ;
|
|
- un contexte nomme qui ne designe rien est un REFUS, pas un contexte vide.
|
|
Puis, si les vrais depots sont la : chaque locataire declare par un site porte l'index que ce
|
|
site lui attribue, et nomme un hebergeur qui existe.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
RACINE = Path(__file__).resolve().parents[2]
|
|
sys.path.insert(0, str(RACINE / "scripts"))
|
|
|
|
import contexte as C # noqa: E402
|
|
|
|
ECHECS: list[str] = []
|
|
|
|
|
|
def verifier(cond: bool, msg: str) -> None:
|
|
print(("OK " if cond else "ECHEC ") + msg)
|
|
if not cond:
|
|
ECHECS.append(msg)
|
|
|
|
|
|
def refuse(fonction, msg: str) -> None:
|
|
try:
|
|
fonction()
|
|
except C.ContexteInvalide:
|
|
verifier(True, msg)
|
|
return
|
|
verifier(False, msg + " (aucun refus)")
|
|
|
|
|
|
def ecrire(chemin: Path, texte: str) -> None:
|
|
chemin.parent.mkdir(parents=True, exist_ok=True)
|
|
chemin.write_text(texte, encoding="utf-8")
|
|
|
|
|
|
def fabriquer(d: Path) -> Path:
|
|
"""Un dossier d'ecosystemes : un moteur, un site, deux locataires, un depot ambigu."""
|
|
moteur = d / "Moteur"
|
|
moteur.mkdir()
|
|
ecrire(d / "SITE-Essai" / "underlay.yml",
|
|
"underlay:\n index: 9\n tenants:\n OPS-Un: 5\n OPS-Absent: 6\n")
|
|
ecrire(d / "SITE-Essai" / "underlay.vault.yml", "$ANSIBLE_VAULT;1.1;AES256\n")
|
|
ecrire(d / "OPS-Un" / "plan" / "nomenclature.yml", "index: 5\n")
|
|
ecrire(d / "OPS-Un" / "inventories" / "principal" / "hosts.yml", "all: {}\n")
|
|
ecrire(d / "OPS-Un" / "inventories" / "principal" / "group_vars" / "all" / "vault.yml", "x\n")
|
|
ecrire(d / "OPS-Un" / "parente.yml",
|
|
"parente:\n depots:\n hebergeur:\n nom: SITE-Essai\n")
|
|
ecrire(d / "OPS-Deux" / "plan" / "nomenclature.yml", "index: 7\n")
|
|
ecrire(d / "OPS-Fantome" / "plan" / "nomenclature.yml", "index: 8\n")
|
|
ecrire(d / "OPS-Fantome" / "parente.yml",
|
|
"parente:\n depots:\n hebergeur:\n nom: SITE-Inexistant\n")
|
|
ecrire(d / "Ambigu" / "underlay.yml", "underlay: {index: 1}\n")
|
|
ecrire(d / "Ambigu" / "plan" / "nomenclature.yml", "index: 1\n")
|
|
return moteur
|
|
|
|
|
|
def classes(d: Path) -> None:
|
|
site = C.Ecosysteme.depuis(d / "SITE-Essai")
|
|
un = C.Ecosysteme.depuis(d / "OPS-Un")
|
|
verifier(isinstance(site, C.Site) and site.nature == "site", "un depot a `underlay.yml` est un Site")
|
|
verifier(isinstance(un, C.Locataire) and un.nature == "locataire",
|
|
"un depot a nomenclature est un Locataire")
|
|
refuse(lambda: C.Ecosysteme.depuis(d / "Ambigu"),
|
|
"un depot qui porte `underlay.yml` ET une nomenclature est refuse")
|
|
refuse(lambda: C.Ecosysteme.depuis(d / "Moteur"), "un depot qui ne declare rien est refuse")
|
|
|
|
verifier(site.index == 9 and un.index == 5, "chaque classe lit SON index (underlay / nomenclature)")
|
|
verifier(site.voute == d / "SITE-Essai" / "underlay.vault.yml" and site.a_sa_voute(),
|
|
"la voute d'un site est `underlay.vault.yml`")
|
|
verifier(un.voute == d / "OPS-Un" / "inventories" / "principal" / "group_vars" / "all" / "vault.yml"
|
|
and un.a_sa_voute(), "la voute d'un locataire est dans son inventaire")
|
|
verifier(un.inventaire() == d / "OPS-Un" / "inventories" / "principal" / "hosts.yml",
|
|
"l'inventaire d'un locataire est son `hosts.yml` genere")
|
|
verifier(site.inventaire().name == "site_inventaire.py",
|
|
"l'inventaire d'un site est un script, pas un `hosts.yml`")
|
|
verifier(un.cle_voute.name == "setops-vault-ops-un", "la cle de voute est nommee d'apres le depot")
|
|
|
|
verifier(site.allocations() == {"OPS-Un": 5, "OPS-Absent": 6}, "le site rend les index qu'il attribue")
|
|
verifier([l.nom for l in site.locataires()] == ["OPS-Un"],
|
|
"`locataires()` rend les locataires presents")
|
|
verifier(site.locataires_absents() == ["OPS-Absent"], "un locataire declare sans depot est NOMME absent")
|
|
verifier(un.site() == site, "`locataire.site()` rend l'hebergeur que nomme `parente.yml`")
|
|
verifier(C.Locataire(d / "OPS-Deux").site() is None, "sans hebergeur nomme, `site()` rend None")
|
|
refuse(lambda: C.Locataire(d / "OPS-Fantome").site(), "un hebergeur nomme mais absent est refuse")
|
|
refuse(lambda: C.Site.charger("OPS-Un", d), "`Site.charger` refuse un locataire")
|
|
refuse(lambda: C.Locataire.charger("SITE-Essai", d), "`Locataire.charger` refuse un site")
|
|
|
|
|
|
def contexte_actif(d: Path, moteur: Path) -> None:
|
|
sauve = {k: os.environ.pop(k) for k in ("SETOPS_INSTANCE", "SETOPS_UNDERLAY") if k in os.environ}
|
|
try:
|
|
verifier(C.actif(moteur) is None, "rien de monte, rien de nomme : aucun contexte (None)")
|
|
|
|
(moteur / "underlay.yml").symlink_to(d / "SITE-Essai" / "underlay.yml")
|
|
verifier(C.actif(moteur) == C.Site(d / "SITE-Essai"), "le lien `underlay.yml` seul designe le site")
|
|
|
|
(moteur / "instance").symlink_to(d / "OPS-Un")
|
|
verifier(len(C.montes(moteur)) == 2, "les deux liens montes : deux ecosystemes designes")
|
|
verifier(C.actif(moteur) == C.Locataire(d / "OPS-Un"),
|
|
"les deux montes : le locataire l'emporte, comme la console d'aujourd'hui")
|
|
|
|
os.environ["SETOPS_INSTANCE"] = str(d / "OPS-Deux")
|
|
verifier(C.actif(moteur) == C.Locataire(d / "OPS-Deux"), "`SETOPS_INSTANCE` passe avant le lien")
|
|
del os.environ["SETOPS_INSTANCE"]
|
|
|
|
fichier = moteur / C.NOM_FICHIER_CONTEXTE
|
|
fichier.write_text("# le contexte de ce poste\nsite:SITE-Essai\n", encoding="utf-8")
|
|
verifier(C.actif(moteur) == C.Site(d / "SITE-Essai"),
|
|
"le fichier `contexte` fait foi, meme quand les liens disent autre chose")
|
|
fichier.write_text("locataire: OPS-Un\n", encoding="utf-8")
|
|
verifier(C.actif(moteur) == C.Locataire(d / "OPS-Un"), "`locataire:<depot>` designe le locataire")
|
|
|
|
for contenu, msg in (("site:SITE-Inexistant\n", "un site nomme qui n'existe pas"),
|
|
("locataire:SITE-Essai\n", "une nature qui ne correspond pas au depot"),
|
|
("hebergeur:SITE-Essai\n", "une nature inconnue"),
|
|
("site:SITE-Essai\nlocataire:OPS-Un\n", "deux valeurs"),
|
|
("\n# rien\n", "un fichier vide")):
|
|
fichier.write_text(contenu, encoding="utf-8")
|
|
refuse(lambda: C.actif(moteur), f"fichier `contexte` refuse : {msg}")
|
|
finally:
|
|
os.environ.pop("SETOPS_INSTANCE", None)
|
|
os.environ.update(sauve)
|
|
|
|
|
|
def vrais_depots() -> None:
|
|
"""Sur les depots reels, s'ils sont la : les deux sens de la relation concordent."""
|
|
sites = [C.Site(p.parent) for p in sorted(C.DOSSIER_ECOSYSTEMES.glob("SITE-*/underlay.yml"))]
|
|
if not sites:
|
|
print("SAUTE aucun site frere : rien de reel a confronter")
|
|
return
|
|
for site in sites:
|
|
for loc in site.locataires():
|
|
attendu = site.allocations().get(loc.nom)
|
|
verifier(attendu is None or loc.index == attendu,
|
|
f"{site.nom} attribue {attendu} a {loc.nom}, qui porte l'index {loc.index}")
|
|
heb = loc.nom_hebergeur()
|
|
if heb:
|
|
verifier((C.DOSSIER_ECOSYSTEMES / heb / "underlay.yml").is_file(),
|
|
f"{loc.nom} nomme l'hebergeur {heb}, qui existe")
|
|
verifier(not site.locataires_absents(),
|
|
f"{site.nom} : aucun locataire declare sans depot ({site.locataires_absents() or 'ok'})")
|
|
|
|
|
|
def main() -> int:
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
d = Path(tmp)
|
|
moteur = fabriquer(d)
|
|
classes(d)
|
|
contexte_actif(d, moteur)
|
|
vrais_depots()
|
|
if ECHECS:
|
|
print(f"\n{len(ECHECS)} echec(s).")
|
|
return 1
|
|
print("\nLe contexte se nomme, se reconnait, et refuse ce qui ne designe rien.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|