CHAMPS_ECRITS_A_LA_MAIN est vide. Serveurs et applications, les deux plus gros, sont passes au generateur — chargement, rendu et sauvegarde. L EPREUVE QUI COMPTE. Ouvrir chaque vue et enregistrer sans rien toucher doit renvoyer exactement le plan qu on vient de lire : 14 serveurs, 25 applications, 2 domaines, 4 bases, IDENTIQUE partout. C est ce qui separe un formulaire genere d un formulaire qui en a l air — un champ visible a l ecran et perdu en silence a l enregistrement serait le pire des deux mondes. test_rendu_gui.py le mesure a chaque make prouver. TROIS DEFAUTS TROUVES EN CHEMIN. Le formulaire annoncait des defauts INVENTES : 2048 Mo, 2 coeurs, 16G. Il n existe aucun defaut fixe — deriver_ressources calcule depuis les roles portes (1024 et 1 pour infra-pki-01, 5632 et 4 pour collab-01). Un repere faux fait croire qu on connait la valeur. Le schema nomme le champ derive, et l ecran montre la valeur reelle de cet hote. L option vide d un select dit desormais ce qu elle produira : « (defaut : asgard) ». Une SECONDE occurrence du defaut d hier dormait dans sourceDeValeurs : elle lisait encore data.nomenclature. Elle n avait jamais leve parce que la vue Serveurs, seule a emprunter cette source, avait un formulaire ecrit a la main. Elle a leve a la seconde ou le generateur l a prise. Le banc ne voit que les chemins vivants : verifier_gui.py fait donc aussi une verification STATIQUE, qui voit ce qui dort. La validation client s accrochait a data-v, pose a la main sur trois champs. Le formulaire genere l aurait perdu et la validation serait passee au vert sur ZERO champ. Le generateur marque chaque controle, et la sauvegarde refuse si elle n en inspecte aucun. DEUX CHAMPS GARDENT LEUR EDITEUR, et le schema le dit (x-editeur) : la matrice des integrations montre les universelles et les exemptions, et l editeur de liens contraint le role a meta/liens.yml. Le generateur s efface plutot que de remplacer un editeur qui en sait plus que lui. LIMITE : je n ai toujours pas ouvert ces pages dans un navigateur. make prouver : CONFORME, 61 OK, 0 echec, 1 saute. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
211 lines
11 KiB
Markdown
211 lines
11 KiB
Markdown
# Le plan et la génération d'inventaire (méta-classe)
|
||
|
||
> **Pour qui :** le **mainteneur** — le plan et la génération de l'inventaire, à fond.
|
||
|
||
Set-OPS ne s'édite plus comme un inventaire à la main : on **décrit un plan**, et
|
||
l'inventaire Ansible en est **généré**. Le dépôt est la définition ; chaque VM en
|
||
est une instance. Ce document décrit le modèle, les registres, les commandes et
|
||
le flux de travail.
|
||
|
||
> Règle d'or : **`instance/inventories/<inventaire>/hosts.yml` est GÉNÉRÉ. Ne jamais l'éditer
|
||
> à la main.** On édite le *plan* puis on régénère (`make instancier-appliquer`).
|
||
|
||
> **`<inventaire>` n'est pas un nom, c'est une place.** Le dépôt n'impose pas comment une
|
||
> instance nomme son inventaire : **le moteur le cherche**, dans l'ordre `principal`, puis
|
||
> `production` (`scripts/inventory_rules.py` : `ORDRE_INVENTAIRE` ; pour la construction du
|
||
> gabarit, `ORDRE_INVENTAIRE_MODELE` essaie `lab` d'abord). La flotte utilise `principal` ;
|
||
> le modèle public livré dans `exemples/modeles/socle/` utilise `production`, et le
|
||
> QUICKSTART l'écrit tel quel parce que c'est ce que son lecteur a sous la main. Les
|
||
> documents de doctrine, eux, écrivent `<inventaire>` : coder l'un des deux noms en dur y
|
||
> serait faux pour la moitié des lecteurs — et ça l'a été jusqu'au 2026-09-06.
|
||
|
||
---
|
||
|
||
## 1. Le modèle : deux ancres, cinq liens
|
||
|
||
L'écosystème = des **serveurs** (VM) et des **applications**, reliés à leurs
|
||
**bases**, leurs **domaines** et leurs dépendances :
|
||
|
||
```
|
||
serveur (VM) ──fournit──▶ capacités (= groupes/rôles Ansible)
|
||
application ──tourne_sur──▶ serveur (une VM peut porter N applications)
|
||
application ──requiert──▶ application(s) (DNS, PKI, BD…)
|
||
application ──utilise──▶ base(s) (via le DSN)
|
||
application ──expose──▶ domaine(s) public(s) (le FQDN, derrière l'edge)
|
||
base ──hébergée_sur──▶ serveur de BD
|
||
```
|
||
|
||
L'**application** est l'entité pivot : tout ce qui décrit « ce qui tourne et à
|
||
quoi c'est connecté » pend d'elle. Le **groupe** Ansible n'est plus une cible de
|
||
liaison — seulement une **capacité** qu'une VM fournit (le rôle appliqué).
|
||
|
||
---
|
||
|
||
## 2. Les registres (source unique de vérité)
|
||
|
||
Machine-lisibles, validés, consommés par le GUI, le CLI et Ansible. **Ils vivent dans
|
||
l'instance** (`instance/plan/`), pas dans le moteur — c'est toute la séparation
|
||
moteur/instance. Seul `dependances-groupes.yml` est sous `docs/`, parce qu'il décrit une
|
||
propriété des *rôles*, la même pour toutes les instances.
|
||
|
||
| Registre | Décrit | Champs clés |
|
||
| --- | --- | --- |
|
||
| `nomenclature.yml` | nommage & adressage | **`index`** (le seed, seul champ d'adressage), `fonctions` (catégorie/service), `categories` (libellés de zones), `cidr_hote`, `reservations`. **Ni `supernet`, ni `vlan`, ni `passerelle` : P20 les refuse** — ils se dérivent. |
|
||
| `serveurs.yml` | les VM du plan | `fonction`, `etat` (actif/planifie), placement Proxmox (`noeud`/`stockage`/`disque`/`memoire`/`coeurs`), `integrations` (les `client_*` **facultatives** seulement — les universelles viennent du rôle, voir `integrations-vm.md`) |
|
||
| `applications.yml` | les applications | `groupe` (capacité/rôle), `hote` (VM), `port`, `requiert`, `expose`, (+ bases via consommateur) |
|
||
| `bases-donnees.yml` | serveurs de BD + bases | `serveurs_bd` ; `bases_donnees` : `serveur`/`base`/`proprietaire`/`secret`(Vault), `consommateur` + `portee` (`application`/`groupe`/`hote`), `usage` |
|
||
| `domaines.yml` | zones DNS publiques | `domaines_publics` : `autorite`, `edge`, `secondaires`, `dnssec`, `mail` |
|
||
| `dependances-groupes.yml` | prérequis entre groupes | `requiert_groupes_actifs` |
|
||
|
||
### Dérivations clés
|
||
- **Nommage/adressage** : tout part du seed `index` et de la `fonction` de l'hôte.
|
||
`supernet = 10.<index>.0.0/16`, `zone = 10.<index>.(15+catégorie)`,
|
||
`VLAN = 1000 + index×10 + zone`, `VMID = <VLAN><octet-hôte><rang>` — neuf chiffres,
|
||
miroir de l'IP. Voir `docs/nomenclature-vm.md`. *(Les formules à cinq chiffres et le
|
||
réseau unique `10.0.x` qui figuraient ici décrivaient le modèle d'avant le
|
||
multi-instance.)*
|
||
- **DSN** (lien application↔base) : `<type>://<proprietaire>:<secret>@<hôte>:<port>/<base>`.
|
||
Une application reçoit les bases où `(portee=application ET consommateur=elle)`
|
||
OU `(portee=groupe ET consommateur=son groupe)` OU `(portee=hote ET consommateur=son hôte)`.
|
||
- **Exposition DNS** : `application.expose: [fqdn]` + l'`edge` du domaine parent →
|
||
`serveur_nginx` génère le vhost (`application → hôte → IP:port`).
|
||
|
||
---
|
||
|
||
## 3. La génération (méta-classe)
|
||
|
||
`make instancier` produit `hosts.yml` **depuis le plan** :
|
||
|
||
- **host vars** : `ansible_host`/`ansible_user` + `proxmox_*` (IP/VMID/VLAN/passerelle
|
||
**dérivés** de la nomenclature ; placement/taille depuis `serveurs.yml`).
|
||
- **groupes** d'une VM = socle (`serveur_debian` + `serveur_durci`)
|
||
+ **services** (les `groupe` des applications de l'hôte)
|
||
+ **intégrations universelles** (politique du rôle : `roles/client_*/meta/integration.yml`)
|
||
+ **intégrations facultatives** (`serveurs.yml: integrations`)
|
||
+ **état** (`hotes_actifs` / `hotes_planifies`).
|
||
|
||
La comparaison est **sémantique** (via `ansible-inventory --list`, formatage
|
||
ignoré). « **Diff vide** » = le plan reproduit exactement l'inventaire courant ;
|
||
c'est le feu vert pour appliquer.
|
||
|
||
---
|
||
|
||
## 4. Le flux de travail
|
||
|
||
```
|
||
éditer le PLAN ──▶ make instancier (revoir le diff) ──▶ make instancier-appliquer ──▶ déployer
|
||
(GUI ou CLI) (que va-t-il changer ?) (régénère hosts.yml) (make deployer)
|
||
```
|
||
|
||
### Via le GUI — `make inventaire-ui`
|
||
|
||
**Douze vues**, éditables ou dérivées :
|
||
|
||
| Vue | Rôle |
|
||
| --- | --- |
|
||
| **Serveurs** *(éditable)* | les VM du plan : fonction/état/placement/intégrations (VMID·IP·VLAN dérivés en direct) ; bouton **« Appliquer le plan »** |
|
||
| **Applications** *(éditable)* | groupe/hôte/port/requiert/expose, et les **liens** acceptés par le rôle |
|
||
| **Bases** *(éditable)* | serveurs de BD et bases (portée + consommateur), DSN affiché (secret masqué) |
|
||
| **Domaines** *(éditable)* | zones publiques vs internes, autorité · edge · DNSSEC, expositions |
|
||
| **Nomenclature** *(éditable)* | le modèle dont tout l'adressage dérive : zones, fonctions (catégorie · service), réservations. Chaque fonction affiche **ce qu'elle dérive** (VLAN, sous-réseau, bloc d'hôtes) et les VM qui la portent. L'`index` y est **montré, pas éditable** : il est alloué par le site |
|
||
| **Intégrations** *(éditable)* | la matrice serveurs × intégrations ; les universelles en ✓ non décochables |
|
||
| **Intrants** *(éditable)* | les intrants de base — identité, Proxmox, fabric, et la liste de rappel des secrets (lecture seule) |
|
||
| **Inventaire** *(lecture seule)* | l'inventaire **généré** — vue d'ensemble des hôtes |
|
||
| **Chaîne** *(lecture seule)* | vue holistique par hôte : groupes → rôles, et par application ses `expose` / `requiert` / bases |
|
||
| **Flux** *(lecture seule)* | la matrice d'audit des flux — source de nftables et justification lisible |
|
||
| **Couches** *(lecture seule)* | l'ordre de déploiement en six couches |
|
||
| **Réseau** *(lecture seule + bascule)* | la flotte multi-instances, les collisions d'index, et le bouton **« Activer »** |
|
||
|
||
Édition → **Sauvegarder** (écrit le registre) → **Appliquer le plan** (régénère
|
||
`hosts.yml`). L'écriture directe de l'inventaire est refusée (409).
|
||
|
||
**Les formulaires des six registres sont générés** depuis `docs/audit/schema-plan.json`
|
||
(`make schema`), dérivé des constantes du moteur. La sauvegarde en dérive aussi : un
|
||
champ ajouté au plan apparaît à l'écran *et* arrive au fichier. Deux exceptions
|
||
déclarées au schéma (`x-editeur`) : la matrice des intégrations et l'éditeur de liens
|
||
gardent leur éditeur propre, plus riche que ce que le schéma sait dire.
|
||
|
||
### Via le CLI / `make`
|
||
Chaque registre a son script miroir et ses cibles `make` :
|
||
|
||
| Domaine | Lister | Vérifier | Bootstrap (depuis l'inventaire) |
|
||
| --- | --- | --- | --- |
|
||
| Serveurs | `make serveurs` | `make serveurs-verifier` | `make serveurs-bootstrap` |
|
||
| Applications | `make applications` | `make applications-verifier` | `make applications-bootstrap` |
|
||
| Bases | `make bases` | `make bases-verifier` | — |
|
||
| Domaines | `make domaines` | `make domaines-verifier` | — |
|
||
|
||
Génération :
|
||
|
||
```bash
|
||
make instancier # génère hosts.genere.yml (gitignoré) + diff sémantique
|
||
make instancier-appliquer # régénère hosts.yml (refuse si diff non vide ; FORCE=1 pour forcer)
|
||
```
|
||
|
||
Validation globale : `make inventaire-verifier` (ansible-inventory + tous les
|
||
registres + **`node --check` du JS du GUI**).
|
||
|
||
---
|
||
|
||
## 5. Garde-fous
|
||
|
||
- **Validateurs** : chaque registre est validé (références connues, énumérations,
|
||
unicité). Un `expose` sans domaine parent, un `requiert` fantôme, une `portee`
|
||
inconnue, une `fonction` absente → rejet.
|
||
- **Diff vide** : `instancier-appliquer` refuse d'écraser l'inventaire si le plan
|
||
ne le reproduit pas (sauf `FORCE=1` pour un changement intentionnel).
|
||
- **`node --check`** : le JS embarqué du GUI est vérifié (`scripts/verifier_gui.py`,
|
||
intégré à `make inventaire-verifier`) — une erreur de syntaxe JS casse toute la page.
|
||
- **git** : `hosts.yml` est versionné ; `git diff` / `git checkout` est le filet.
|
||
- **Secrets** : jamais en clair ; `secret:` nomme une variable Ansible Vault.
|
||
|
||
---
|
||
|
||
## 6. Réutilisation de la règle (Ansible)
|
||
|
||
La règle de résolution vit **une seule fois**, en Python (`scripts/inventory_rules.py`),
|
||
et est exposée à Ansible par un *filter plugin* (`filter_plugins/registres.py`) :
|
||
`bases_de_application`, `applications_de_hote`, `expositions_des_applications`,
|
||
`chaine_connexion`. Les playbooks par application (`serveur_web_dorsal`, `serveur_web_frontal`)
|
||
itèrent ainsi sur les applications de l'hôte et résolvent leurs DSN.
|
||
|
||
---
|
||
|
||
## 7. Amorçage / reconstruction
|
||
|
||
Pour (re)construire le plan depuis un inventaire existant :
|
||
|
||
```bash
|
||
make serveurs-bootstrap # VM -> instance/plan/serveurs.yml (fonction/état/placement/integrations)
|
||
make applications-bootstrap # services serveurs_* (hors socle) -> instance/plan/applications.yml
|
||
make instancier # vérifier le diff vide
|
||
```
|
||
|
||
C'est ainsi que le plan a été initialisé sans perte, avec diff vide vérifié.
|
||
|
||
---
|
||
|
||
## 8. Moteur et instance : deux dépôts
|
||
|
||
Le **moteur** (ce dépôt, `Set-OPS`) est générique et partageable ; il ne contient
|
||
aucune donnée d'instance. Une **instance** (le plan + l'inventaire d'une organisation) vit
|
||
dans son **propre dépôt** (ex. `OPS-monatelier`).
|
||
|
||
Le moteur localise l'instance via **`SETOPS_INSTANCE`** (défaut : `instance`). Deux
|
||
modèles :
|
||
|
||
- **Modèle A — dépôts frères** (en cours) : moteur et instance côte à côte ; un
|
||
symlink `instance -> ../OPS-monatelier` (gitignoré) fait que le défaut résout
|
||
l'instance sans configuration. Idéal quand on développe le moteur *et* l'instance.
|
||
- **Modèle B — moteur en sous-module** (futur, quand plusieurs écosystèmes vivront chez des exploitants distincts) : l'instance épingle
|
||
une version du moteur ; `SETOPS_INSTANCE` pointe la racine de l'instance.
|
||
|
||
Pour brancher une instance (modèle A) :
|
||
|
||
```bash
|
||
cd Set-OPS
|
||
ln -s ../OPS-monatelier instance # ou : export SETOPS_INSTANCE=/chemin/instance
|
||
make inventaire-verifier # lit l'instance via le symlink
|
||
```
|
||
|
||
Le moteur écrit `hosts.yml` (généré) **dans le dépôt d'instance**, jamais dans le sien.
|