Set-OPS-Public/docs/plan-et-generation.md
Daniel Allaire 2887b57f0c GUI : les six registres ont un formulaire genere, et la sauvegarde aussi
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
2026-09-08 18:26:33 -04:00

11 KiB
Raw Blame History

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 :

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 :

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) :

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.