Phase 1b : renommer nomenclature domaines -> fonctions

Leve la collision de nom entre les familles fonctionnelles de la
nomenclature (infra-pki, web-frontal...) et les domaines DNS.

- docs/nomenclature.yml : cle domaines -> fonctions (+ commentaires).
- inventory_gui.py : domaineDe/infoDomaine -> fonctionDe/infoFonction,
  nomenclature.fonctions, libelles et placeholder alignes.
- inventory_rules.py : docstring.

Aucun role ni inventaire impacte (nomenclature lue a la planification).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-06-23 20:20:40 -04:00
parent 27bb51d007
commit 113e78ee02
4 changed files with 21 additions and 20 deletions

View file

@ -3,6 +3,7 @@
## 2026-06-23
### Modifié
- **Phase 1b — clarté de nommage.** La clé `domaines:` de `docs/nomenclature.yml` (familles fonctionnelles : `infra-pki`, `web-frontal`…) est renommée **`fonctions:`** pour lever la collision avec les domaines DNS (`docs/domaines.yml`). L'auto-proposition du GUI suit (`fonctionDe` / `infoFonction`, `nomenclature.fonctions`, libellés « Fonction inconnue… », placeholder `fonction-NN`). Aucun rôle ni inventaire impacté : la nomenclature n'est consommée qu'au moment de la planification.
- **Refonte « application-hub », Phase 1 — les liens deviennent explicites (inventaire inchangé, zéro régression).** L'application porte désormais tous ses liens : `groupe` (capacité/rôle), `hote` (VM), `port`, **`requiert`** (autres applications dont elle dépend) et **`expose`** (FQDN publics qui la publient). L'**exposition DNS quitte `docs/domaines.yml`** (le bloc `exposition` est retiré) et vit sur l'application via `expose` ; `docs/domaines.yml` ne décrit plus que les zones (autorité, edge, secondaires, dnssec, mail). `serveurs_nginx` dérive ses vhosts des **applications** (`expose` + edge du domaine), résolvant `application → hôte → IP` (rôle + `expositions.conf.j2` réécrits). Nouvelle règle partagée `expositions_des_applications` (+ `domaine_parent`) dans `inventory_rules.py`, exposée à Ansible par le filter plugin ; `valider_applications` valide `requiert` (applis connues), `expose` (domaine parent déclaré) et `port`. CLI (`applications.py --port/--requiert/--expose`, `domaines.py` affiche les expositions dérivées) et **GUI** (champs Port/Requiert/Expose sur la fiche Application, persistés) suivent ; l'API expose aussi le registre des domaines. Le groupe n'est plus une cible de liaison, seulement une capacité. *(Renommage `nomenclature.domaines:``fonctions:` reporté en Phase 1b ; génération de l'inventaire depuis le plan en Phase 3.)*
### Ajouté

View file

@ -1,15 +1,15 @@
---
# Nomenclature Set-OPS : source unique pour hostnames, VMID, VLAN et adressage.
#
# Derivations (tout part du domaine de l'hote) :
# hostname = <domaine>-<NN> ex. web-frontal-03
# Derivations (tout part de la FONCTION de l'hote ; "domaine" est reserve au DNS) :
# hostname = <fonction>-<NN> ex. web-frontal-03
# VMID = 9 . categorie . service . NN ex. 9 5 3 03 -> 95303
# VLAN = categorie.vlan ex. 15
# IP = <prefixe_sous_reseau>.(service*10 + NN) ex. 10.1.15.33
# passerelle / CIDR : voir la categorie
#
# Contrainte : NN va de 01 a 09 par domaine (l'octet hote = service*10 + NN
# reste dans le bloc du service). Au-dela, ouvrir un nouveau domaine ou service.
# Contrainte : NN va de 01 a 09 par fonction (l'octet hote = service*10 + NN
# reste dans le bloc du service). Au-dela, ouvrir une nouvelle fonction ou service.
supernet: "10.1.0.0/16"
cidr_hote: 24
@ -27,7 +27,7 @@ categories:
5: { libelle: "Applications", vlan: 15, sous_reseau: "10.1.15.0/24", passerelle: "10.1.15.1" }
9: { libelle: "Modeles / essais", vlan: 99, sous_reseau: "10.1.99.0/24", passerelle: "10.1.99.1" }
domaines:
fonctions:
infra-pki: { categorie: 1, service: 1 }
infra-edge: { categorie: 1, service: 2 }
infra-mail: { categorie: 1, service: 3 }

View file

@ -1072,7 +1072,7 @@ HTML = r"""<!doctype html>
}
const index = selection, h = hotes[index];
const actif = h.etat === 'actif';
const info = infoDomaine(domaineDe(h.nom).domaine);
const info = infoFonction(fonctionDe(h.nom).fonction);
const zoneHint = info ? `<span class="zone-hint" title="Dérivé de la nomenclature">${echapper(info.libelle)} · VLAN ${info.vlan}</span>` : '';
const st = statutHote(h);
const libelle = actif ? (hoteBloque(h) ? 'prérequis manquant' : 'déployable') : 'planifié';
@ -1097,7 +1097,7 @@ HTML = r"""<!doctype html>
cible.innerHTML = `
<article class="detail ${hoteBloque(h) ? 'bloque' : ''}">
<div class="detail-tete">
<div class="detail-nom"><input value="${echapper(h.nom || '')}" placeholder="domaine-NN (ex. web-frontal-03)" oninput="definirNom(${index}, this.value)" onchange="autoProposer(${index})">${zoneHint}</div>
<div class="detail-nom"><input value="${echapper(h.nom || '')}" placeholder="fonction-NN (ex. web-frontal-03)" oninput="definirNom(${index}, this.value)" onchange="autoProposer(${index})">${zoneHint}</div>
<button type="button" class="fantome" title="Proposer VMID, VLAN, IP et passerelle depuis la nomenclature" onclick="proposer(${index})"> Proposer</button>
<div class="etat-bascule">
<button type="button" class="${actif ? 'on actif' : ''}" onclick="definirEtat(${index}, 'actif')">actif</button>
@ -1168,29 +1168,29 @@ HTML = r"""<!doctype html>
return lignes.join('\n');
}
function domaineDe(nom) {
function fonctionDe(nom) {
const m = (nom || '').match(/^(.+)-(\d+)$/);
return m ? {domaine: m[1], seq: parseInt(m[2], 10)} : {domaine: nom || '', seq: null};
return m ? {fonction: m[1], seq: parseInt(m[2], 10)} : {fonction: nom || '', seq: null};
}
function infoDomaine(domaine) {
const d = (nomenclature.domaines || {})[domaine];
function infoFonction(fonction) {
const d = (nomenclature.fonctions || {})[fonction];
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};
}
function prochainSeqLibre(domaine) {
function prochainSeqLibre(fonction) {
const pris = new Set();
hotes.forEach(h => { const x = domaineDe(h.nom); if (x.domaine === domaine && x.seq != null) pris.add(x.seq); });
hotes.forEach(h => { const x = fonctionDe(h.nom); if (x.fonction === fonction && x.seq != null) pris.add(x.seq); });
let s = 1; while (pris.has(s)) s++; return s;
}
function proposer(index, silencieux=false) {
const h = hotes[index];
let {domaine, seq} = domaineDe(h.nom);
const info = infoDomaine(domaine);
if (!info) { if (!silencieux) message('Domaine inconnu de la nomenclature : ' + (domaine || '(vide)'), 'erreur'); return; }
if (seq == null) { seq = prochainSeqLibre(domaine); h.nom = domaine + '-' + String(seq).padStart(2, '0'); selectionNom = h.nom; }
if (seq > 9) { if (!silencieux) message('Séquence > 9 pour ' + domaine + ' : hors plage d\'adressage, à saisir à la main.', 'erreur'); return; }
let {fonction, seq} = fonctionDe(h.nom);
const info = infoFonction(fonction);
if (!info) { if (!silencieux) message('Fonction inconnue de la nomenclature : ' + (fonction || '(vide)'), 'erreur'); return; }
if (seq == null) { seq = prochainSeqLibre(fonction); h.nom = fonction + '-' + String(seq).padStart(2, '0'); selectionNom = h.nom; }
if (seq > 9) { if (!silencieux) message('Séquence > 9 pour ' + fonction + ' : hors plage d\'adressage, à saisir à la main.', 'erreur'); return; }
const base3 = info.sous_reseau.split('.').slice(0, 3).join('.');
h.vmid = '9' + info.categorie + info.service + String(seq).padStart(2, '0');
h.vlan = String(info.vlan);
@ -1202,7 +1202,7 @@ HTML = r"""<!doctype html>
}
function autoProposer(index) {
const h = hotes[index];
if (!String(h.vmid || '').trim() && infoDomaine(domaineDe(h.nom).domaine)) proposer(index, true);
if (!String(h.vmid || '').trim() && infoFonction(fonctionDe(h.nom).fonction)) proposer(index, true);
}
function echapper(valeur) {

View file

@ -283,7 +283,7 @@ def expositions_du_groupe(registre: dict, groupe: str) -> list[dict]:
def charger_nomenclature(path: Path | None) -> dict:
"""Charge le registre de nomenclature (domaines, categories, adressage)."""
"""Charge le registre de nomenclature (fonctions, categories, adressage)."""
if not path or not path.exists():
return {}
with path.open("r", encoding="utf-8") as fichier: