contexte : etape 3, l'instancier lit la fiche du site (P91)
Le site depose fiche-site.yml chez ses locataires (make fiches-site- deposer) ; l'instancier y lit temps, delegation DNS et routage. Sans le site, l'inventaire genere est identique au versionne, a l'octet pres. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
d6591cb271
commit
2beef04dc6
11 changed files with 218 additions and 8 deletions
|
|
@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente
|
||||||
## Écrire, puis relire (D-68)
|
## Écrire, puis relire (D-68)
|
||||||
|
|
||||||
`--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**.
|
`--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 90 preuves de `make prouver` : elles lisent le dépôt, sans le
|
C'est aussi ce que font les 91 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
|
moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le
|
||||||
dépôt annonce.**
|
dépôt annonce.**
|
||||||
|
|
||||||
|
|
|
||||||
38
CHANGELOG.md
38
CHANGELOG.md
|
|
@ -1,5 +1,43 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-10-05 (90) — Étape 3, premier consommateur : l'inventaire d'un locataire se génère sans son site
|
||||||
|
|
||||||
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 3 : les consommateurs lisent les
|
||||||
|
fiches au lieu des fichiers de l'autre, à résultat identique. Premier consommateur :
|
||||||
|
l'instancier.
|
||||||
|
|
||||||
|
**Le site dépose sa fiche** (décision du 2026-10-04) :
|
||||||
|
- `site.deposer_fiche(locataire)` écrit `fiche-site.yml` dans le dépôt du locataire ; aucune
|
||||||
|
date dedans, deux dépôts d'une même fiche sont identiques (vérifié) ;
|
||||||
|
- `make fiches-site-deposer` (runbook « Tenir un site en état ») dépose chez chaque locataire
|
||||||
|
dont le site est l'hébergeur actif ; le fichier se commite chez le locataire ;
|
||||||
|
- déposée chez OPS-Chezlepro et OPS-Technolibre.
|
||||||
|
|
||||||
|
**L'instancier la lit** : la zone du site et ses résolveurs (avec sa règle propre, pas de
|
||||||
|
délégation de sa propre zone), les serveurs de temps, le mode de routage, viennent de
|
||||||
|
`fiche-site.yml`. Sans fiche, il lit encore le site monté, et le dit (transition).
|
||||||
|
|
||||||
|
**Le critère, mesuré en mémoire sans rien écrire**, pour les deux locataires :
|
||||||
|
|
||||||
|
| | inventaire généré |
|
||||||
|
|---|---|
|
||||||
|
| site monté, avec fiche | **identique** au versionné, à l'octet près |
|
||||||
|
| **site absent, avec fiche** | **identique** à l'octet près |
|
||||||
|
| site absent, sans fiche | différent (sans temps, sans SDN, sans délégation DNS) |
|
||||||
|
|
||||||
|
C'est la portabilité d'un locataire : son runner, qui n'a pas le dépôt de son site, génère
|
||||||
|
désormais l'inventaire exact.
|
||||||
|
|
||||||
|
**Éprouvé** :
|
||||||
|
- **P91**, « La fiche du site est déposée à jour, et l'inventaire se génère sans le site » :
|
||||||
|
la fiche déposée est celle que le site calcule aujourd'hui, et l'instancier, lancé dans un
|
||||||
|
processus sans le site, rend l'inventaire versionné. Aucun écart chez les deux locataires.
|
||||||
|
- `test_contexte.py`, 97 contrôles : une fiche déposée altérée est vue périmée, et elle change
|
||||||
|
l'inventaire généré (le test la remet en place).
|
||||||
|
- `make verifier` conforme, **91/91**. Les trois documents comptent 91 preuves.
|
||||||
|
|
||||||
|
**À commiter** : `fiche-site.yml` chez les deux locataires.
|
||||||
|
|
||||||
## 2026-10-05 (89) — Étape 2 terminée : les sorties, et le contrat entier tient
|
## 2026-10-05 (89) — Étape 2 terminée : les sorties, et le contrat entier tient
|
||||||
|
|
||||||
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, frontière, dernier temps : ce qui
|
**Le chemin** (`docs/conception-contextes.md` §6), étape 2, frontière, dernier temps : ce qui
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -610,7 +610,7 @@ site: ansible-runtime ## Regenere playbooks/site.yml depuis les couches et le gr
|
||||||
site-verifier: ## Verifie que playbooks/site.yml correspond aux couches declarees
|
site-verifier: ## Verifie que playbooks/site.yml correspond aux couches declarees
|
||||||
python3 scripts/orchestrer.py verifier
|
python3 scripts/orchestrer.py verifier
|
||||||
|
|
||||||
.PHONY: flux flux-verifier
|
.PHONY: flux flux-verifier fiches-site-deposer
|
||||||
cles-recenser: ## Montre ce qui n'existe QUE sur ce poste (sans rien ecrire)
|
cles-recenser: ## Montre ce qui n'existe QUE sur ce poste (sans rien ecrire)
|
||||||
python3 scripts/exporter_cles.py --recenser
|
python3 scripts/exporter_cles.py --recenser
|
||||||
|
|
||||||
|
|
@ -870,6 +870,12 @@ site-intrants: ## Les intrants que le SITE monte expose a ses locataires (derive
|
||||||
site-intrants-verifier: ## Le locataire monte suit-il encore son site ? (aucune ecriture)
|
site-intrants-verifier: ## Le locataire monte suit-il encore son site ? (aucune ecriture)
|
||||||
python3 scripts/site_intrants.py --verifier
|
python3 scripts/site_intrants.py --verifier
|
||||||
|
|
||||||
|
fiches-site-deposer: ## Le site depose chez chacun de ses locataires la fiche qu'il lui destine (a commiter chez eux)
|
||||||
|
@# LE SITE DEPOSE, LE LOCATAIRE LIT (2026-10-05). Le runner d'un locataire n'a pas le depot
|
||||||
|
@# de son site : ce que le site lui attribue et lui offre lui parvient par ce fichier,
|
||||||
|
@# `fiche-site.yml`, que l'instancier lit. P91 refuse une fiche perimee.
|
||||||
|
python3 scripts/contexte.py --deposer-fiches
|
||||||
|
|
||||||
.PHONY: vpn-admin-plan vpn-admin-appliquer
|
.PHONY: vpn-admin-plan vpn-admin-appliquer
|
||||||
vpn-admin-plan: ## Acces WireGuard des admins : ce que la frontiere porte face au plan (aucune ecriture)
|
vpn-admin-plan: ## Acces WireGuard des admins : ce que la frontiere porte face au plan (aucune ecriture)
|
||||||
python3 scripts/vpn_admin.py plan
|
python3 scripts/vpn_admin.py plan
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,8 @@ croisées produisent. La première mesure des flux a trouvé une information que
|
||||||
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
|
jetait : les clients nommés d'un port aussi public, que Proxmox doit admettre nommément. Il
|
||||||
les publie désormais (`sources_declarees`). La frontière, en trois temps : les identités
|
les publie désormais (`sources_declarees`). La frontière, en trois temps : les identités
|
||||||
(P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites.
|
(P87), les entrées publiques (P88), l'administration (P89) et les sorties (P90) sont faites.
|
||||||
**L'étape 2 est terminée** (2026-10-05).
|
**L'étape 2 est terminée** (2026-10-05). Étape 3 : l'instancier lit la fiche déposée par le site, et
|
||||||
|
l'inventaire d'un locataire se génère sans son site, à l'octet près (P91).
|
||||||
|
|
||||||
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
Méthodes du contrat : `site.fiche_pour(locataire)`, `locataire.face_reseau()`. Une classe
|
||||||
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
n'ouvre jamais les fichiers de l'autre ; une preuve vérifiera la règle.
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met
|
||||||
|
|
||||||
## Le trou qu'il comble
|
## Le trou qu'il comble
|
||||||
|
|
||||||
`scripts/prouver.py` porte 90 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
`scripts/prouver.py` porte 91 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
|
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
|
cohérent **avec lui-même** — que les handlers existent, que les intrants ont un
|
||||||
propriétaire, que rien n'est codé en dur.
|
propriétaire, que rien n'est codé en dur.
|
||||||
|
|
|
||||||
|
|
@ -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 :
|
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 (90 preuves, zéro réseau)
|
make prouver le dépôt est-il cohérent avec lui-même (91 preuves, zéro réseau)
|
||||||
make remise-verifier le second temps de la remise est-il fait, ou en retard ? (P80)
|
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 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ù
|
make expositions-plan chaque service publié répond-il, et depuis où
|
||||||
|
|
|
||||||
|
|
@ -209,6 +209,12 @@ runbooks:
|
||||||
- cible: site-intrants
|
- cible: site-intrants
|
||||||
nature: mesure
|
nature: mesure
|
||||||
pourquoi: "Ce que ce site expose a ses locataires, derive et non declare deux fois."
|
pourquoi: "Ce que ce site expose a ses locataires, derive et non declare deux fois."
|
||||||
|
- cible: fiches-site-deposer
|
||||||
|
nature: ecriture
|
||||||
|
pourquoi: >-
|
||||||
|
Deposer chez chaque locataire la fiche que le site lui destine. Son runner n'a pas
|
||||||
|
le depot du site : c'est par elle que son inventaire se genere sans lui. A commiter
|
||||||
|
dans le depot de chaque locataire.
|
||||||
- cible: site-verifier
|
- cible: site-verifier
|
||||||
nature: mesure
|
nature: mesure
|
||||||
pourquoi: "Le playbook du site correspond-il encore aux couches declarees ?"
|
pourquoi: "Le playbook du site correspond-il encore aux couches declarees ?"
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,9 @@ DOSSIER_ECOSYSTEMES = RACINE.parent
|
||||||
# Le contexte actif, nomme (decision du 2026-10-04) : une seule valeur, `site:<depot>` ou
|
# Le contexte actif, nomme (decision du 2026-10-04) : une seule valeur, `site:<depot>` ou
|
||||||
# `locataire:<depot>`. Absent, on retombe sur les liens d'avant, le temps de la bascule.
|
# `locataire:<depot>`. Absent, on retombe sur les liens d'avant, le temps de la bascule.
|
||||||
NOM_FICHIER_CONTEXTE = "contexte"
|
NOM_FICHIER_CONTEXTE = "contexte"
|
||||||
|
# La fiche que le site depose chez chacun de ses locataires (decision du 2026-10-04 : le site
|
||||||
|
# DEPOSE, le runner du locataire n'a pas acces au depot du site).
|
||||||
|
NOM_FICHIER_FICHE_SITE = "fiche-site.yml"
|
||||||
NATURES = ("site", "locataire")
|
NATURES = ("site", "locataire")
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -290,6 +293,20 @@ class Site(Ecosysteme):
|
||||||
"serveurs_temps": temps, "routage_tenants": routage},
|
"serveurs_temps": temps, "routage_tenants": routage},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def deposer_fiche(self, locataire: "Locataire") -> Path:
|
||||||
|
"""Ecrit chez le locataire la fiche que ce site lui destine. Rend le chemin ecrit.
|
||||||
|
|
||||||
|
Aucune date dans le fichier : deux depots d'une meme fiche sont identiques, et un
|
||||||
|
depot qui ne change rien ne fabrique pas de difference a commiter.
|
||||||
|
"""
|
||||||
|
chemin = locataire.depot / NOM_FICHIER_FICHE_SITE
|
||||||
|
entete = (f"# GENERE par le site {self.nom} (scripts/contexte.py --deposer-fiches).\n"
|
||||||
|
"# Ne pas editer : c'est ce que le site attribue et offre a ce locataire.\n"
|
||||||
|
"# Voir docs/conception-contextes.md §2.4.\n")
|
||||||
|
chemin.write_text(entete + yaml.safe_dump(self.fiche_pour(locataire), allow_unicode=True,
|
||||||
|
sort_keys=False), encoding="utf-8")
|
||||||
|
return chemin
|
||||||
|
|
||||||
def decrire(self) -> dict:
|
def decrire(self) -> dict:
|
||||||
d = super().decrire()
|
d = super().decrire()
|
||||||
d.update({"locataires": self.noms_locataires(),
|
d.update({"locataires": self.noms_locataires(),
|
||||||
|
|
@ -351,6 +368,11 @@ class Locataire(Ecosysteme):
|
||||||
return str(d["domaine_interne"]).strip()
|
return str(d["domaine_interne"]).strip()
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
def fiche_du_site(self) -> dict | None:
|
||||||
|
"""La fiche que son hebergeur a deposee chez lui, ou None s'il n'y en a pas."""
|
||||||
|
d = _yaml(self.depot / NOM_FICHIER_FICHE_SITE)
|
||||||
|
return d or None
|
||||||
|
|
||||||
def nom_hebergeur(self) -> str | None:
|
def nom_hebergeur(self) -> str | None:
|
||||||
h = ((self.filiation.get("depots") or {}).get("hebergeur") or {}).get("nom")
|
h = ((self.filiation.get("depots") or {}).get("hebergeur") or {}).get("nom")
|
||||||
return str(h) if h else None
|
return str(h) if h else None
|
||||||
|
|
@ -998,6 +1020,44 @@ def verifier_frontiere(site: "Site", locataire: "Locataire", face: dict | None =
|
||||||
return ecarts
|
return ecarts
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_fiche_deposee(site: "Site", locataire: "Locataire") -> list[str]:
|
||||||
|
"""La fiche que le locataire porte est-elle celle que son site lui destine aujourd'hui ?"""
|
||||||
|
deposee = locataire.fiche_du_site()
|
||||||
|
if deposee is None:
|
||||||
|
return [f"aucune `{NOM_FICHIER_FICHE_SITE}` chez le locataire : la deposer "
|
||||||
|
f"(`python3 scripts/contexte.py --deposer-fiches`)"]
|
||||||
|
attendue = site.fiche_pour(locataire)
|
||||||
|
if deposee != attendue:
|
||||||
|
cles = sorted(k for k in set(deposee) | set(attendue) if deposee.get(k) != attendue.get(k))
|
||||||
|
return [f"`{NOM_FICHIER_FICHE_SITE}` perimee ({', '.join(cles)}) : la redeposer et la commiter"]
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def verifier_inventaire_sans_site(locataire: "Locataire") -> list[str]:
|
||||||
|
"""L'inventaire du locataire se genere-t-il SANS son site, a l'octet pres ?
|
||||||
|
|
||||||
|
C'est la portabilite promise (§2.4) : generer sur le runner du locataire, qui n'a pas le
|
||||||
|
depot du site, doit rendre l'inventaire versionne. On lance l'instancier dans un processus a
|
||||||
|
part, `SETOPS_UNDERLAY` pointe sur rien — ses globales se fixent a l'import.
|
||||||
|
"""
|
||||||
|
import subprocess
|
||||||
|
code = ("import sys, yaml; sys.path.insert(0, 'scripts'); import instancier\n"
|
||||||
|
"t = yaml.safe_dump(instancier.generer(), default_flow_style=False, sort_keys=True, "
|
||||||
|
"allow_unicode=True)\n"
|
||||||
|
"r = instancier.INVENTAIRE.read_text(encoding='utf-8')\n"
|
||||||
|
"r = r[r.index('all:'):] if 'all:' in r else r\n"
|
||||||
|
"print('IDENTIQUE' if t == r else 'DIFFERENT')\n")
|
||||||
|
env = dict(os.environ, SETOPS_INSTANCE=str(locataire.depot), SETOPS_UNDERLAY="/nulle/part")
|
||||||
|
env.pop("SETOPS_INVENTAIRE", None)
|
||||||
|
r = subprocess.run([sys.executable, "-c", code], capture_output=True, text=True, env=env, cwd=RACINE)
|
||||||
|
verdict = (r.stdout or "").strip().splitlines()[-1:] or [""]
|
||||||
|
if verdict[0] == "IDENTIQUE":
|
||||||
|
return []
|
||||||
|
if verdict[0] == "DIFFERENT":
|
||||||
|
return ["sans le site, l'inventaire genere differe de l'inventaire versionne"]
|
||||||
|
return [f"la generation sans le site a echoue : {(r.stderr or '').strip().splitlines()[-1:]}"]
|
||||||
|
|
||||||
|
|
||||||
def couples_hebergeur() -> list[tuple["Site", "Locataire"]]:
|
def couples_hebergeur() -> list[tuple["Site", "Locataire"]]:
|
||||||
"""Chaque (site, locataire) dont ce site est l'hebergeur ACTIF — les seuls qui ont une fiche
|
"""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."""
|
a verifier : un site de reprise ne depose rien tant que la bascule n'a pas eu lieu."""
|
||||||
|
|
@ -1077,6 +1137,8 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
|
||||||
ap.add_argument("--json", action="store_true")
|
ap.add_argument("--json", action="store_true")
|
||||||
ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine")
|
ap.add_argument("--fiche", metavar="LOCATAIRE", help="la fiche que son hebergeur lui destine")
|
||||||
|
ap.add_argument("--deposer-fiches", action="store_true",
|
||||||
|
help="chaque site ecrit sa fiche chez ses locataires (hebergeur actif)")
|
||||||
ap.add_argument("--verifier-fiches", action="store_true",
|
ap.add_argument("--verifier-fiches", action="store_true",
|
||||||
help="chaque fiche dit-elle ce que le locataire porte ?")
|
help="chaque fiche dit-elle ce que le locataire porte ?")
|
||||||
ap.add_argument("--face", metavar="LOCATAIRE", help="la face reseau qu'il publie pour son site")
|
ap.add_argument("--face", metavar="LOCATAIRE", help="la face reseau qu'il publie pour son site")
|
||||||
|
|
@ -1113,6 +1175,16 @@ def main(argv: list[str] | None = None) -> int:
|
||||||
return 1
|
return 1
|
||||||
print(yaml.safe_dump(site.fiche_pour(loc), allow_unicode=True, sort_keys=False), end="")
|
print(yaml.safe_dump(site.fiche_pour(loc), allow_unicode=True, sort_keys=False), end="")
|
||||||
return 0
|
return 0
|
||||||
|
if args.deposer_fiches:
|
||||||
|
couples = couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("Aucun site frere : aucune fiche a deposer.")
|
||||||
|
return 0
|
||||||
|
for site, loc in couples:
|
||||||
|
chemin = site.deposer_fiche(loc)
|
||||||
|
print(f" {site.nom} -> {chemin}")
|
||||||
|
print("Deposees. Les commiter dans le depot de chaque locataire.")
|
||||||
|
return 0
|
||||||
if args.verifier_fiches:
|
if args.verifier_fiches:
|
||||||
couples = couples_hebergeur()
|
couples = couples_hebergeur()
|
||||||
if not couples:
|
if not couples:
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,28 @@ def _domaine_interne() -> str:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def _fiche_du_site() -> dict | None:
|
||||||
|
"""La fiche que l'hebergeur a deposee chez CE locataire (`fiche-site.yml`), ou None.
|
||||||
|
|
||||||
|
ETAPE 3 DU CHEMIN (docs/conception-contextes.md §6). Trois valeurs de l'inventaire venaient
|
||||||
|
d'une lecture DIRECTE du site, monte sur le poste : la zone a deleguer et ses resolveurs,
|
||||||
|
les serveurs de temps, le mode de routage. Generer sur le runner du locataire, qui n'a pas
|
||||||
|
le depot du site, les aurait perdues. La fiche les porte, et l'inventaire devient le meme
|
||||||
|
ou qu'on le genere. Sans fiche (transition), on lit encore le site, et on le dit.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
import contexte
|
||||||
|
return contexte.Locataire(INSTANCE.resolve()).fiche_du_site()
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
FICHE_SITE = _fiche_du_site()
|
||||||
|
if FICHE_SITE is None:
|
||||||
|
print("note : pas de `fiche-site.yml` chez ce locataire — temps, delegation DNS et routage "
|
||||||
|
"lus directement dans le site monte (transition).", file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def _zones_deleguees() -> list:
|
def _zones_deleguees() -> list:
|
||||||
"""La zone de l'HEBERGEUR, et l'adresse a qui la demander. [] si aucune carte.
|
"""La zone de l'HEBERGEUR, et l'adresse a qui la demander. [] si aucune carte.
|
||||||
|
|
||||||
|
|
@ -116,6 +138,13 @@ def _zones_deleguees() -> list:
|
||||||
ne delegue plus rien, donc ne peut plus nommer les services du site. C'est ce qu'on
|
ne delegue plus rien, donc ne peut plus nommer les services du site. C'est ce qu'on
|
||||||
veut CONSTATER d'une emancipation, pas une panne a reparer.
|
veut CONSTATER d'une emancipation, pas une panne a reparer.
|
||||||
"""
|
"""
|
||||||
|
if FICHE_SITE is not None:
|
||||||
|
gen = FICHE_SITE.get("generation") or {}
|
||||||
|
zone = str(gen.get("zone_site") or "").strip()
|
||||||
|
if not zone or zone == _domaine_interne():
|
||||||
|
return []
|
||||||
|
adresses = [str(a) for a in gen.get("resolveurs_site") or [] if a]
|
||||||
|
return [{"nom": zone, "adresses": adresses}] if adresses else []
|
||||||
try:
|
try:
|
||||||
import underlay as underlay_mod
|
import underlay as underlay_mod
|
||||||
zone = str((underlay_mod.lire_plan_site("10-intrants.yml") or {})
|
zone = str((underlay_mod.lire_plan_site("10-intrants.yml") or {})
|
||||||
|
|
@ -240,8 +269,11 @@ def generer() -> dict:
|
||||||
# La patte de la frontiere que les tenants traversent — calculee une fois.
|
# La patte de la frontiere que les tenants traversent — calculee une fois.
|
||||||
_temps_frontiere: list = []
|
_temps_frontiere: list = []
|
||||||
try:
|
try:
|
||||||
_carte = underlay_mod.charger()
|
_carte = None if FICHE_SITE is not None else underlay_mod.charger()
|
||||||
if _carte:
|
if FICHE_SITE is not None:
|
||||||
|
_temps_frontiere = [str(x) for x in (FICHE_SITE.get("generation") or {})
|
||||||
|
.get("serveurs_temps") or []]
|
||||||
|
elif _carte:
|
||||||
# LE LIEN DE TRANSIT SE DECLARE, IL NE SE DEVINE PAS. `reseau_transit()`
|
# LE LIEN DE TRANSIT SE DECLARE, IL NE SE DEVINE PAS. `reseau_transit()`
|
||||||
# le derive de `passerelle_sortie` — le champ qui dit « c'est par la qu'on
|
# le derive de `passerelle_sortie` — le champ qui dit « c'est par la qu'on
|
||||||
# sort ». Le reconnaitre a son prefixe d'adresse aurait marche ici et menti
|
# sort ». Le reconnaitre a son prefixe d'adresse aurait marche ici et menti
|
||||||
|
|
@ -275,7 +307,9 @@ def generer() -> dict:
|
||||||
#
|
#
|
||||||
# L'ETIQUETTE DOIT ETRE VIDE en SDN : le VNet la porte deja. En poser une seconde
|
# L'ETIQUETTE DOIT ETRE VIDE en SDN : le VNet la porte deja. En poser une seconde
|
||||||
# au niveau de la carte donnerait un double etiquetage.
|
# au niveau de la carte donnerait un double etiquetage.
|
||||||
sdn = underlay_mod.routage_tenants(underlay_mod.charger()) == "sdn"
|
sdn = (str((FICHE_SITE.get("generation") or {}).get("routage_tenants")) == "sdn"
|
||||||
|
if FICHE_SITE is not None
|
||||||
|
else underlay_mod.routage_tenants(underlay_mod.charger()) == "sdn")
|
||||||
index_tenant = nomenclature.get("index")
|
index_tenant = nomenclature.get("index")
|
||||||
|
|
||||||
# UNE MACHINE SANS FONCTION DECLAREE N'A PAS D'ADRESSE — ET LE DISAIT PAS (2026-08-24).
|
# UNE MACHINE SANS FONCTION DECLAREE N'A PAS D'ADRESSE — ET LE DISAIT PAS (2026-08-24).
|
||||||
|
|
|
||||||
|
|
@ -4830,6 +4830,31 @@ def preuve_frontiere_sorties() -> tuple[bool, str]:
|
||||||
f"face reseau publie.")
|
f"face reseau publie.")
|
||||||
|
|
||||||
|
|
||||||
|
def preuve_inventaire_portable() -> tuple[bool, str]:
|
||||||
|
"""Chaque locataire porte-t-il la fiche a jour de son site, et son inventaire se genere-t-il
|
||||||
|
SANS le site, a l'octet pres ?
|
||||||
|
|
||||||
|
POURQUOI (2026-10-05, `docs/conception-contextes.md` §6, etape 3). L'instancier lisait le site
|
||||||
|
monte sur le poste pour trois valeurs (temps, delegation DNS, routage) ; genere ailleurs,
|
||||||
|
l'inventaire les perdait. Il lit desormais la fiche que le site depose. Deux conditions : la
|
||||||
|
fiche deposee est celle que le site calcule aujourd'hui ; et l'inventaire genere sans le site
|
||||||
|
est l'inventaire versionne. C'est la portabilite d'un locataire, prouvee.
|
||||||
|
"""
|
||||||
|
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 deposee a confronter."
|
||||||
|
ecarts = []
|
||||||
|
for s, l in couples:
|
||||||
|
ecarts += [f"{l.nom} : {e}" for e in _ctx.verifier_fiche_deposee(s, l)]
|
||||||
|
ecarts += [f"{l.nom} : {e}" for e in _ctx.verifier_inventaire_sans_site(l)]
|
||||||
|
if ecarts:
|
||||||
|
return False, f"{len(ecarts)} ecart(s) : " + " | ".join(ecarts[:4])
|
||||||
|
return True, (f"{len(couples)} locataire(s) : fiche du site deposee a jour, et inventaire genere "
|
||||||
|
f"sans le site identique a l'octet pres.")
|
||||||
|
|
||||||
|
|
||||||
PREUVES: list[dict] = [
|
PREUVES: list[dict] = [
|
||||||
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
{"id": "P01", "titre": "Lint (ansible-lint)", "refs": ["AFF-006"],
|
||||||
"cmds": [["ansible-lint", "-q"]]},
|
"cmds": [["ansible-lint", "-q"]]},
|
||||||
|
|
@ -5030,6 +5055,8 @@ PREUVES: list[dict] = [
|
||||||
"refs": [], "func": preuve_frontiere_administration},
|
"refs": [], "func": preuve_frontiere_administration},
|
||||||
{"id": "P90", "titre": "Ce qui sort d'un locataire vers l'Internet, la frontiere le tient de lui",
|
{"id": "P90", "titre": "Ce qui sort d'un locataire vers l'Internet, la frontiere le tient de lui",
|
||||||
"refs": [], "func": preuve_frontiere_sorties},
|
"refs": [], "func": preuve_frontiere_sorties},
|
||||||
|
{"id": "P91", "titre": "La fiche du site est deposee a jour, et l'inventaire se genere sans le site",
|
||||||
|
"refs": [], "func": preuve_inventaire_portable},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -425,6 +425,31 @@ def sorties() -> None:
|
||||||
verifier(bool(C.verifier_sorties(site, loc, f)), f"une sortie alteree ({nom}) est vue a la frontiere")
|
verifier(bool(C.verifier_sorties(site, loc, f)), f"une sortie alteree ({nom}) est vue a la frontiere")
|
||||||
|
|
||||||
|
|
||||||
|
def fiche_deposee() -> None:
|
||||||
|
"""La fiche deposee est a jour, l'inventaire se genere sans le site — et une fiche deposee
|
||||||
|
alteree se voit."""
|
||||||
|
couples = C.couples_hebergeur()
|
||||||
|
if not couples:
|
||||||
|
print("SAUTE aucun site frere : aucune fiche deposee a confronter")
|
||||||
|
return
|
||||||
|
for site, loc in couples:
|
||||||
|
e = C.verifier_fiche_deposee(site, loc)
|
||||||
|
verifier(not e, f"{loc.nom} porte la fiche que {site.nom} lui destine" + (f" ({e[0]})" if e else ""))
|
||||||
|
e = C.verifier_inventaire_sans_site(loc)
|
||||||
|
verifier(not e, f"{loc.nom} : l'inventaire se genere sans le site, a l'octet pres"
|
||||||
|
+ (f" ({e[0]})" if e else ""))
|
||||||
|
site, loc = couples[0]
|
||||||
|
chemin = loc.depot / C.NOM_FICHIER_FICHE_SITE
|
||||||
|
original = chemin.read_text(encoding="utf-8")
|
||||||
|
try:
|
||||||
|
chemin.write_text(original.replace("10.0.4.1", "192.0.2.1"), encoding="utf-8")
|
||||||
|
verifier(bool(C.verifier_fiche_deposee(site, loc)), "une fiche deposee alteree est vue perimee")
|
||||||
|
verifier(bool(C.verifier_inventaire_sans_site(loc)),
|
||||||
|
"une fiche deposee alteree change l'inventaire genere : la preuve le voit")
|
||||||
|
finally:
|
||||||
|
chemin.write_text(original, encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
with tempfile.TemporaryDirectory() as tmp:
|
with tempfile.TemporaryDirectory() as tmp:
|
||||||
d = Path(tmp)
|
d = Path(tmp)
|
||||||
|
|
@ -440,6 +465,7 @@ def main() -> int:
|
||||||
entrees_publiques()
|
entrees_publiques()
|
||||||
administration()
|
administration()
|
||||||
sorties()
|
sorties()
|
||||||
|
fiche_deposee()
|
||||||
if ECHECS:
|
if ECHECS:
|
||||||
print(f"\n{len(ECHECS)} echec(s).")
|
print(f"\n{len(ECHECS)} echec(s).")
|
||||||
return 1
|
return 1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue