intrants du site : ce qu un locataire doit savoir pour l habiter, derive
Un locataire ECRIT les adresses des services de son site — resolveur, cache, forge, depot de sauvegarde, plan d administration, sortie. Une copie se perime, et deux l avaient fait en deux jours avec la meme forme : `serveur_ops_forge_amont` visait 10.0.33.11 quand la forge sert en 10.37.33.11, et `ac-racine-site.crt` portait la racine d avant la reconstruction du site. Rien ne les relisait. `make site-intrants` lit le plan du site et rend le contrat — sept valeurs, toutes derivees. `make site-intrants-verifier` les confronte a ce que le locataire declare, et P73 en fait une preuve. Elle a trouve le defaut de la forge des sa premiere execution. Le contrat se DERIVE du plan du site, pas d une liste tenue a part : ajouter un service prete au site l ajoute au contrat, sans qu on ait a y penser. P69 RESTREINTE AU COUPLE MONTE. Elle balayait tous les depots OPS-* et les comparait au site monte. Elle avait raison tant qu un seul site existait : une adresse en 10.x.3z ne pouvait designer que lui. Deux sites decoupent leurs zones de la meme facon — c est le but, un locataire doit pouvoir habiter l un ou l autre sans se renumeroter. Le troisieme octet a cesse de distinguer « mon site » d « un autre site » : 10.31.34.11, juste pour un locataire de TechnoLibre, etait declare faux parce que Chezlepro etait monte. Un locataire n appartient a aucun site — il en habite un, choisi par le symlink au deploiement. La seule paire jugeable est celle qui est montee. Meme portee que P73. Une marche payee : la premiere version de site_intrants recopiait la resolution d instance au lieu de la partager. P41 a mordu — neuf modules avaient deja porte chacun leur copie, et cinq defauts en etaient sortis en cinq jours. make prouver : 72 OK, 0 echec, 1 saute. ansible-lint : 0 failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
b4e3520561
commit
025776064a
7 changed files with 308 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)
|
||||
|
||||
`--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 72 preuves de `make prouver` : elles lisent le dépôt, sans le
|
||||
C'est aussi ce que font les 73 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.**
|
||||
|
||||
|
|
|
|||
52
CHANGELOG.md
52
CHANGELOG.md
|
|
@ -1,5 +1,57 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-13 (3) — Un site expose ce dont ses locataires ont besoin pour l'habiter
|
||||
|
||||
### Le constat
|
||||
|
||||
Un locataire ECRIT dans ses intrants les adresses des services de son site : ou resoudre,
|
||||
ou prendre ses paquets, ou cloner le genome, ou deposer son etat. Une copie se perime, et
|
||||
deux l'avaient fait en deux jours, avec exactement la meme forme :
|
||||
|
||||
- `serveur_ops_forge_amont: https://10.0.33.11` alors que la forge sert en `10.37.33.11`
|
||||
depuis que le site a pris son propre index. Le commentaire juste au-dessus expliquait
|
||||
encore pourquoi l'ancienne adresse figurait dans les SAN du certificat : le raisonnement
|
||||
etait intact, la valeur non.
|
||||
- `ac-racine-site.crt` versionne portait la racine du site d'AVANT sa reconstruction.
|
||||
|
||||
Aucune des deux n'etait relue par quoi que ce soit.
|
||||
|
||||
### `make site-intrants`
|
||||
|
||||
Le site lit son propre plan et rend le contrat — sept valeurs, toutes DERIVEES :
|
||||
|
||||
```
|
||||
dns_amorcage · artefacts_amorcage · setops_depot_binaires · serveur_ops_forge_amont
|
||||
client_backup_cible · nftables_admin_ssh · passerelle_sortie
|
||||
```
|
||||
|
||||
`make site-intrants-verifier` les confronte a ce que le locataire monte declare, et **P73**
|
||||
en fait une preuve. Elle a trouve le defaut de la forge des sa premiere execution.
|
||||
|
||||
Le contrat se DERIVE du plan du site, pas d'une liste tenue a part : ajouter un service
|
||||
prete au site l'ajoute au contrat, sans qu'on ait a y penser.
|
||||
|
||||
### P69 restreinte au couple monte
|
||||
|
||||
Elle balayait TOUS les depots `OPS-*` et les comparait au site MONTE. Elle avait raison
|
||||
tant qu'un seul site existait : une adresse « en 10.x.3z » ne pouvait designer que lui.
|
||||
|
||||
Deux sites decoupent leurs zones de la meme facon — c'est le but, un locataire doit pouvoir
|
||||
habiter l'un ou l'autre sans se renumeroter. Le troisieme octet a donc cesse de distinguer
|
||||
« mon site » d'« un autre site » : `10.31.34.11`, parfaitement juste pour un locataire de
|
||||
TechnoLibre, etait declare faux parce que Chezlepro etait monte.
|
||||
|
||||
**Un locataire n'appartient a aucun site — il en habite un**, choisi par le symlink au
|
||||
moment du deploiement. La seule paire qu'on puisse juger est donc celle qui est montee.
|
||||
|
||||
### Une marche payee en chemin
|
||||
|
||||
La premiere version de `site_intrants` ecrivait `RACINE / "instance" / "inventories" /
|
||||
"principal"`. **P41 a mordu** : neuf modules avaient deja porte chacun leur copie de cette
|
||||
resolution, et cinq defauts en etaient sortis en cinq jours. La preuve a attrape la
|
||||
dixieme avant qu'elle serve.
|
||||
|
||||
|
||||
## 2026-09-13 (2) — L'annuaire cesse de tout ouvrir avec la meme cle
|
||||
|
||||
### Ce que la mesure a montre
|
||||
|
|
|
|||
7
Makefile
7
Makefile
|
|
@ -776,6 +776,13 @@ devis-proxmox-fw: ansible-runtime ## Devis pare-feu Proxmox (est-ouest intra-ten
|
|||
devis-proxmox-fw-verifier: ## Verifie le devis du pare-feu est-ouest Proxmox (aucune ecriture)
|
||||
python3 scripts/devis_proxmox_fw.py --verifier
|
||||
|
||||
.PHONY: site-intrants site-intrants-verifier
|
||||
site-intrants: ## Les intrants que le SITE monte expose a ses locataires (derives)
|
||||
python3 scripts/site_intrants.py $(if $(JSON),--json,)
|
||||
|
||||
site-intrants-verifier: ## Le locataire monte suit-il encore son site ? (aucune ecriture)
|
||||
python3 scripts/site_intrants.py --verifier
|
||||
|
||||
.PHONY: devis-proxmox-pools devis-proxmox-pools-verifier
|
||||
devis-proxmox-pools: ansible-runtime ## Devis des pools Proxmox (un par tenant), derive du plan
|
||||
python3 scripts/devis_proxmox_pools.py $(if $(JSON),--json,)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (71 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (72 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 4 pool(s) Proxmox, 41 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 62 scripts expliques et atteignables, 119 cibles make documentees, 68 roles avec README. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 63 scripts expliques et atteignables, 121 cibles make documentees, 68 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (139 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (39 genere(s) exempte(s)). |
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 58 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 59 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
|
||||
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 153 regle(s) du site. |
|
||||
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. |
|
||||
|
|
@ -69,7 +69,7 @@
|
|||
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. |
|
||||
| P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. |
|
||||
| P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. |
|
||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (72 preuves, 68 roles, 41 groupes). |
|
||||
| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (73 preuves, 68 roles, 41 groupes). |
|
||||
| P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. |
|
||||
| P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). |
|
||||
| P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `904ece3` (publie le 2026-09-12). |
|
||||
|
|
@ -85,6 +85,7 @@
|
|||
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
|
||||
| P71 | Pool du site : le genome ne nait pas chez un tenant | — | ✅ OK | `site-creer` nomme `--pool-site` ; le pool du genome ne peut plus etre celui d'un tenant. |
|
||||
| P72 | Annuaire : aucun service ne se lie avec le compte du maitre | — | ✅ OK | 5 role(s) consultent l'annuaire, chacun avec SON compte de service ; seul `amorcage_acces` garde celui d'administration, et il provisionne au lieu de consommer. |
|
||||
| P73 | Le locataire designe les services de son site REEL | — | ✅ OK | CONFORME : 5 intrant(s) du locataire concordent avec ce que le site expose. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -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 72 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le
|
||||
`scripts/prouver.py` porte 73 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.
|
||||
|
|
|
|||
|
|
@ -3313,6 +3313,35 @@ def preuve_pool_du_site_nest_pas_celui_du_tenant() -> tuple[bool, str]:
|
|||
|
||||
|
||||
|
||||
|
||||
def preuve_locataire_suit_son_site() -> tuple[bool, str]:
|
||||
"""Le locataire monte designe-t-il encore les services de son site REEL ?
|
||||
|
||||
UN SITE EST UNE INTERFACE. Un locataire qui l'habite a besoin d'un petit nombre de
|
||||
valeurs : ou resoudre, ou prendre ses paquets et ses binaires, ou cloner le genome, ou
|
||||
deposer son etat. Il les ECRIT dans ses intrants — et une copie se perime.
|
||||
|
||||
DEUX FOIS EN DEUX JOURS, avec la meme forme :
|
||||
- `serveur_ops_forge_amont: https://10.0.33.11` alors que la forge sert en
|
||||
10.37.33.11 depuis que le site a pris son propre index. Le commentaire au-dessus
|
||||
expliquait encore pourquoi l'ancienne adresse etait dans les SAN du certificat :
|
||||
le raisonnement intact, la valeur perimee.
|
||||
- une carte de site dans le depot du LOCATAIRE, decrivant un index 23 quand le vrai
|
||||
disait 31.
|
||||
|
||||
P69 couvrait deja `dns_amorcage` et `artefacts_amorcage`. Celle-ci couvre le reste du
|
||||
contrat, et se derive du site plutot que d'une liste tenue a part : ajouter un service
|
||||
prete au site l'ajoute ici, sans qu'on ait a y penser.
|
||||
"""
|
||||
import subprocess
|
||||
r = subprocess.run([sys.executable, str(RACINE / "scripts" / "site_intrants.py"),
|
||||
"--verifier"], capture_output=True, text=True, cwd=RACINE)
|
||||
sortie = (r.stdout + r.stderr).strip()
|
||||
if r.returncode == 0:
|
||||
return True, sortie.splitlines()[0] if sortie else "Aucun site monte."
|
||||
return False, " ".join(l.strip(" -") for l in sortie.splitlines() if l.strip())
|
||||
|
||||
|
||||
def preuve_annuaire_sans_compte_maitre() -> tuple[bool, str]:
|
||||
"""Aucun service ne se lie-t-il a l'annuaire avec son compte d'administration ?
|
||||
|
||||
|
|
@ -3448,8 +3477,24 @@ def preuve_amorcage_suit_le_site() -> tuple[bool, str]:
|
|||
# Les troisiemes octets des zones du site — la signature d'une adresse « du site ».
|
||||
octets_site = {a.split(".")[2] for v in attendus.values() for a in v if a.count(".") == 3}
|
||||
|
||||
# LE COUPLE MONTE, ET LUI SEUL (2026-09-13).
|
||||
#
|
||||
# Cette preuve balayait TOUS les depots `OPS-*` et les comparait au site MONTE. Elle
|
||||
# avait raison tant qu'un seul site existait : une adresse « en 10.x.3z » ne pouvait
|
||||
# designer que lui.
|
||||
#
|
||||
# Deux sites decoupent leurs zones de la meme facon — c'est le but, un locataire doit
|
||||
# pouvoir habiter l'un ou l'autre sans se renumeroter. Le troisieme octet a donc cesse
|
||||
# de distinguer « mon site » d'« un autre site » : `10.31.34.11`, parfaitement juste
|
||||
# pour un locataire de TechnoLibre, etait declare faux parce que Chezlepro etait monte.
|
||||
#
|
||||
# UN LOCATAIRE N'APPARTIENT A AUCUN SITE — il en habite un, choisi par le symlink au
|
||||
# moment du deploiement. La seule paire qu'on puisse juger est donc celle qui est
|
||||
# montee. Meme portee que P73, et pour la meme raison.
|
||||
from inventory_rules import dossier_inventaire, instance_courante
|
||||
fautes, verifies = [], 0
|
||||
for intrants in sorted((RACINE.parent).glob("OPS-*/inventories/*/group_vars/all/10-intrants.yml")):
|
||||
_f = dossier_inventaire() / "group_vars" / "all" / "10-intrants.yml"
|
||||
for intrants in ([_f] if _f.is_file() else []):
|
||||
try:
|
||||
d = yaml.safe_load(intrants.read_text(encoding="utf-8")) or {}
|
||||
except Exception:
|
||||
|
|
@ -3463,7 +3508,7 @@ def preuve_amorcage_suit_le_site() -> tuple[bool, str]:
|
|||
continue # ne pretend pas designer le site : pas notre affaire
|
||||
verifies += 1
|
||||
if adr not in bons:
|
||||
fautes.append(f"{intrants.parents[3].name} : `{cle}` vise {adr}, "
|
||||
fautes.append(f"{instance_courante().resolve().name} : `{cle}` vise {adr}, "
|
||||
f"le site porte {', '.join(bons)}")
|
||||
if not verifies:
|
||||
return True, "Aucun tenant ne s'amorce sur une adresse du site."
|
||||
|
|
@ -3638,6 +3683,8 @@ PREUVES: list[dict] = [
|
|||
"refs": [], "func": preuve_pool_du_site_nest_pas_celui_du_tenant},
|
||||
{"id": "P72", "titre": "Annuaire : aucun service ne se lie avec le compte du maitre",
|
||||
"refs": [], "func": preuve_annuaire_sans_compte_maitre},
|
||||
{"id": "P73", "titre": "Le locataire designe les services de son site REEL",
|
||||
"refs": [], "func": preuve_locataire_suit_son_site},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
193
scripts/site_intrants.py
Normal file
193
scripts/site_intrants.py
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Les intrants qu'un SITE expose a ses locataires — derives, jamais ecrits.
|
||||
|
||||
UN SITE EST UNE INTERFACE, PAS UN DECOR. Un locataire qui veut l'habiter a besoin d'un
|
||||
petit nombre de valeurs : ou resoudre, ou prendre ses paquets, ou cloner le genome, ou
|
||||
deposer son etat, quel plan d'administration laisser entrer. Aujourd'hui il les ECRIT A
|
||||
LA MAIN dans ses intrants, et le depot s'en remet a une garde pour verifier qu'elles
|
||||
n'ont pas vieilli.
|
||||
|
||||
CE QUE CA COUTE, MESURE DEUX FOIS EN DEUX JOURS :
|
||||
- `serveur_ops_forge_amont: https://10.0.33.11` chez Chezlepro — la forge avait demenage
|
||||
en 10.37.33.11 avec le renumerotage du site. Le commentaire juste au-dessus expliquait
|
||||
encore pourquoi l'ancienne adresse figurait dans les SAN du certificat : le raisonnement
|
||||
etait intact, la valeur non.
|
||||
- `OPS-Technolibre/underlay.yml` decrivait un site d'index 23 quand le vrai disait 31.
|
||||
|
||||
Les deux avaient la meme forme : une copie qui a cesse de designer ce qu'elle nommait, et
|
||||
que rien ne relisait. Une valeur DERIVEE ne peut pas avoir ce defaut.
|
||||
|
||||
CE SCRIPT NE MODIFIE RIEN. Il lit le site monte (`underlay.yml` et son `plan/`) et rend le
|
||||
contrat. `--verifier` le confronte a ce que le locataire monte declare, et c'est la que le
|
||||
retard se voit.
|
||||
|
||||
Usage :
|
||||
python3 scripts/site_intrants.py # le contrat, lisible
|
||||
python3 scripts/site_intrants.py --json # le meme, pour l'API et les gabarits
|
||||
python3 scripts/site_intrants.py --verifier # garde : le locataire suit-il son site ?
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
RACINE = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
|
||||
import underlay as underlay_mod # noqa: E402
|
||||
from inventory_rules import dossier_inventaire # noqa: E402
|
||||
import yaml # noqa: E402
|
||||
|
||||
PORT_CACHE = 3142
|
||||
PREFIXE_BINAIRES = "setops-binaires"
|
||||
|
||||
|
||||
def _une(groupe: str) -> str:
|
||||
"""L'adresse du site qui porte ce groupe, ou '' s'il n'y en a pas.
|
||||
|
||||
PLUSIEURS ADRESSES = AUCUNE REPONSE. Un service porte par deux machines demande un
|
||||
choix (lequel amorce ?) que ce script n'a pas a faire silencieusement.
|
||||
"""
|
||||
a = underlay_mod.adresses_site_portant(groupe)
|
||||
return a[0] if len(a) == 1 else ""
|
||||
|
||||
|
||||
def _nom_expose(groupe: str, domaine: str) -> str:
|
||||
"""Le nom de SERVICE que le site publie pour ce groupe, si son plan en declare un."""
|
||||
apps = underlay_mod.lire_plan_site("applications.yml").get("applications") or {}
|
||||
for a in apps.values():
|
||||
if a.get("groupe") == groupe:
|
||||
for e in (a.get("expose") or []):
|
||||
return str(e)
|
||||
hote = next((m["nom"] for m in underlay_mod.machines_site()
|
||||
if groupe in (underlay_mod.services_site().get(m["nom"]) or [])), "")
|
||||
return f"{hote}.{domaine}" if hote and domaine else ""
|
||||
|
||||
|
||||
def contrat() -> dict:
|
||||
u = underlay_mod.charger()
|
||||
if not u:
|
||||
return {}
|
||||
intrants = underlay_mod.lire_plan_site("10-intrants.yml")
|
||||
domaine = str(intrants.get("domaine_interne") or "")
|
||||
gestion = next((r.get("sous_reseau") for r in underlay_mod.reseaux(u)
|
||||
if r.get("nom") == "management"), "")
|
||||
transit = underlay_mod.reseau_transit(u) or {}
|
||||
cache = _une("serveur_artefacts")
|
||||
forge = _une("serveur_forgejo")
|
||||
|
||||
return {
|
||||
# QUI EST CE SITE
|
||||
"domaine_site": domaine,
|
||||
"index_site": underlay_mod.index_site(u),
|
||||
# OU RESOUDRE, AVANT QUE LE LOCATAIRE AIT SON PROPRE RESOLVEUR
|
||||
"dns_amorcage": _une("serveur_resolveur"),
|
||||
# OU PRENDRE SES PAQUETS, ET SES BINAIRES DE PUBLICATION
|
||||
"artefacts_amorcage": f"{cache}:{PORT_CACHE}" if cache else "",
|
||||
"setops_depot_binaires": (f"http://{cache}:{PORT_CACHE}/{PREFIXE_BINAIRES}"
|
||||
if cache else ""),
|
||||
# OU CLONER LE GENOME, ET AVEC QUELLE RACINE LUI FAIRE CONFIANCE
|
||||
"serveur_ops_forge_amont": f"https://{forge}" if forge else "",
|
||||
"forge_amont_ac": str((underlay_mod.chemin() or Path()).resolve().parent
|
||||
/ "ac-racine-site.crt"),
|
||||
# OU DEPOSER SON ETAT — par NOM : le depot sert plusieurs locataires et peut bouger
|
||||
"client_backup_cible": _nom_expose("serveur_backup_site", domaine),
|
||||
# QUI A LE DROIT D'ENTRER EN SSH
|
||||
"nftables_admin_ssh": [gestion] if gestion else [],
|
||||
# PAR OU SORTIR, ET A QUI DEMANDER L'HEURE
|
||||
"passerelle_sortie": str(transit.get("passerelle_sortie") or ""),
|
||||
}
|
||||
|
||||
|
||||
# Ce que le locataire monte declare, et sous quel nom, pour chaque clef du contrat.
|
||||
# Vide = le locataire ne declare rien de tel, et c'est legitime (il derive deja).
|
||||
OU_LE_LOCATAIRE_LE_DIT = {
|
||||
"dns_amorcage": ("all/10-intrants.yml", "dns_amorcage"),
|
||||
"artefacts_amorcage": ("all/10-intrants.yml", "artefacts_amorcage"),
|
||||
"setops_depot_binaires": ("all/10-intrants.yml", "setops_depot_binaires"),
|
||||
"serveur_ops_forge_amont": ("serveur_ops.yml", "serveur_ops_forge_amont"),
|
||||
"client_backup_cible": ("client_backup.yml", "client_backup_cible"),
|
||||
}
|
||||
|
||||
|
||||
def _declare_par_le_locataire() -> dict[str, str]:
|
||||
"""Lit, sans Ansible, ce que le locataire monte ecrit a propos de son site."""
|
||||
# LA RESOLUTION D'INSTANCE EST PARTAGEE, PAS RECOPIEE (P41). La premiere version
|
||||
# ecrivait `RACINE / "instance" / "inventories" / "principal"` — le symlink et le nom
|
||||
# du dossier en dur. Neuf modules avaient deja porte chacun leur copie, et cinq
|
||||
# defauts en etaient sortis en cinq jours. La preuve a mordu sur la dixieme.
|
||||
base = dossier_inventaire() / "group_vars"
|
||||
vu: dict[str, str] = {}
|
||||
for clef, (fichier, variable) in OU_LE_LOCATAIRE_LE_DIT.items():
|
||||
f = base / fichier
|
||||
if not f.is_file():
|
||||
continue
|
||||
d = yaml.safe_load(f.read_text(encoding="utf-8")) or {}
|
||||
if variable in d:
|
||||
vu[clef] = str(d[variable])
|
||||
return vu
|
||||
|
||||
|
||||
def verifier() -> int:
|
||||
c = contrat()
|
||||
if not c:
|
||||
print("Aucun site monte : rien a exposer.")
|
||||
return 0
|
||||
vu = _declare_par_le_locataire()
|
||||
ecarts, absents = [], []
|
||||
for clef, attendu in c.items():
|
||||
if clef not in OU_LE_LOCATAIRE_LE_DIT or not attendu:
|
||||
continue
|
||||
fichier, variable = OU_LE_LOCATAIRE_LE_DIT[clef]
|
||||
if clef not in vu:
|
||||
absents.append(f"{variable} ({fichier})")
|
||||
continue
|
||||
# `setops_depot_binaires` se DERIVE legitimement d'`artefacts_amorcage` en Jinja :
|
||||
# une expression n'est pas un retard, c'est le contraire.
|
||||
if "{{" in vu[clef]:
|
||||
continue
|
||||
if vu[clef].rstrip("/") != str(attendu).rstrip("/"):
|
||||
ecarts.append(f"{variable} : le locataire dit « {vu[clef]} », "
|
||||
f"le site expose « {attendu} »")
|
||||
if ecarts:
|
||||
print("NON CONFORME : le locataire ne suit plus son site.")
|
||||
for e in ecarts:
|
||||
print(" -", e)
|
||||
return 1
|
||||
note = f" ({len(absents)} non declare(s), donc derive(s) ou non utilise(s))" if absents else ""
|
||||
print(f"CONFORME : {len(vu)} intrant(s) du locataire concordent avec ce que le site expose{note}.")
|
||||
return 0
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__)
|
||||
ap.add_argument("--json", action="store_true")
|
||||
ap.add_argument("--verifier", action="store_true")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
if args.verifier:
|
||||
return verifier()
|
||||
c = contrat()
|
||||
if not c:
|
||||
print("Aucun site monte (`underlay.yml` absent ou brise).")
|
||||
return 0
|
||||
if args.json:
|
||||
print(json.dumps(c, indent=2, ensure_ascii=False))
|
||||
return 0
|
||||
print(f"# Intrants exposes par le site « {c['domaine_site'] or '?'} » (index {c['index_site']})")
|
||||
print("#")
|
||||
print("# A recopier dans les intrants d'un locataire qui veut l'habiter — ou mieux :")
|
||||
print("# a comparer aux siens par `--verifier`, qui dit lesquels ont pris du retard.")
|
||||
for clef, valeur in c.items():
|
||||
if clef in ("domaine_site", "index_site", "forge_amont_ac"):
|
||||
continue
|
||||
v = json.dumps(valeur, ensure_ascii=False) if isinstance(valeur, list) else valeur
|
||||
print(f"{clef}: {v}" if valeur else f"# {clef}: (le site ne porte pas ce service)")
|
||||
print(f"#\n# racine de confiance de la forge : {c['forge_amont_ac']}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
Loading…
Reference in a new issue