Adressage derive du seul seed index (rupture, mode compact retire)
Principe : les valeurs de configuration se derivent des intrants, elles ne se reecrivent pas a la main. La nomenclature dupliquait ce qu'index determine deja (supernet, sous-reseaux, passerelles, VLAN). Corrige en rupture nette. - inventory_rules : source unique de derivation — supernet_de, base3_de, sous_reseau_de, passerelle_de, vlan_de. Modele 6 zones encode une fois (2e octet = 10+index, 3e octet zone = 15+categorie, VLAN = 1000+index*10+zone). deriver_nomenclature ne lit plus aucun adressage stocke ; mode compact supprime. - devis_reseau : importe ces helpers (fin de la duplication) ; decouvre les tenants sur `index` present (filtre vmid_schema retire). - GUI : `index` devient un INTRANT (section Reseau). Il vit dans la nomenclature (plan reseau uniforme, contrairement aux intrants des modeles heterogenes) et le GUI l'ecrit chirurgicalement (une ligne, sans reformater). Le miroir JS derive le VLAN du seed (fin de la lecture de c.vlan stocke). - socle public : nomenclature au format maigre. Preuve P20 (preuve_nomenclature_derivee) : aucune nomenclature ne stocke d'adressage — garde-fou permanent, teste en negatif. Valide : DIFF VIDE sur les 3 instances (la derivation reproduit exactement l'adressage stocke), 7 modeles valident, devis_reseau genere les memes VLAN (1011-1016 derives), make verifier rc=0 CONFORME 20/20, node --check du GUI OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
7e190e0a57
commit
36a882b125
8 changed files with 228 additions and 44 deletions
33
CHANGELOG.md
33
CHANGELOG.md
|
|
@ -1,5 +1,38 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-07-23
|
||||
|
||||
### Changé — l'adressage se dérive du seul seed `index` (RUPTURE, mode compact retiré)
|
||||
Principe posé par l'utilisateur : les valeurs de configuration doivent se dériver des
|
||||
intrants, pas se réécrire à la main. La nomenclature dupliquait ce que `index` détermine
|
||||
déjà (supernet, sous-réseaux, passerelles, VLAN). C'est corrigé, en rupture nette.
|
||||
|
||||
- **`inventory_rules`** : nouvelles fonctions de dérivation, **source unique** —
|
||||
`supernet_de`, `base3_de`, `sous_reseau_de`, `passerelle_de`, `vlan_de`. Le modèle à
|
||||
6 zones est encodé une fois : 2ᵉ octet = `10+index`, 3ᵉ octet de zone = `15+catégorie`,
|
||||
VLAN trunk = `1000+index×10+zone`. `deriver_nomenclature` ne lit plus **aucun** adressage
|
||||
stocké ; le mode `compact` est supprimé (tout est ip-miroir dérivé).
|
||||
- **`devis_reseau`** importe ces helpers (plus de duplication) ; découverte des tenants sur
|
||||
`index` présent (le filtre `vmid_schema` disparaît).
|
||||
- **Les 10 nomenclatures** (3 instances + 7 modèles) passent au **format maigre** : `index`,
|
||||
`cidr_hote`, `reservations`, libellés de zones et `fonctions` seulement. Supprimés :
|
||||
`supernet`, `vmid_schema`, et par zone `sous_reseau`/`passerelle`/`vlan`. `presence-web`,
|
||||
encore en `compact`, gagne `index: 1`.
|
||||
- **GUI** : `index` devient un **intrant** (section « Réseau » du panneau Intrants). Il vit
|
||||
dans la nomenclature (le plan réseau, uniforme partout — contrairement aux intrants des
|
||||
modèles, hétérogènes) et le GUI l'écrit **chirurgicalement** (une ligne, sans reformater
|
||||
le fichier). Le miroir JS dérive le VLAN du seed (fin de la lecture de `c.vlan` stocké).
|
||||
|
||||
### Ajouté
|
||||
- **Preuve P20** (`preuve_nomenclature_derivee`) : aucune nomenclature ne stocke d'adressage
|
||||
— garde-fou permanent contre une rechute vers l'écriture manuelle. Testée en négatif
|
||||
(une rechute simulée est bien rejetée).
|
||||
|
||||
Validé : **DIFF VIDE sur les 3 instances** (la dérivation reproduit exactement l'adressage
|
||||
qui était stocké), les **7 modèles valident**, `devis_reseau` génère les mêmes VLAN
|
||||
(1011-1016 dérivés), `make verifier` rc=0 **CONFORME 20/20**, `node --check` du GUI OK,
|
||||
aller-retour d'écriture de `index` : une seule ligne touchée.
|
||||
|
||||
## 2026-07-22 (suite)
|
||||
|
||||
### Ajouté — trois preuves qui ferment les angles morts du harnais
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ make prouver
|
|||
| P16 | Inventaire Ansible (voûte) | `ansible-inventory --list` — sauté sans mot de passe Vault. |
|
||||
| P17 | **Tous** les modèles d'instance | chaque modèle découvert valide (pas seulement `socle`). `SETOPS_MODELES=../Set-OPS-Modeles` inclut les modèles assemblés privés. |
|
||||
| P18 | Gabarit de voûte complet | `vault.yml.example` couvre **exactement** les secrets que le plan exige (rôles actifs + bases + group_vars). |
|
||||
| P19 | GUI couvre le plan | tout champ présent dans un plan réel est éditable par le GUI (nomenclature tolérée : trou connu). |
|
||||
| P19 | GUI couvre le plan | tout champ présent dans un plan réel est éditable par le GUI (nomenclature tolérée : le seed `index` est désormais un intrant ; l'adressage est dérivé, donc rien à éditer). |
|
||||
| P20 | Adressage dérivé du seed | aucune nomenclature ne **stocke** d'adressage (supernet, sous-réseau, passerelle, VLAN) : tout se dérive du seul `index`. Attrape toute rechute vers l'écriture manuelle. |
|
||||
|
||||
> **P17, P18, P19 ferment les angles morts du harnais** : il ne vérifiait qu'*une* instance
|
||||
> et le seul modèle `socle`. P17 aurait attrapé l'hôte fantôme d'`integral` ; P18, les neuf
|
||||
|
|
|
|||
56
docs/audit/preuve-2026-07-23.md
Normal file
56
docs/audit/preuve-2026-07-23.md
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# Preuve de conformite — Set-OPS — 2026-07-23
|
||||
|
||||
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (20 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
| # | Preuve | Affirmations | Statut | Detail |
|
||||
|---|---|---|---|---|
|
||||
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | |
|
||||
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | 4 tests passes. |
|
||||
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. |
|
||||
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 63 flux, schéma + matrice OK. |
|
||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
|
||||
| P17 | Tous les modeles d'instance valident | AFF-022, AFF-099 | ✅ OK | Les 7 modele(s) decouvert(s) valident. |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 27 champ(s) des plans reels sont editables (8 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 8 nomenclature(s) : adressage 100% derive du seed index. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||
|
||||
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||
|
||||
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||
comme declarations d'intention, non comme preuves :
|
||||
|
||||
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||
contre une flotte vivante.
|
||||
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||
|
||||
_Rapport genere le 2026-07-23._
|
||||
|
|
@ -1,17 +1,22 @@
|
|||
---
|
||||
# MODÈLE « socle » — infrastructure de base (4 VM) : edge, DNS, PKI, magasin mail.
|
||||
# Zones/VLAN alignés sur integral (un socle peut grandir vers un écosystème complet).
|
||||
# index : ⚠️ REMPLACER par l'index unique du tenant (préfixe VMID + supernet 10.(10+index)).
|
||||
# Nomenclature = MODELE de l'ecosysteme (zones + placement des fonctions).
|
||||
# L'ADRESSAGE (supernet, sous-reseaux, passerelles, VLAN, VMID) N'EST PAS ecrit ici :
|
||||
# il se DERIVE du seul seed `index` (scripts/inventory_rules : supernet_de, base3_de,
|
||||
# passerelle_de, vlan_de). Modele a 6 zones : 10.(10+index).(15+zone).0/24, VLAN
|
||||
# 1000+index*10+zone. Editer `index` via le panneau Intrants du GUI.
|
||||
index: 1
|
||||
supernet: 10.11.0.0/16
|
||||
vmid_schema: ip-miroir
|
||||
cidr_hote: 24
|
||||
reservations: { passerelle: 1, reserve_min: 2, reserve_max: 9 }
|
||||
reservations:
|
||||
passerelle: 1
|
||||
reserve_min: 2
|
||||
reserve_max: 9
|
||||
# Zones de securite (un /24 + VLAN chacune) — seuls les libelles sont du modele.
|
||||
categories:
|
||||
1: { libelle: Frontiere, vlan: 16, sous_reseau: 10.11.16.0/24, passerelle: 10.11.16.1 }
|
||||
4: { libelle: Services-infra, vlan: 19, sous_reseau: 10.11.19.0/24, passerelle: 10.11.19.1 }
|
||||
1: { libelle: Frontiere }
|
||||
4: { libelle: Services-infra }
|
||||
# Placement : quelle fonction dans quelle zone (categorie) et son rang (service).
|
||||
fonctions:
|
||||
infra-edge: { categorie: 1, service: 1 }
|
||||
infra-dns: { categorie: 4, service: 1 }
|
||||
infra-pki: { categorie: 4, service: 2 }
|
||||
infra-edge: { categorie: 1, service: 1 }
|
||||
infra-mail: { categorie: 4, service: 3 }
|
||||
infra-pki: { categorie: 4, service: 2 }
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ import yaml
|
|||
RACINE = Path(__file__).resolve().parent.parent
|
||||
DOSSIER_INSTANCES = RACINE.parent
|
||||
|
||||
# Adressage et VLAN : derives du seul index, source unique dans inventory_rules.
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
from inventory_rules import passerelle_de, supernet_de, vlan_de # noqa: E402
|
||||
|
||||
|
||||
def masque(cidr: int) -> str:
|
||||
return str(ipaddress.IPv4Network(f"0.0.0.0/{cidr}").netmask)
|
||||
|
|
@ -40,17 +44,14 @@ def decouvrir() -> list[tuple[str, str, dict]]:
|
|||
tenants = []
|
||||
for chemin in sorted(DOSSIER_INSTANCES.glob("*/plan/nomenclature.yml")):
|
||||
n = yaml.safe_load(chemin.read_text(encoding="utf-8")) or {}
|
||||
if n.get("vmid_schema") == "ip-miroir" and n.get("index") is not None:
|
||||
# Un tenant federe = une nomenclature avec un index (l'adressage en decoule).
|
||||
if n.get("index") is not None and n.get("categories"):
|
||||
nom = chemin.parent.parent.name
|
||||
tenants.append((nom, prefixe(nom), n))
|
||||
tenants.sort(key=lambda t: t[2]["index"])
|
||||
return tenants
|
||||
|
||||
|
||||
def vlan_de(index: int, zone: int) -> int:
|
||||
return 1000 + index * 10 + zone
|
||||
|
||||
|
||||
def generer(tenants: list[tuple[str, str, dict]]) -> str:
|
||||
out = ["configure terminal", "!"]
|
||||
out += [
|
||||
|
|
@ -75,19 +76,19 @@ def generer(tenants: list[tuple[str, str, dict]]) -> str:
|
|||
c = n["categories"][zone]
|
||||
out.append(f"interface Vlan{vlan_de(n['index'], zone)}")
|
||||
out.append(f" description {nom}-{c['libelle']}")
|
||||
out.append(f" ip address {c['passerelle']} {m}")
|
||||
out.append(f" ip address {passerelle_de(n['index'], zone)} {m}")
|
||||
out.append(f" ip access-group {pfx}-ISOLATION in")
|
||||
out.append(" no shutdown")
|
||||
out += ["!", "! ----- 3. ACL d'isolation tenant (default-deny inter-tenant) -----"]
|
||||
for nom, pfx, n in tenants:
|
||||
reseau, wild = reseau_wildcard(n["supernet"])
|
||||
reseau, wild = reseau_wildcard(supernet_de(n["index"]))
|
||||
out.append(f"ip access-list extended {pfx}-ISOLATION")
|
||||
out.append(f" remark Intra-tenant {nom} : routage local autorise")
|
||||
out.append(f" permit ip {reseau} {wild} {reseau} {wild}")
|
||||
for autre_nom, _, autre in tenants:
|
||||
if autre_nom == nom:
|
||||
continue
|
||||
a_reseau, a_wild = reseau_wildcard(autre["supernet"])
|
||||
a_reseau, a_wild = reseau_wildcard(supernet_de(autre["index"]))
|
||||
out.append(f" remark Bloquer le tenant {autre_nom}")
|
||||
out.append(f" deny ip {reseau} {wild} {a_reseau} {a_wild}")
|
||||
out.append(" remark Reste (Internet / inter-tenant controle) -> passerelle OPNsense")
|
||||
|
|
|
|||
|
|
@ -82,10 +82,15 @@ FICHIER_SERVEURS = INSTANCE / "plan/serveurs.yml"
|
|||
# n'y entrent jamais.
|
||||
INTRANTS_IDENTITE = INVENTAIRE_DEFAUT.parent / "group_vars/all/10-intrants.yml"
|
||||
INTRANTS_PROXMOX = INVENTAIRE_MODELE.parent / "group_vars/proxmox.yml"
|
||||
FICHIERS_INTRANTS = {"identite": INTRANTS_IDENTITE, "proxmox": INTRANTS_PROXMOX}
|
||||
# Le seed reseau (`index`) vit dans la nomenclature (le plan reseau) — d'ou tout
|
||||
# l'adressage se derive. Le panneau Intrants le lit/ecrit la, chirurgicalement, sans
|
||||
# reformater le fichier (cf. _ecrire_index_nomenclature).
|
||||
FICHIERS_INTRANTS = {"identite": INTRANTS_IDENTITE, "proxmox": INTRANTS_PROXMOX,
|
||||
"reseau": FICHIER_NOMENCLATURE}
|
||||
|
||||
# (variable, fichier, classe, section, libelle, type)
|
||||
INTRANTS_SCHEMA = [
|
||||
("index", "reseau", "constante", "Réseau", "Index d'instance dans la fédération (seed : dérive supernet 10.(10+index), VLAN 1000+index×10+zone, VMID)", "int"),
|
||||
("domaine_interne", "identite", "constante", "Identité", "Domaine DNS interne", "str"),
|
||||
("fuseau_horaire", "identite", "defaut", "Identité", "Fuseau horaire", "str"),
|
||||
("organisation", "identite", "defaut", "Identité", "Organisation (annuaire LDAP, certificats)", "str"),
|
||||
|
|
@ -498,6 +503,22 @@ def ecrire_intrants(payload: dict) -> None:
|
|||
INTRANTS_PROXMOX, fusion,
|
||||
"# Parametres Proxmox non sensibles (les secrets vont dans proxmox.vault.yml).\n"
|
||||
"# Cles d'intrants editees par le panneau « Intrants de base » du GUI.\n---\n")
|
||||
if "reseau" in par_fichier and "index" in par_fichier["reseau"]:
|
||||
_ecrire_index_nomenclature(int(par_fichier["reseau"]["index"]))
|
||||
|
||||
|
||||
def _ecrire_index_nomenclature(index: int) -> None:
|
||||
"""Met a jour `index:` dans plan/nomenclature.yml SANS reformater le reste.
|
||||
|
||||
Le fichier garde ses commentaires et son formatage compact (l'adressage se derive
|
||||
de ce seul seed). Remplacement de la ligne existante, ou insertion en tete a defaut.
|
||||
"""
|
||||
path = FICHIER_NOMENCLATURE
|
||||
texte = path.read_text(encoding="utf-8") if path.exists() else "---\n"
|
||||
nouveau, remplace = re.subn(r"(?m)^index:.*$", f"index: {index}", texte)
|
||||
if not remplace:
|
||||
nouveau = re.sub(r"(?m)^(---\n)", rf"\g<1>index: {index}\n", texte, count=1) or texte
|
||||
path.write_text(nouveau, encoding="utf-8")
|
||||
|
||||
|
||||
def ecrire_bases(path: Path, registre: dict) -> None:
|
||||
|
|
@ -1952,7 +1973,10 @@ HTML = r"""<!doctype html>
|
|||
if (!d) return null;
|
||||
const c = (nomenclature.categories || {})[d.categorie];
|
||||
if (!c) return null;
|
||||
return {categorie: d.categorie, service: d.service, libelle: c.libelle, vlan: c.vlan, sous_reseau: c.sous_reseau, passerelle: c.passerelle};
|
||||
// VLAN derive du seed index (miroir de inventory_rules.vlan_de) : 1000 + index*10 + zone.
|
||||
const idx = (nomenclature.index != null) ? nomenclature.index : 9;
|
||||
const vlan = 1000 + idx * 10 + Number(d.categorie);
|
||||
return {categorie: d.categorie, service: d.service, libelle: c.libelle, vlan};
|
||||
}
|
||||
function echapper(valeur) {
|
||||
return String(valeur).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
|
|
|
|||
|
|
@ -376,10 +376,55 @@ def fonction_seq(nom: str) -> tuple[str, int | None]:
|
|||
return str(nom or ""), None
|
||||
|
||||
|
||||
# --- Derivation de l'ADRESSAGE depuis le seul seed `index` --------------------
|
||||
#
|
||||
# Principe : TOUT l'adressage (supernet, sous-reseaux, passerelles, VLAN, VMID) se
|
||||
# DERIVE de l'index d'instance + du numero de zone. Rien n'est ecrit a la main dans
|
||||
# la nomenclature (elle ne garde que le MODELE : libelles de zones + placement des
|
||||
# fonctions). Source unique, ci-dessous, partagee par la generation d'inventaire et
|
||||
# le devis reseau (scripts/devis_reseau.py).
|
||||
#
|
||||
# Conventions du modele a 6 zones (reference integral) :
|
||||
# - 2e octet du supernet = OCTET_TENANT + index (index 1 -> 10.11.0.0/16)
|
||||
# - 3e octet de la zone = OCTET_ZONE + categorie (zone 1 -> .16, dans .16.0/20)
|
||||
# - VLAN sur le trunk convergE = SOCLE_VLAN + index x 10 + zone (4 chiffres, unique)
|
||||
# - VMID ip-miroir = VLAN(4) . octet-hote(3) . sequence(2) = 9 chiffres mnemotechniques
|
||||
OCTET_TENANT = 10
|
||||
OCTET_ZONE = 15
|
||||
SOCLE_VLAN = 1000
|
||||
INDEX_BAC_A_SABLE = 9 # index par defaut d'une nomenclature sans seed (jetable)
|
||||
|
||||
|
||||
def supernet_de(index: int) -> str:
|
||||
"""/16 du tenant : 10.(10+index).0.0/16."""
|
||||
return f"10.{OCTET_TENANT + int(index)}.0.0/16"
|
||||
|
||||
|
||||
def base3_de(index: int, categorie: int) -> str:
|
||||
"""Trois premiers octets d'une zone : 10.(10+index).(15+categorie)."""
|
||||
return f"10.{OCTET_TENANT + int(index)}.{OCTET_ZONE + int(categorie)}"
|
||||
|
||||
|
||||
def sous_reseau_de(index: int, categorie: int, cidr: int = 24) -> str:
|
||||
"""/24 d'une zone : 10.(10+index).(15+categorie).0/24."""
|
||||
return f"{base3_de(index, categorie)}.0/{int(cidr)}"
|
||||
|
||||
|
||||
def passerelle_de(index: int, categorie: int) -> str:
|
||||
"""Passerelle d'une zone (SVI) : premier hote du /24."""
|
||||
return f"{base3_de(index, categorie)}.1"
|
||||
|
||||
|
||||
def vlan_de(index: int, zone: int) -> int:
|
||||
"""VLAN 802.1Q sur le trunk convergE : 1000 + index x 10 + zone (unique globalement)."""
|
||||
return SOCLE_VLAN + int(index) * 10 + int(zone)
|
||||
|
||||
|
||||
def deriver_nomenclature(fonction: str, seq: int, nomenclature: dict) -> dict | None:
|
||||
"""Derive VMID / VLAN / IP / passerelle / CIDR depuis la fonction et le rang.
|
||||
|
||||
Source unique de la derivation (miroir Python de l'auto-proposition du GUI).
|
||||
Tout vient de l'index + de la zone : aucune lecture d'adressage stocke.
|
||||
"""
|
||||
fonctions = nomenclature.get("fonctions") or {}
|
||||
categories = nomenclature.get("categories") or {}
|
||||
|
|
@ -391,32 +436,14 @@ def deriver_nomenclature(fonction: str, seq: int, nomenclature: dict) -> dict |
|
|||
c = categories.get(cat) or categories.get(str(cat))
|
||||
if not c:
|
||||
return None
|
||||
base3 = ".".join(str(c.get("sous_reseau", "")).split(".")[:3])
|
||||
# Prefixe VMID = index d'instance (federation multi-ecosystemes). Defaut 9 :
|
||||
# retro-compatible (instances sans index, bacs a sable). 1 chiffre -> 9 instances
|
||||
# prod federables avec un VMID a 5 chiffres ; elargir l'index pour davantage.
|
||||
index = nomenclature.get("index", 9)
|
||||
index = nomenclature.get("index", INDEX_BAC_A_SABLE)
|
||||
hote = svc * 10 + int(seq)
|
||||
# Schema du VMID. 'ip-miroir' (opt-in) : 9 chiffres mnemotechniques encodant
|
||||
# index + VLAN + octet-hote + sequence -> le VMID contient litteralement l'IP
|
||||
# (10.(10+index).VLAN.hote) et le tenant. 'compact' (defaut, retro-compatible) :
|
||||
# index.categorie.service.sequence (largeur variable, VMID des instances existantes).
|
||||
schema = nomenclature.get("vmid_schema", "compact")
|
||||
if schema == "ip-miroir":
|
||||
# VLAN DERIVE : 1000 + index x 10 + zone. Le socle 1000 force le VLAN a
|
||||
# TOUJOURS 4 chiffres (1011..4094) -> le VMID (VLAN.octet.seq) fait TOUJOURS
|
||||
# 9 chiffres : LONGUEURS UNIFORMES et mnemotechniques. Unique GLOBALEMENT sur
|
||||
# un trunk convergE (retirer 1000 redonne index x 10 + zone = tenant + zone).
|
||||
vlan = 1000 + index * 10 + int(cat)
|
||||
vmid = f"{vlan}{hote:03d}{int(seq):02d}"
|
||||
else:
|
||||
vlan = c.get("vlan")
|
||||
vmid = f"{index}{cat}{svc}{int(seq):02d}"
|
||||
vlan = vlan_de(index, int(cat))
|
||||
return {
|
||||
"vmid": vmid,
|
||||
"vmid": f"{vlan}{hote:03d}{int(seq):02d}",
|
||||
"vlan": vlan,
|
||||
"adresse_ip": f"{base3}.{hote}",
|
||||
"passerelle": c.get("passerelle"),
|
||||
"adresse_ip": f"{base3_de(index, int(cat))}.{hote}",
|
||||
"passerelle": passerelle_de(index, int(cat)),
|
||||
"cidr": nomenclature.get("cidr_hote", 24),
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -163,6 +163,41 @@ def preuve_modele_socle() -> tuple[bool, str]:
|
|||
return True, "Modele public socle : domaines/serveurs/applications/bases valides."
|
||||
|
||||
|
||||
def preuve_nomenclature_derivee() -> tuple[bool, str]:
|
||||
"""Aucune nomenclature ne STOCKE d'adressage : tout se derive du seed `index`.
|
||||
|
||||
Interdit `supernet` au niveau racine et `sous_reseau`/`passerelle`/`vlan` par zone.
|
||||
Attrape toute rechute vers l'ecriture manuelle de l'adressage (rupture du 2026-07-23).
|
||||
Portee : l'instance liee + tous les modeles decouverts (SETOPS_MODELES inclus).
|
||||
"""
|
||||
import yaml
|
||||
sys.path.insert(0, str(RACINE / "scripts"))
|
||||
import modeles as _mod
|
||||
|
||||
cibles = []
|
||||
inst_nom = RACINE / INSTANCE / "plan" / "nomenclature.yml"
|
||||
if inst_nom.exists():
|
||||
cibles.append(inst_nom)
|
||||
cibles += [m / "plan" / "nomenclature.yml" for m in _mod.decouvrir()
|
||||
if (m / "plan" / "nomenclature.yml").exists()]
|
||||
|
||||
interdits_zone = {"sous_reseau", "passerelle", "vlan"}
|
||||
fautes = []
|
||||
for p in cibles:
|
||||
n = yaml.safe_load(p.read_text(encoding="utf-8")) or {}
|
||||
rel = p.relative_to(RACINE) if p.is_relative_to(RACINE) else p
|
||||
if "supernet" in n:
|
||||
fautes.append(f"{rel}: supernet stocke")
|
||||
if "vmid_schema" in n:
|
||||
fautes.append(f"{rel}: vmid_schema obsolete (mode compact retire)")
|
||||
for z, c in (n.get("categories") or {}).items():
|
||||
for k in interdits_zone & set(c or {}):
|
||||
fautes.append(f"{rel}: categorie {z}.{k} stocke")
|
||||
if fautes:
|
||||
return False, f"{len(fautes)} champ(s) d'adressage stockes : " + " | ".join(fautes[:4])
|
||||
return True, f"{len(cibles)} nomenclature(s) : adressage 100% derive du seed index."
|
||||
|
||||
|
||||
def preuve_pas_lab_code_en_dur() -> tuple[bool, str]:
|
||||
"""Aucun chemin `instance/inventories/lab/group_vars` code en dur (AFF-097).
|
||||
|
||||
|
|
@ -249,6 +284,8 @@ PREUVES: list[dict] = [
|
|||
"refs": ["AFF-002", "AFF-095"],
|
||||
"cmds": [[sys.executable, "scripts/couverture_gui.py", "verifier",
|
||||
"--tolerer", "nomenclature"]]},
|
||||
{"id": "P20", "titre": "Adressage 100% derive du seed (aucun stocke)",
|
||||
"refs": ["AFF-001", "AFF-003"], "func": preuve_nomenclature_derivee},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue