From 82510221481cb9a870c85b48b82bdeab642fe063 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sun, 4 Oct 2026 20:16:55 -0400 Subject: [PATCH] contexte : etape 2a, la fiche du site et la preuve qu elle dit vrai site.fiche_pour(locataire) reunit attribution, offre, racine et les trois lectures directes de l instancier ; verifier_fiche la confronte aux copies, a la racine et a l inventaire du locataire. P84 : aucune ecart sur les deux couples ; sept alterations vues en ecart. Co-Authored-By: Claude Opus 5.5 --- AGENTS.md | 2 +- CHANGELOG.md | 39 ++++ docs/devis-services.md | 2 +- docs/responsabilites-locataire-hebergeur.md | 2 +- scripts/contexte.py | 219 +++++++++++++++++++- scripts/prouver.py | 29 +++ scripts/tests/test_contexte.py | 48 ++++- 7 files changed, 333 insertions(+), 8 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index b88fe67..e2d9d3f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente ## Écrire, puis relire (D-68) `--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**. -C'est aussi ce que font les 83 preuves de `make prouver` : elles lisent le dépôt, sans le +C'est aussi ce que font les 84 preuves de `make prouver` : elles lisent le dépôt, sans le moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le dépôt annonce.** diff --git a/CHANGELOG.md b/CHANGELOG.md index 042a5d4..1be1c5d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,44 @@ # CHANGELOG — Set-OPS +## 2026-10-04 (80) — Étape 2, première moitié : la fiche du site, et la preuve qu'elle dit vrai + +**Le chemin** (`docs/conception-contextes.md` §6), étape 2 : générer les fiches **à côté** de +l'existant, et prouver qu'elles disent exactement ce que les lectures croisées produisent. +Première moitié : le sens site → locataire. La face réseau du locataire suit. + +**Fait** (`scripts/contexte.py`) : +- `site.fiche_pour(locataire)` réunit ce que trois canaux portaient sans fiche : ce que le site + **attribue** (index, adresse publique), ce qu'il **offre** (les intrants de service, + calculés par `site_intrants.contrat()`), sa **racine de confiance** (le certificat + lui-même), et ce que l'instancier **lisait directement** dans le site (zone et résolveurs à + déléguer, serveurs de temps, mode de routage), par les mêmes fonctions qu'aujourd'hui. La + fiche porte l'empreinte de ses quatre sources. +- `verifier_fiche(site, locataire)` confronte la fiche à ce que le locataire porte : ses + copies (la table de `site_intrants`), sa racine, et son inventaire généré (`chrony_serveurs` + sur chaque machine, la délégation DNS sur la machine qui résout, le rattachement SDN). +- `locataire.domaine_interne()`, lu comme le lit `instancier._domaine_interne`. La première + version le cherchait dans `00-instance.yml` ; il est dans `10-intrants.yml`. La comparaison + passait quand même, parce que les domaines du site et du locataire diffèrent : relevé et + corrigé avant le commit. +- `_monter(site, locataire)` : les modules d'avant lisent « le site monté » ; la fiche leur + désigne le site voulu le temps de l'appel, puis remet l'environnement, même sur exception. + Passerelle de transition, jusqu'à l'étape 3. +- `python3 scripts/contexte.py --fiche ` affiche la fiche ; + `--verifier-fiches` confronte chaque couple dont le site est l'hébergeur **actif** (un site + de reprise ne dépose rien tant que la bascule n'a pas eu lieu). + +**Rien n'est encore déposé ni consommé** : la fiche est calculée, pas écrite chez le locataire. +Le dépôt et la bascule de l'instancier sont l'étape 3, à inventaire identique. + +**Éprouvé** : +- **P84**, « La fiche du site dit exactement ce que chaque locataire porte » : 2 fiches, + SITE-Chezlepro → OPS-Chezlepro et → OPS-Technolibre, aucun écart. +- `test_contexte.py`, 50 contrôles : les deux fiches réelles concordent, et une fiche altérée + de **sept façons** (index, adresse publique, intrant de service, racine, serveurs de temps, + délégation DNS, mode de routage) est vue en écart à chaque fois. +- `make verifier` conforme, **84/84**. `AGENTS.md`, `devis-services.md` et + `responsabilites-locataire-hebergeur.md` comptent désormais 84 preuves. + ## 2026-10-04 (79) — Étape 1 : le module `contexte.py`, que rien n'utilise encore **Le chemin** (`docs/conception-contextes.md` §6), premier pas : le tronc commun et les deux diff --git a/docs/devis-services.md b/docs/devis-services.md index 009254d..785a402 100644 --- a/docs/devis-services.md +++ b/docs/devis-services.md @@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met ## Le trou qu'il comble -`scripts/prouver.py` porte 83 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le +`scripts/prouver.py` porte 84 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est cohérent **avec lui-même** — que les handlers existent, que les intrants ont un propriétaire, que rien n'est codé en dur. diff --git a/docs/responsabilites-locataire-hebergeur.md b/docs/responsabilites-locataire-hebergeur.md index efa5e7c..de3567a 100644 --- a/docs/responsabilites-locataire-hebergeur.md +++ b/docs/responsabilites-locataire-hebergeur.md @@ -99,7 +99,7 @@ Nommer un point ouvert vaut mieux qu'une ligne rassurante sans mécanisme derri Aucune de ces responsabilités n'est laissée à la parole : ``` -make prouver le dépôt est-il cohérent avec lui-même (83 preuves, zéro réseau) +make prouver le dépôt est-il cohérent avec lui-même (84 preuves, zéro réseau) make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80) make certificats-plan ce que le disque porte contre ce que la mémoire sert make expositions-plan chaque service publié répond-il, et depuis où diff --git a/scripts/contexte.py b/scripts/contexte.py index d472039..5c6c43e 100644 --- a/scripts/contexte.py +++ b/scripts/contexte.py @@ -12,18 +12,24 @@ site affichant zero machine, `make ci` melangeant le modele public et les ecosys Ce module est le SEUL endroit ou se lisent ces indices. Les scripts lui demandent « ou suis-je ? » et recoivent un objet : un `Site` ou un `Locataire`, qui heritent d'`Ecosysteme`. -ETAPE 1 DU CHEMIN (§6) : le module existe, il est teste, et RIEN NE L'UTILISE ENCORE. Les -scripts l'adopteront un par un, chacun a resultat identique. Les fiches du contrat -(`site.fiche_pour`, `locataire.face_reseau`) sont l'etape 2. +ETAPES 1 ET 2 DU CHEMIN (§6) : le module existe, il est teste, et RIEN NE L'UTILISE ENCORE. +La fiche du site (`site.fiche_pour(locataire)`) est calculee A COTE de l'existant, et +`verifier_fiche` prouve qu'elle dit exactement ce que le locataire porte aujourd'hui. La face +reseau du locataire suit. Les consommateurs basculeront a l'etape 3, a resultat identique. Usage : - python3 scripts/contexte.py # le contexte actif, et ce que chaque indice dit + python3 scripts/contexte.py # le contexte actif, et ce que chaque indice dit python3 scripts/contexte.py --json + python3 scripts/contexte.py --fiche OPS-Technolibre # la fiche que son hebergeur lui destine + python3 scripts/contexte.py --verifier-fiches # chaque fiche dit-elle ce que le locataire porte ? """ from __future__ import annotations import argparse +import contextlib +import hashlib import json +import os import sys from pathlib import Path @@ -53,6 +59,35 @@ def _yaml(chemin: Path) -> dict: return yaml.safe_load(chemin.read_text(encoding="utf-8")) or {} +def _empreinte(chemin: Path) -> str: + return hashlib.sha256(chemin.read_bytes()).hexdigest() if chemin.is_file() else "" + + +@contextlib.contextmanager +def _monter(site: "Site | None" = None, locataire: "Locataire | None" = None): + """Monte CE site et CE locataire le temps d'interroger les modules d'avant. + + `underlay`, `site_intrants`, `instancier` lisent « le site monte » et « l'instance + courante » (variables `SETOPS_*`, sinon les liens). Plutot que de recopier leurs calculs, + on leur designe l'ecosysteme voulu, puis on remet l'environnement tel quel. C'est une + passerelle de transition : a l'etape 3, ces modules recevront leurs objets. + """ + clefs = ("SETOPS_UNDERLAY", "SETOPS_INSTANCE") + avant = {k: os.environ.get(k) for k in clefs} + try: + if site is not None: + os.environ["SETOPS_UNDERLAY"] = str(site.underlay) + if locataire is not None: + os.environ["SETOPS_INSTANCE"] = str(locataire.depot) + yield + finally: + for k, v in avant.items(): + if v is None: + os.environ.pop(k, None) + else: + os.environ[k] = v + + # --- LE TRONC COMMUN ---------------------------------------------------------------------- class Ecosysteme: @@ -210,6 +245,50 @@ class Site(Ecosysteme): presents = {l.nom for l in self.locataires()} return [n for n in self.noms_locataires() if n not in presents] + # LE CONTRAT, SENS SITE -> LOCATAIRE (§2.4) --------------------------------------------- + def fiche_pour(self, locataire: "Locataire") -> dict: + """Ce que CE site attribue et offre a CE locataire, en une fiche. + + Trois canaux le portaient jusqu'ici, sans fiche (relevé du 2026-10-04) : + - des COPIES a la main : index, adresse publique, intrants de service, racine ; + - une LECTURE DIRECTE du site par `instancier.py` au moment de generer l'inventaire : + delegation DNS, serveurs de temps, mode de routage ; + - le genome (hors fiche : il passe par la forge). + La fiche les reunit, calcules par LES MEMES fonctions qu'aujourd'hui. Elle porte + l'empreinte de ses sources : une fiche qui ne correspond plus a son site se voit. + """ + import site_intrants as si + with _monter(self, locataire): + contrat = si.contrat() + carte = underlay_mod.charger() or {} + intrants = underlay_mod.lire_plan_site("10-intrants.yml") + # Les trois lectures d'`instancier.py`, a l'identique (`_zones_deleguees`, la patte + # de transit de la frontiere, `routage_tenants`). + zone = str(intrants.get("domaine_interne") or "").strip() + resolveurs = [a for a in underlay_mod.adresses_site_portant("serveur_resolveur") if a] + transit = underlay_mod.reseau_transit(carte) or {} + reseaux_transit = {str(transit.get("nom"))} if transit.get("nom") else set() + temps = sorted({str(h["ip"]) for h in underlay_mod.hotes(carte) + if h.get("role") == "frontiere" and h.get("ip") + and str(h.get("reseau")) in reseaux_transit + and str(h.get("etat", "actif")) != "reserve"}) + routage = underlay_mod.routage_tenants(carte) + ip_publique = si.ip_publique_de(locataire.nom) + racine = self.depot / "ac-racine-site.crt" + offre = {k: v for k, v in contrat.items() if k not in ("ip_publique", "forge_amont_ac")} + return { + "site": self.nom, + "locataire": locataire.nom, + "sources": {f: _empreinte(self.depot / f) for f in + ("underlay.yml", "plan/10-intrants.yml", "opnsense.yml", "ac-racine-site.crt")}, + "attribue": {"index": self.allocations().get(locataire.nom), "ip_publique": ip_publique}, + "offre": offre, + "racine_confiance": {"fichier": racine.name, "sha256": _empreinte(racine), + "pem": racine.read_text(encoding="utf-8") if racine.is_file() else ""}, + "generation": {"zone_site": zone, "resolveurs_site": resolveurs, + "serveurs_temps": temps, "routage_tenants": routage}, + } + def decrire(self) -> dict: d = super().decrire() d.update({"locataires": self.noms_locataires(), @@ -258,6 +337,19 @@ class Locataire(Ecosysteme): def voute(self) -> Path: return self.dossier_inventaire() / "group_vars" / "all" / "vault.yml" + def domaine_interne(self) -> str: + """Son domaine interne : le premier `domaine_interne` non vide de `group_vars/all`, + dans l'ordre des fichiers — la lecture meme d'`instancier._domaine_interne`.""" + base = self.dossier_inventaire() / "group_vars" / "all" + for fichier in sorted(base.glob("*.yml")) if base.is_dir() else []: + try: + d = yaml.safe_load(fichier.read_text(encoding="utf-8")) or {} + except yaml.YAMLError: + continue + if isinstance(d, dict) and str(d.get("domaine_interne", "")).strip(): + return str(d["domaine_interne"]).strip() + return "" + def nom_hebergeur(self) -> str | None: h = ((self.filiation.get("depots") or {}).get("hebergeur") or {}).get("nom") return str(h) if h else None @@ -275,6 +367,102 @@ class Locataire(Ecosysteme): return d +# --- LA FICHE DIT-ELLE CE QUE LE LOCATAIRE PORTE ? ---------------------------------------- + +def _hostvars(inventaire: Path) -> dict: + d = _yaml(inventaire) + hv: dict = {} + + def parcourir(g: dict) -> None: + for h, v in (g.get("hosts") or {}).items(): + hv.setdefault(h, {}).update(v or {}) + for c in (g.get("children") or {}).values(): + parcourir(c or {}) + parcourir(d.get("all") or {}) + return hv + + +def verifier_fiche(site: "Site", locataire: "Locataire", fiche: dict | None = None) -> list[str]: + """Les ecarts entre la fiche du site et ce que le locataire porte AUJOURD'HUI. + + Vide = la fiche dit exactement ce que les trois canaux d'avant ont depose chez le locataire : + ses copies (intrants, index, adresse publique, racine) et son inventaire genere (temps, + delegation DNS, rattachement SDN). C'est la condition pour que l'instancier puisse, a + l'etape 3, lire la fiche au lieu du site sans que l'inventaire change d'un octet. + """ + import site_intrants as si + f = fiche if fiche is not None else site.fiche_pour(locataire) + ecarts: list[str] = [] + + # 1. L'attribution. + if f["attribue"]["index"] != locataire.index: + ecarts.append(f"index : le site attribue {f['attribue']['index']}, " + f"le locataire porte {locataire.index}") + + # 2. L'offre et l'adresse publique, contre les copies du locataire (la table de `site_intrants`). + with _monter(site, locataire): + vu = si._declare_par_le_locataire() + valeurs = dict(f["offre"], ip_publique=f["attribue"]["ip_publique"]) + for clef, copie in vu.items(): + attendu = valeurs.get(clef) + if not attendu or "{{" in str(copie): + continue + if si._normaliser(copie) != si._normaliser(attendu): + fichier, variable = si.OU_LE_LOCATAIRE_LE_DIT[clef] + ecarts.append(f"{variable} ({fichier}) : le locataire dit « {copie} », la fiche « {attendu} »") + + # 3. La racine de confiance. + copie_ac = locataire.depot / "ac-racine-site.crt" + if copie_ac.is_file() and _empreinte(copie_ac) != f["racine_confiance"]["sha256"]: + ecarts.append("ac-racine-site.crt : la copie du locataire n'est pas la racine du site") + + # 4. Ce que l'instancier a lu dans le site, tel qu'il l'a ecrit dans l'inventaire. + hv = _hostvars(locataire.inventaire()) + if not hv: + ecarts.append(f"inventaire introuvable ou vide : {locataire.inventaire()}") + return ecarts + gen = f["generation"] + temps = gen["serveurs_temps"] or None + for h, v in sorted(hv.items()): + if v.get("chrony_serveurs") != temps: + ecarts.append(f"{h} : chrony_serveurs = {v.get('chrony_serveurs')}, la fiche dit {temps}") + break + domaine_loc = locataire.domaine_interne() + attendu_zones = ([{"adresses": gen["resolveurs_site"], "nom": gen["zone_site"]}] + if gen["zone_site"] and gen["resolveurs_site"] and gen["zone_site"] != domaine_loc + else []) + portees = [v["serveur_resolveur_zones_deleguees"] for v in hv.values() + if "serveur_resolveur_zones_deleguees" in v] + normal = [sorted(({"adresses": list(z.get("adresses") or []), "nom": z.get("nom")} for z in p), + key=lambda z: str(z["nom"])) for p in portees] + if attendu_zones and not normal: + ecarts.append("delegation DNS : la fiche en offre une, l'inventaire n'en porte aucune") + for p in normal: + if p != attendu_zones and (attendu_zones or p): + ecarts.append(f"delegation DNS : l'inventaire porte {p}, la fiche dit {attendu_zones}") + break + sdn = gen["routage_tenants"] == "sdn" + for h, v in sorted(hv.items()): + if sdn and (not v.get("proxmox_pont") or v.get("proxmox_etiquette_vlan")): + ecarts.append(f"{h} : routage SDN selon la fiche, mais pont « {v.get('proxmox_pont')} », " + f"etiquette « {v.get('proxmox_etiquette_vlan')} »") + break + if not sdn and v.get("proxmox_pont"): + ecarts.append(f"{h} : routage « {gen['routage_tenants']} » selon la fiche, mais un pont SDN") + break + return ecarts + + +def couples_hebergeur() -> list[tuple["Site", "Locataire"]]: + """Chaque (site, locataire) dont ce site est l'hebergeur ACTIF — les seuls qui ont une fiche + a verifier : un site de reprise ne depose rien tant que la bascule n'a pas eu lieu.""" + out = [] + for underlay in sorted(DOSSIER_ECOSYSTEMES.glob("*/underlay.yml")): + site = Site(underlay.parent) + out += [(site, loc) for loc in site.locataires() if loc.nom_hebergeur() == site.nom] + return out + + # --- LE CONTEXTE ACTIF -------------------------------------------------------------------- def _lire_fichier_contexte(fichier: Path, dossier: Path) -> Ecosysteme: @@ -343,7 +531,30 @@ def actif(racine: Path = RACINE, dossier: Path | None = None) -> Ecosysteme | No def main(argv: list[str] | None = None) -> int: ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument("--json", action="store_true") + ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine") + ap.add_argument("--verifier-fiches", action="store_true", + help="chaque fiche dit-elle ce que le locataire porte ?") args = ap.parse_args(argv) + if args.fiche: + loc = Locataire.charger(args.fiche) + site = loc.site() + if site is None: + print(f"{loc.nom} ne nomme aucun hebergeur.") + return 1 + print(yaml.safe_dump(site.fiche_pour(loc), allow_unicode=True, sort_keys=False), end="") + return 0 + if args.verifier_fiches: + couples = couples_hebergeur() + if not couples: + print("Aucun site frere : aucune fiche a verifier.") + return 0 + total = 0 + for site, loc in couples: + e = verifier_fiche(site, loc) + total += len(e) + print(f"{'OK ' if not e else 'ECART'} {site.nom} -> {loc.nom}" + + "".join(f"\n - {x}" for x in e)) + return 1 if total else 0 try: eco = actif() erreur = None diff --git a/scripts/prouver.py b/scripts/prouver.py index e69027f..d5629eb 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -4665,6 +4665,33 @@ def preuve_runbooks_suivent_le_makefile() -> tuple[bool, str]: f"chacune portee par un assistant ou exemptee avec son motif.") +def preuve_fiche_du_site() -> tuple[bool, str]: + """La fiche que chaque site destine a ses locataires dit-elle ce qu'ils portent deja ? + + POURQUOI (2026-10-04, `docs/conception-contextes.md` §2.4). Le site informait le locataire + par trois canaux, sans fiche : des copies ecrites a la main (index, adresse publique, + intrants, racine), une lecture DIRECTE du site par l'instancier (temps, delegation DNS, + routage SDN), et le genome. La fiche les reunit. Avant qu'un seul consommateur bascule sur + elle (etape 3), elle doit dire EXACTEMENT ce que ces canaux ont depose : sinon la bascule + changerait la flotte au lieu de la decrire. + + Pour chaque couple (site, locataire) dont le site est l'hebergeur ACTIF : ecarts entre + `site.fiche_pour(locataire)` et les copies du locataire, sa racine, et son inventaire + genere. `scripts/tests/test_contexte.py` prouve que chaque sorte d'ecart est vue. + """ + sys.path.insert(0, str(RACINE / "scripts")) + import contexte as _ctx + couples = _ctx.couples_hebergeur() + if not couples: + return True, "Aucun site frere : aucune fiche a confronter." + ecarts = [f"{s.nom} -> {l.nom} : {e}" for s, l in couples for e in _ctx.verifier_fiche(s, l)] + if ecarts: + return False, (f"{len(ecarts)} ecart(s) entre la fiche du site et ce que le locataire " + f"porte : " + " | ".join(ecarts[:4])) + return True, (f"{len(couples)} fiche(s) de site : chacune dit exactement ce que son locataire " + f"porte (copies, racine, inventaire genere).") + + PREUVES: list[dict] = [ {"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"], "cmds": [["ansible-lint", "-q"]]}, @@ -4851,6 +4878,8 @@ PREUVES: list[dict] = [ "refs": [], "func": preuve_dns_public_tient}, {"id": "P83", "titre": "Assistants : le registre des runbooks ne prend pas de retard sur le Makefile", "refs": [], "func": preuve_runbooks_suivent_le_makefile}, + {"id": "P84", "titre": "La fiche du site dit exactement ce que chaque locataire porte", + "refs": [], "func": preuve_fiche_du_site}, ] diff --git a/scripts/tests/test_contexte.py b/scripts/tests/test_contexte.py index 5bc4d6e..55db640 100644 --- a/scripts/tests/test_contexte.py +++ b/scripts/tests/test_contexte.py @@ -9,7 +9,8 @@ Sur une arborescence FABRIQUEE (un site, deux locataires, un depot ambigu), on v 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. +site lui attribue, et nomme un hebergeur qui existe ; la fiche de chaque hebergeur dit +exactement ce que son locataire porte, et une fiche alteree (sept facons) est vue en ecart. """ from __future__ import annotations @@ -154,6 +155,49 @@ def vrais_depots() -> None: f"{site.nom} : aucun locataire declare sans depot ({site.locataires_absents() or 'ok'})") +def monter_restaure() -> None: + avant = {k: os.environ.get(k) for k in ("SETOPS_INSTANCE", "SETOPS_UNDERLAY")} + try: + with C._monter(C.Site(Path("/nulle/SITE")), C.Locataire(Path("/nulle/OPS"))): + dedans = (os.environ.get("SETOPS_UNDERLAY"), os.environ.get("SETOPS_INSTANCE")) + raise RuntimeError("sortie par exception") + except RuntimeError: + pass + apres = {k: os.environ.get(k) for k in ("SETOPS_INSTANCE", "SETOPS_UNDERLAY")} + verifier(dedans == ("/nulle/SITE/underlay.yml", "/nulle/OPS") and apres == avant, + "`_monter` designe le site et le locataire voulus, puis remet l'environnement, meme sur exception") + + +def fiches() -> None: + """La fiche du site dit ce que chaque locataire porte — et `verifier_fiche` voit chaque ecart.""" + import copy + couples = C.couples_hebergeur() + if not couples: + print("SAUTE aucun site frere : aucune fiche a confronter") + return + for site, loc in couples: + e = C.verifier_fiche(site, loc) + verifier(not e, f"fiche {site.nom} -> {loc.nom} : identique a ce que le locataire porte" + + (f" ({e[0]})" if e else "")) + site, loc = couples[0] + juste = site.fiche_pour(loc) + alterations = { + "index": lambda f: f["attribue"].__setitem__("index", (f["attribue"]["index"] or 0) + 1), + "adresse publique": lambda f: f["attribue"].__setitem__("ip_publique", "192.0.2.99"), + "intrant de service": lambda f: f["offre"].__setitem__("dns_amorcage", "192.0.2.53"), + "racine de confiance": lambda f: f["racine_confiance"].__setitem__("sha256", "0" * 64), + "serveurs de temps": lambda f: f["generation"].__setitem__("serveurs_temps", ["192.0.2.123"]), + "delegation DNS": lambda f: f["generation"].__setitem__("resolveurs_site", ["192.0.2.1"]), + "mode de routage": lambda f: f["generation"].__setitem__( + "routage_tenants", "switch" if f["generation"]["routage_tenants"] == "sdn" else "sdn"), + } + for nom, alterer in alterations.items(): + f = copy.deepcopy(juste) + alterer(f) + verifier(bool(C.verifier_fiche(site, loc, f)), + f"une fiche alteree ({nom}) est vue en ecart") + + def main() -> int: with tempfile.TemporaryDirectory() as tmp: d = Path(tmp) @@ -161,6 +205,8 @@ def main() -> int: classes(d) contexte_actif(d, moteur) vrais_depots() + monter_restaure() + fiches() if ECHECS: print(f"\n{len(ECHECS)} echec(s).") return 1