D-70 / P31 : l'exigence de documentation devient une preuve
Directive de l'exploitant : la doc dit et explique tout ce que Set-OPS fait. Une exigence seulement enoncee pourrit en silence — trois exemples le jour meme dans la carte. Ecart mesure : 66 cibles make sur 85 sans texte d'aide (make aide en montrait 19), 11 scripts sur 35 cites nulle part. Les 66 cibles ont recu leur aide : 85 commandes documentees. P31 garde le couvert. Le chemin pour l'ecrire a ete instructif : deux fois mon critere s'est revele creux. D'abord « le nom apparait dans un document » — le rapport d'audit GENERE recopiait les noms manquants dans son message d'echec. Puis j'ai failli refaire le trou en plus grand : generer un inventaire de l'outillage aurait satisfait le critere par construction. Un critere qu'on peut satisfaire en generant du texte ne prouve rien. P31 teste donc que chaque script porte une docstring qui l'explique et reste ATTEIGNABLE (cible make ou autre outil), que chaque cible porte son aide (sauf les internes prefixees _, exemption nommee), que chaque role a son README. Verifiee dans les deux sens. Ce qu'elle ne garde pas, et c'est dit dans son code : que l'explication soit bonne. Le pourquoi se juge en revue. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c5fd3aa70b
commit
5308574730
6 changed files with 202 additions and 68 deletions
39
CHANGELOG.md
39
CHANGELOG.md
|
|
@ -1,5 +1,44 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-08 — D-70 : l'exigence de documentation devient une preuve (P31)
|
||||
|
||||
Directive de l'exploitant : « la doc dit et explique tout ce que Set-OPS fait, et pourquoi
|
||||
c'est ainsi. » Une exigence qu'on se contente d'énoncer pourrit en silence — on venait
|
||||
d'en avoir trois exemples le jour même dans la carte.
|
||||
|
||||
**L'écart mesuré avant de le combler :**
|
||||
|
||||
```
|
||||
cibles make sans texte d'aide : 66 sur 85 → `make help` en montrait 19
|
||||
scripts jamais cités en doc : 11 sur 35 → dont 3 applicateurs et 4 devis du jour
|
||||
```
|
||||
|
||||
Les 66 cibles ont reçu leur aide : `make aide` couvre maintenant **85 commandes** au lieu
|
||||
de 19. C'est ce qui rend le moteur utilisable par quelqu'un qui ne lit pas le Makefile —
|
||||
la règle « un sysadmin l'exploite sans IA » n'a pas d'autre traduction concrète.
|
||||
|
||||
**P31 garde l'exigence**, et le chemin pour l'écrire a été instructif : *deux fois* mon
|
||||
critère s'est révélé creux.
|
||||
|
||||
D'abord « le nom du script apparaît dans un document » : le rapport d'audit **généré**
|
||||
recopiait les noms manquants dans son message d'échec, ce qui les rendait cités au tour
|
||||
suivant. Une preuve qui se nourrit de sa propre sortie passe au vert sans qu'une ligne
|
||||
soit écrite.
|
||||
|
||||
Puis, en corrigeant, j'ai failli créer le même trou en plus grand : générer un inventaire
|
||||
de l'outillage aurait satisfait le critère par construction. **Un critère qu'on peut
|
||||
satisfaire en générant du texte ne prouve rien.** P31 teste donc que chaque script porte
|
||||
une docstring qui l'explique et qu'il reste **atteignable** — par une cible `make`, ou par
|
||||
un autre outil.
|
||||
|
||||
Vérifiée dans les deux sens, comme les devis : on retire l'aide d'une cible et la
|
||||
docstring d'un script, les deux défauts sont nommés ; on restaure, `CONFORME`.
|
||||
|
||||
**Ce que P31 ne garde pas, et c'est dit dans son propre code** : que l'explication soit
|
||||
*bonne*. Le « pourquoi » se juge en revue. Il vit dans ce journal — qui porte le fait
|
||||
mesuré, pas seulement le changement — et dans le registre des décisions. Prétendre le
|
||||
mesurer mécaniquement serait se mentir.
|
||||
|
||||
## 2026-08-08 — Tisser le travail du jour dans les points d'entrée
|
||||
|
||||
Question de l'exploitant : faut-il refondre la documentation ? **Non.** L'état mesuré ne le
|
||||
|
|
|
|||
132
Makefile
132
Makefile
|
|
@ -80,7 +80,7 @@ DOSSIER_PLAYBOOKS_GROUPES := playbooks/groupes
|
|||
.DEFAULT_GOAL := aide
|
||||
|
||||
.PHONY: ansible-runtime
|
||||
ansible-runtime:
|
||||
ansible-runtime: ## Prepare le repertoire temporaire local d'Ansible (prerequis interne des cibles qui deploient)
|
||||
@mkdir -p "$(ANSIBLE_LOCAL_TEMP)"
|
||||
@mkdir -p "$(ANSIBLE_SSH_CONTROL_PATH_DIR)"
|
||||
|
||||
|
|
@ -95,7 +95,7 @@ _instance-requise:
|
|||
fi
|
||||
|
||||
.PHONY: aide
|
||||
aide:
|
||||
aide: ## Affiche l'aide detaillee du moteur (au-dela de cette liste)
|
||||
@printf '%s\n' 'Set-OPS — moteur d ecosystemes numeriques souverains'
|
||||
@printf '%s\n' ''
|
||||
@printf '%s\n' 'Nouveau ? -> QUICKSTART.md (de zero a ton ecosysteme sur Proxmox)'
|
||||
|
|
@ -180,93 +180,93 @@ aide:
|
|||
@printf '%s\n' ' FICHIER_INVENTAIRE=$(SETOPS_INSTANCE)/inventories/production/hosts.yml FICHIER_DEPENDANCES=docs/dependances-groupes.yml CONFIRMER=true'
|
||||
|
||||
.PHONY: lint
|
||||
lint: ansible-runtime
|
||||
lint: ansible-runtime ## Passe ansible-lint sur tout le depot
|
||||
ansible-lint
|
||||
|
||||
.PHONY: syntaxe syntaxe-modele syntaxe-nettoyage syntaxe-verification-modele syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
|
||||
syntaxe: syntaxe-modele syntaxe-verification-modele syntaxe-nettoyage syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
|
||||
syntaxe: syntaxe-modele syntaxe-verification-modele syntaxe-nettoyage syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox ## Verifie la syntaxe de TOUS les playbooks (modele, hote, groupes, proxmox)
|
||||
|
||||
syntaxe-modele: ansible-runtime
|
||||
syntaxe-modele: ansible-runtime ## Verifie la syntaxe du playbook de preparation du gabarit dore
|
||||
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE) --syntax-check
|
||||
|
||||
syntaxe-verification-modele: ansible-runtime
|
||||
syntaxe-verification-modele: ansible-runtime ## Verifie la syntaxe du playbook de verification du gabarit
|
||||
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE) --syntax-check
|
||||
|
||||
syntaxe-nettoyage: ansible-runtime
|
||||
syntaxe-nettoyage: ansible-runtime ## Verifie la syntaxe du playbook de nettoyage du gabarit
|
||||
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_NETTOYER_MODELE) --syntax-check
|
||||
|
||||
syntaxe-verification-hote: ansible-runtime
|
||||
syntaxe-verification-hote: ansible-runtime ## Verifie la syntaxe du playbook de verification d'hote
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) --syntax-check
|
||||
|
||||
syntaxe-groupes: ansible-runtime
|
||||
syntaxe-groupes: ansible-runtime ## Verifie la syntaxe des 30 playbooks de groupe
|
||||
@for playbook in $(DOSSIER_PLAYBOOKS_GROUPES)/*.yml; do \
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --syntax-check; \
|
||||
done
|
||||
|
||||
syntaxe-proxmox: ansible-runtime
|
||||
syntaxe-proxmox: ansible-runtime ## Verifie la syntaxe du playbook de clonage de VM
|
||||
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) --syntax-check
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire)
|
||||
python3 scripts/tests/test_inventory_host.py
|
||||
|
||||
.PHONY: verifier
|
||||
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe
|
||||
verifier: lint test inventaire-verifier site-verifier flux-verifier syntaxe ## Rejoue les preuves SANS reecrire le rapport (verification rapide)
|
||||
python3 scripts/prouver.py --verifier
|
||||
|
||||
# Harnais de preuve : rejoue les preuves automatisables du registre et ecrit
|
||||
# docs/audit/preuve-<date>.md (piece justificative horodatee, rejouable).
|
||||
# `make verifier` l'appelle en mode --verifier (preuves seules, aucun rapport ecrit).
|
||||
.PHONY: prouver
|
||||
prouver: ansible-runtime _instance-requise
|
||||
prouver: ansible-runtime _instance-requise ## Execute les preuves et ecrit docs/audit/preuve-<date>.md
|
||||
python3 scripts/prouver.py
|
||||
|
||||
.PHONY: inventaire hote-planifier hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-ui inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production instance-utiliser instance-courante
|
||||
inventaire: inventaire-production
|
||||
inventaire: inventaire-production ## Verifie l'inventaire et en affiche le graphe (lab puis production)
|
||||
|
||||
# Bascule le symlink 'instance' vers un autre dépôt d'instance (séparation par
|
||||
# instance : prod vs bac à sable). Ex. : make instance-utiliser NOM=OPS-Chezlepro-lab
|
||||
instance-utiliser:
|
||||
instance-utiliser: ## Bascule l'instance active (symlink instance/) vers un dossier frere — NOM=<dossier>
|
||||
@if [[ -z "$(NOM)" ]]; then printf '%s\n' "Usage: make instance-utiliser NOM=<dossier-frère> (ex. OPS-Chezlepro-lab)"; exit 2; fi
|
||||
@if [[ ! -d "../$(NOM)" ]]; then printf '%s\n' "Introuvable: ../$(NOM)"; exit 2; fi
|
||||
@if [[ -e instance && ! -L instance ]]; then printf '%s\n' "Refus: 'instance' existe et n'est pas un symlink."; exit 2; fi
|
||||
@rm -f instance && ln -s "../$(NOM)" instance
|
||||
@printf 'instance -> %s\n' "$$(readlink instance)"
|
||||
|
||||
instance-courante:
|
||||
instance-courante: ## Affiche vers quel ecosysteme pointe l'instance active
|
||||
@printf 'instance -> %s\n' "$$(readlink instance 2>/dev/null || echo '(non monté)')"
|
||||
|
||||
# Vue d'ensemble : toutes les instances de la fédération, l'active (*), leur index,
|
||||
# plage VLAN, statut fédéré/prod ; signale les collisions d'index. Lecture seule.
|
||||
instances:
|
||||
instances: ## Liste les ecosystemes decouverts (dossiers freres) et signale les collisions d'index
|
||||
@python3 scripts/instances.py
|
||||
|
||||
# (Re)génère le plan de recette (docs/audit/plan-de-recette.md) depuis les exercices
|
||||
# du wiki. La preuve P22 vérifie qu'il reste à jour.
|
||||
plan-recette:
|
||||
plan-recette: ## Regenere docs/audit/plan-de-recette.md depuis le wiki
|
||||
@python3 scripts/plan_recette.py
|
||||
|
||||
# Liste les modèles disponibles (socle + SETOPS_MODELES) pour créer une instance.
|
||||
instance-modeles:
|
||||
instance-modeles: ## Liste les modeles d'ecosysteme disponibles
|
||||
@python3 scripts/instance_creer.py --lister-modeles
|
||||
|
||||
# Crée un dépôt d'instance frère depuis un modèle. Ne bascule pas le symlink.
|
||||
# Ex. : make instance-creer NOM=OPS-ClientX MODELE=socle INDEX=4
|
||||
instance-creer:
|
||||
instance-creer: ## Cree un nouvel ecosysteme depuis un modele — NOM=<nom> MODELE=<modele>
|
||||
@python3 scripts/instance_creer.py --nom "$(NOM)" --modele "$(MODELE)" \
|
||||
$(if $(INDEX),--index $(INDEX),)
|
||||
|
||||
# Crée un MODÈLE (dépôt privé). Deux modes :
|
||||
# base : copier un modèle générique -> make model-creer MODE=base BASE=identite NOM=maison-obnl
|
||||
# instance : promouvoir une instance -> make model-creer MODE=instance SOURCE=OPS-Chezlepro NOM=cabinet
|
||||
model-creer:
|
||||
model-creer: ## Cree un modele d'ecosysteme — MODE=<mode> NOM=<nom>
|
||||
@python3 scripts/model_creer.py --mode "$(MODE)" --nom "$(NOM)" \
|
||||
$(if $(BASE),--base $(BASE),) $(if $(SOURCE),--source $(SOURCE),) $(if $(DEST),--dest $(DEST),)
|
||||
|
||||
config:
|
||||
config: ## Affiche la configuration Proxmox lue par le moteur
|
||||
python3 scripts/config_proxmox.py
|
||||
|
||||
inventaire-ui: _instance-requise
|
||||
inventaire-ui: _instance-requise ## Ouvre la console d'exploitation (GUI web) sur l'inventaire actif
|
||||
python3 scripts/inventory_gui.py --inventaire $(FICHIER_INVENTAIRE)
|
||||
|
||||
hote-ajouter hote-planifier hote-groupes:
|
||||
|
|
@ -276,14 +276,14 @@ hote-ajouter hote-planifier hote-groupes:
|
|||
@printf '%s\n' '(creer-vm lit desormais VMID/IP/VLAN/passerelle directement dans l inventaire genere.)'
|
||||
@exit 2
|
||||
|
||||
hote-afficher: ansible-runtime
|
||||
hote-afficher: ansible-runtime ## Affiche tout ce que le plan derive pour un hote — HOTE=<nom>
|
||||
@if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
|
||||
exit 2; \
|
||||
fi
|
||||
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) afficher --hote $(HOTE)
|
||||
|
||||
appliquer: ansible-runtime
|
||||
appliquer: ansible-runtime ## Applique un groupe a la flotte — GROUPE=<groupe>
|
||||
@if [[ -z "$(GROUPE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
|
||||
exit 2; \
|
||||
|
|
@ -295,7 +295,7 @@ appliquer: ansible-runtime
|
|||
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-groupe --groupe $(GROUPE)
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
|
||||
|
||||
deployer: _instance-requise
|
||||
deployer: _instance-requise ## Deploie un hote, couche par couche, dans l'ordre du graphe — HOTE=<nom>
|
||||
@set -e; \
|
||||
if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
|
||||
|
|
@ -328,15 +328,15 @@ deployer: _instance-requise
|
|||
$(MAKE) verifier-hote LIMITE="$(HOTE)"
|
||||
|
||||
.PHONY: site site-verifier deployer-tout
|
||||
site: ansible-runtime
|
||||
site: ansible-runtime ## Regenere playbooks/site.yml depuis les couches et le graphe de dependances
|
||||
python3 scripts/orchestrer.py ecrire
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) playbooks/site.yml --syntax-check
|
||||
|
||||
site-verifier:
|
||||
site-verifier: ## Verifie que playbooks/site.yml correspond aux couches declarees
|
||||
python3 scripts/orchestrer.py verifier
|
||||
|
||||
.PHONY: flux flux-verifier
|
||||
flux: ansible-runtime
|
||||
flux: ansible-runtime ## Regenere le registre des flux et les regles nftables depuis les meta/flux.yml
|
||||
python3 scripts/resoudre_flux.py registre
|
||||
python3 scripts/resoudre_flux.py nftables
|
||||
|
||||
|
|
@ -455,39 +455,39 @@ frontiere-appliquer: ansible-runtime ## Reconcilie la frontiere : cree ce qui ma
|
|||
devis-proxmox-fw: ansible-runtime ## Devis pare-feu Proxmox (est-ouest intra-tenant), derive du registre des flux
|
||||
python3 scripts/devis_proxmox_fw.py $(if $(JSON),--json,)
|
||||
|
||||
devis-proxmox-fw-verifier:
|
||||
devis-proxmox-fw-verifier: ## Verifie le devis du pare-feu est-ouest Proxmox (aucune ecriture)
|
||||
python3 scripts/devis_proxmox_fw.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,)
|
||||
|
||||
devis-proxmox-pools-verifier:
|
||||
devis-proxmox-pools-verifier: ## Verifie le devis des pools Proxmox (aucune ecriture)
|
||||
python3 scripts/devis_proxmox_pools.py --verifier
|
||||
|
||||
.PHONY: devis-sdn devis-sdn-verifier
|
||||
devis-sdn: ansible-runtime ## Devis SDN EVPN (zone + VNets + sous-reseaux par tenant), derive du seed
|
||||
python3 scripts/devis_sdn.py $(if $(JSON),--json,)
|
||||
|
||||
devis-sdn-verifier:
|
||||
devis-sdn-verifier: ## Verifie le devis SDN EVPN — zones, VNets, sous-reseaux (aucune ecriture)
|
||||
python3 scripts/devis_sdn.py --verifier
|
||||
|
||||
devis-opnsense-verifier:
|
||||
devis-opnsense-verifier: ## Verifie le devis de la frontiere nord/sud (aucune ecriture)
|
||||
python3 scripts/devis_opnsense.py --verifier
|
||||
|
||||
.PHONY: underlay
|
||||
underlay: ## Underlay (fabric physique cluster-global : mgmt/iSCSI/Ceph) : affiche + valide (P23)
|
||||
python3 scripts/underlay.py
|
||||
|
||||
flux-verifier:
|
||||
flux-verifier: ## Verifie que le registre des flux correspond aux meta/flux.yml des roles
|
||||
python3 scripts/resoudre_flux.py verifier
|
||||
|
||||
.PHONY: valider
|
||||
valider: ansible-runtime
|
||||
valider: ansible-runtime ## Passe la recette de validation sur la flotte
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) playbooks/valider.yml
|
||||
|
||||
.PHONY: wiki-publier
|
||||
wiki-publier:
|
||||
wiki-publier: ## Publie le wiki (wiki/) vers la forge
|
||||
@set -e; \
|
||||
if [[ -z "$(WIKI_REMOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: URL du wiki Forgejo requise.'; \
|
||||
|
|
@ -524,7 +524,7 @@ wiki-publier:
|
|||
git push --quiet; \
|
||||
printf '%s\n' 'Wiki publie.'
|
||||
|
||||
deployer-tout: _instance-requise
|
||||
deployer-tout: _instance-requise ## Deploie TOUTE la flotte dans l'ordre des couches
|
||||
@set -e; \
|
||||
if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
printf '%s\n' 'Refus: deploiement ORCHESTRE de TOUTE la flotte (action impactante).'; \
|
||||
|
|
@ -553,7 +553,7 @@ deployer-tout: _instance-requise
|
|||
# --- Reconstruction from-zero : creer TOUTES les VM (2a) puis deployer (2b) ---
|
||||
|
||||
.PHONY: flotte-creer
|
||||
flotte-creer: _instance-requise
|
||||
flotte-creer: _instance-requise ## Cree les VM manquantes de la flotte depuis le plan
|
||||
@set -e; \
|
||||
if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
printf '%s\n' 'Refus: creation de TOUTES les VM actives du plan (clone Proxmox).'; \
|
||||
|
|
@ -580,7 +580,7 @@ _attendre-flotte: ansible-runtime
|
|||
printf 'Flotte joignable.\n'
|
||||
|
||||
.PHONY: reconstruire
|
||||
reconstruire: _instance-requise
|
||||
reconstruire: _instance-requise ## Reconstruit un ecosysteme depuis zero : VM puis deploiement complet
|
||||
@set -e; \
|
||||
if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
printf '%s\n' 'Refus: RECONSTRUCTION — cree les VM manquantes (2a) PUIS deploie tout (2b).'; \
|
||||
|
|
@ -597,7 +597,7 @@ reconstruire: _instance-requise
|
|||
.PHONY: myDay
|
||||
myDay: reconstruire
|
||||
|
||||
deployer-groupe:
|
||||
deployer-groupe: ## Deploie un seul groupe sur toute la flotte — GROUPE=<groupe>
|
||||
@if [[ -z "$(GROUPE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
|
||||
exit 2; \
|
||||
|
|
@ -605,7 +605,7 @@ deployer-groupe:
|
|||
$(MAKE) appliquer GROUPE="$(GROUPE)"
|
||||
|
||||
.PHONY: verifier-deploiement
|
||||
verifier-deploiement: ansible-runtime
|
||||
verifier-deploiement: ansible-runtime ## Verifie l'etat de la flotte apres deploiement
|
||||
@set -e; \
|
||||
if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
|
||||
|
|
@ -634,7 +634,7 @@ verifier-deploiement: ansible-runtime
|
|||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)" --check --diff; \
|
||||
done
|
||||
|
||||
cloner-vm: ansible-runtime
|
||||
cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom> VMID=<id>
|
||||
@if [[ -z "$(HOTE)" || -z "$(VMID)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom VMID=id_clone.'; \
|
||||
exit 2; \
|
||||
|
|
@ -704,7 +704,7 @@ cloner-vm: ansible-runtime
|
|||
fi; \
|
||||
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
|
||||
|
||||
creer-vm: _instance-requise
|
||||
creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable — HOTE=<nom>
|
||||
@set -e; \
|
||||
if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote (declare dans le plan).'; \
|
||||
|
|
@ -741,7 +741,7 @@ creer-vm: _instance-requise
|
|||
$(MAKE) --no-print-directory _attendre-hote LIMITE="$(HOTE)"; \
|
||||
fi
|
||||
|
||||
inventaire-verifier: ansible-runtime _instance-requise
|
||||
inventaire-verifier: ansible-runtime _instance-requise ## Verifie que l'inventaire se parse (voute dechiffree)
|
||||
ansible-inventory -i $(INVENTAIRE_LAB) --list > /dev/null
|
||||
ansible-inventory -i $(INVENTAIRE_PRODUCTION) --list > /dev/null
|
||||
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) verifier-playbooks --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
|
||||
|
|
@ -753,61 +753,61 @@ inventaire-verifier: ansible-runtime _instance-requise
|
|||
python3 scripts/domaines.py verifier
|
||||
|
||||
.PHONY: bases bases-verifier domaines domaines-verifier applications applications-verifier applications-bootstrap serveurs serveurs-verifier serveurs-bootstrap
|
||||
serveurs:
|
||||
serveurs: ## Liste les serveurs declares au plan
|
||||
python3 scripts/serveurs.py lister
|
||||
|
||||
serveurs-verifier:
|
||||
serveurs-verifier: ## Valide le registre des serveurs
|
||||
python3 scripts/serveurs.py verifier
|
||||
|
||||
serveurs-bootstrap:
|
||||
serveurs-bootstrap: ## Amorce l'acces SSH aux serveurs neufs
|
||||
python3 scripts/serveurs.py bootstrap
|
||||
|
||||
.PHONY: instancier instancier-appliquer
|
||||
instancier: _instance-requise
|
||||
instancier: _instance-requise ## Genere hosts.yml depuis le plan (sans l'appliquer)
|
||||
python3 scripts/instancier.py generer
|
||||
python3 scripts/instancier.py comparer
|
||||
|
||||
instancier-appliquer: _instance-requise
|
||||
instancier-appliquer: _instance-requise ## Applique l'inventaire genere — FORCE=1 pour passer outre le diff
|
||||
python3 scripts/instancier.py appliquer $(if $(FORCE),--force)
|
||||
|
||||
bases:
|
||||
bases: ## Liste les bases de donnees declarees au plan
|
||||
python3 scripts/bases_donnees.py lister
|
||||
|
||||
bases-verifier:
|
||||
bases-verifier: ## Valide le registre des bases de donnees
|
||||
python3 scripts/bases_donnees.py verifier
|
||||
|
||||
domaines:
|
||||
domaines: ## Liste les domaines declares au plan
|
||||
python3 scripts/domaines.py lister
|
||||
|
||||
domaines-verifier:
|
||||
domaines-verifier: ## Valide le registre des domaines
|
||||
python3 scripts/domaines.py verifier
|
||||
|
||||
applications:
|
||||
applications: ## Liste les applications declarees au plan
|
||||
python3 scripts/applications.py lister
|
||||
|
||||
applications-verifier:
|
||||
applications-verifier: ## Valide le registre des applications
|
||||
python3 scripts/applications.py verifier
|
||||
|
||||
applications-bootstrap:
|
||||
applications-bootstrap: ## Amorce les applications declarees au plan
|
||||
python3 scripts/applications.py bootstrap
|
||||
|
||||
inventaire-lister: ansible-runtime
|
||||
inventaire-lister: ansible-runtime ## Affiche l'inventaire complet (JSON)
|
||||
ansible-inventory -i $(FICHIER_INVENTAIRE) --list
|
||||
|
||||
inventaire-graphe: ansible-runtime
|
||||
inventaire-graphe: ansible-runtime ## Affiche le graphe des groupes de l'inventaire
|
||||
ansible-inventory -i $(FICHIER_INVENTAIRE) --graph
|
||||
|
||||
inventaire-hote: ansible-runtime
|
||||
inventaire-hote: ansible-runtime ## Affiche les variables derivees d'un hote — HOTE=<nom>
|
||||
@if [[ -z "$(HOTE)" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
|
||||
exit 2; \
|
||||
fi
|
||||
ansible-inventory -i $(FICHIER_INVENTAIRE) --host $(HOTE)
|
||||
|
||||
inventaire-lab:
|
||||
inventaire-lab: ## Affiche le graphe de l'inventaire de laboratoire
|
||||
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_LAB)"
|
||||
|
||||
inventaire-production:
|
||||
inventaire-production: ## Affiche le graphe de l'inventaire de production
|
||||
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_PRODUCTION)"
|
||||
|
||||
.PHONY: _verifier-acces-modele _verifier-privileges-modele preparer-modele verifier-modele nettoyer-modele
|
||||
|
|
@ -817,13 +817,13 @@ _verifier-acces-modele: ansible-runtime
|
|||
_verifier-privileges-modele: ansible-runtime
|
||||
ansible -i $(INVENTAIRE_LAB) $(GROUPE_MODELE) -b -m command -a "whoami"
|
||||
|
||||
preparer-modele: ansible-runtime _verifier-acces-modele _verifier-privileges-modele
|
||||
preparer-modele: ansible-runtime _verifier-acces-modele _verifier-privileges-modele ## Prepare le gabarit dore (VM de reference clonee pour chaque hote)
|
||||
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE)
|
||||
|
||||
verifier-modele: ansible-runtime
|
||||
verifier-modele: ansible-runtime ## Verifie le gabarit dore
|
||||
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE)
|
||||
|
||||
nettoyer-modele: ansible-runtime
|
||||
nettoyer-modele: ansible-runtime ## Nettoie le gabarit avant capture — exige CONFIRMER=true
|
||||
@if [[ "$(CONFIRMER)" != "true" ]]; then \
|
||||
printf '%s\n' 'Refus: relancer avec CONFIRMER=true pour le nettoyage final du modele.'; \
|
||||
exit 2; \
|
||||
|
|
@ -880,8 +880,8 @@ _verifier-acces-hote: ansible-runtime
|
|||
_verifier-privileges-hote: ansible-runtime
|
||||
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -b -m command -a "whoami"
|
||||
|
||||
faits: ansible-runtime
|
||||
faits: ansible-runtime ## Interroge les faits Ansible de la flotte — LIMITE=<motif>
|
||||
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -m setup -a "filter=ansible_distribution*"
|
||||
|
||||
verifier-hote: ansible-runtime
|
||||
verifier-hote: ansible-runtime ## Passe le playbook de verification sur un hote
|
||||
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) $(OPTIONS_PLAYBOOK)
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (29 OK · 0 echec · 1 saute)
|
||||
- **Verdict** : ✅ CONFORME (30 OK · 0 echec · 1 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -43,6 +43,7 @@
|
|||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 2 pool(s) Proxmox, 28 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 23 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 12, 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, 2 zone(s), 12 VNet(s), 12 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 35 scripts expliques et atteignables, 85 cibles make documentees, 54 roles avec README. |
|
||||
|
||||
## Couverture des affirmations ✅ du registre
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ code + les README de rôles). Cette page comble ces deux trous.
|
|||
| **Ordre de déploiement** | `docs/couches-deploiement.yml` (couches) + `docs/dependances-groupes.yml` (graphe) → `playbooks/site.yml` (**généré**, `make site`) |
|
||||
| **Conformité du déployé** | `docs/devis-services.md` — les **cinq devis de service** (`make identite-plan`, `certificats-plan`, `expositions-plan`, `postgresql-plan`, `courriel-plan`). Répondent à ce que `make prouver` ne demande jamais : *ce qui tourne correspond-il à ce qui est déclaré ?* |
|
||||
| **Preuve / recette** | `docs/audit/affirmations.md` (registre), `make prouver` → `docs/audit/preuve-<date>.md` — **statique** : lit le dépôt, aucun appel réseau ; la conformité du déployé est l'affaire des devis de service (ligne au-dessus), `docs/audit/plan-de-recette.md` (**généré** du wiki), `docs/audit/protocole-operateur-independant.md` |
|
||||
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **66 décisions en vigueur** (D-01 → D-69, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
||||
| **Décisions d'architecture** | `docs/decisions-architecture.md` — **67 décisions en vigueur** (D-01 → D-70, 3 renversées), pourquoi, où lire le détail, et ce qui les garde ; plus les **décisions renversées** et leur cause |
|
||||
| **SDN / routage** | `docs/sdn-evpn.md` — décision du 2026-08-02 : le routage inter-zone passe des commutateurs aux hyperviseurs (zones EVPN = VRF). **Non éprouvé** : spike avant génération |
|
||||
| **Migration de tenant** | `docs/migration-tenant.md` — recette en 8 étapes, machine à états, gardes ; le receveur se construit **avant** tout gel |
|
||||
| **Exploitation courante** | `docs/runbooks-exploitation.md`, `docs/intrants-communs.md`, `docs/intrants-base-gui-conception.md`, `docs/theme-forgejo-hors-flotte.md` |
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ sont les seules vérifiables.
|
|||
| **D-34** | Une **exemption** se dérive du **service rendu** (`sauf_role`), jamais d'un nom d'hôte | l'AC ne s'enrôle pas auprès d'elle-même ; l'exemption doit suivre step-ca si on le déplace | `roles/client_pki/meta/integration.yml` | P26 |
|
||||
| **D-68** | On **écrit, puis on relit et on compare** — quelle que soit l'interface ; on choisit celle dont le chemin de **lecture** parle le même langage que le chemin d'**écriture** | « toujours préférer l'API » n'aurait prédit aucune des pannes du 2026-08-08 : sur six familles de défauts, deux venaient d'un CLI, une d'un module Ansible (`ldap_entry` crée sans jamais modifier), une d'un `grep` de fichier, une de la précédence Ansible, une de mon comparateur. Le facteur commun est d'avoir écrit sans relire. Et la plupart de la flotte n'a **pas** d'API — Postfix, Dovecot, nginx, slapd, nftables : `postconf -h` / `postconf -e` sont symétriques, c'est tout ce qu'on demande | `devis-services.md` | les 5 devis |
|
||||
| **D-69** | Sur Keycloak : **l'API pour toute map ou collection** (`smtpServer`, `attributes`, `config`), `kcadm` pour les scalaires et les créations | `kcadm -s` sur une map accepte la commande, **sort en succès et n'écrit rien** — mesuré deux fois le 2026-08-08 (`smtpServer` resté vide après deux déploiements verts, puis `post.logout.redirect.uris`). Le CLI reste préféré ailleurs : c'est le vocabulaire de la documentation du produit, donc lisible sans IA | `roles/serveur_keycloak/tasks/` | `make identite-plan` |
|
||||
| **D-70** | La documentation **dit et explique tout ce que le dépôt fait** — et l'exigence est **outillée**, pas seulement énoncée | une exigence qu'on n'outille pas pourrit en silence : la carte annonçait « 28 décisions » quand il y en avait 66, et disait les accès « non construits » alors qu'ils tournaient en production. **P31** garde le couvert — chaque script s'explique et reste atteignable, chaque cible `make` porte son aide (sauf les internes préfixées `_`), chaque rôle a son README. Elle ne garde **pas** la qualité du « pourquoi » : ça se juge en revue, et ça vit dans `CHANGELOG.md` et ici | `devis-services.md`, `CHANGELOG.md` | **P31** |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ Usage :
|
|||
from __future__ import annotations
|
||||
|
||||
import datetime as _dt
|
||||
import ast
|
||||
import json
|
||||
import re
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
|
@ -397,6 +399,95 @@ def preuve_inventaire_ansible() -> tuple[bool, str]:
|
|||
return True, f"{n_hotes} hotes, {n_groupes} groupes (inventaire dechiffre et parse)."
|
||||
|
||||
|
||||
def preuve_documentation_outillage() -> tuple[bool, str]:
|
||||
"""Tout ce que Set-OPS FAIT s'explique et reste atteignable.
|
||||
|
||||
Exigence de l'exploitant, 2026-08-08 : « la doc dit et explique tout ce que Set-OPS
|
||||
fait, et pourquoi c'est ainsi. » Une exigence qu'on n'outille pas pourrit en silence
|
||||
— la carte annoncait « 28 decisions » quand il y en avait 66, et disait les acces
|
||||
« non construits » alors qu'ils tournaient en production.
|
||||
|
||||
CE QU'ELLE TESTE, et pourquoi ainsi :
|
||||
|
||||
- chaque `scripts/*.py` porte une docstring de module dont la premiere ligne
|
||||
explique a quoi il sert. Le premier jet verifiait plutot que le nom du script
|
||||
« apparaisse dans un document » : deux fois ce critere s'est revele creux —
|
||||
d'abord parce que le rapport d'audit GENERE recopiait les noms manquants dans
|
||||
son message d'echec, ensuite parce qu'un inventaire genere de l'outillage
|
||||
aurait fait passer la preuve au vert sans qu'une ligne soit ecrite. Un critere
|
||||
qu'on peut satisfaire en generant du texte ne prouve rien ;
|
||||
- chaque script est ATTEIGNABLE : invoque par une cible `make`, ou importe/appele
|
||||
par un autre script (bibliotheques partagees, scripts appeles par les preuves).
|
||||
Un outil que rien n'atteint est du code mort qui se documente tout seul ;
|
||||
- chaque cible `make` porte un texte d'aide `##`, SAUF celles prefixees `_` :
|
||||
convention du depot pour les cibles internes (attentes, verifications de
|
||||
privileges) qui ne sont pas des commandes d'exploitant. L'exemption est nommee
|
||||
ici pour rester un choix et non un trou ;
|
||||
- chaque role porte un README.
|
||||
|
||||
CE QU'ELLE NE TESTE PAS : que l'explication soit BONNE. Le « pourquoi » se juge en
|
||||
revue ; il vit dans `CHANGELOG.md` (les faits mesures) et dans
|
||||
`decisions-architecture.md`. Pretendre le mesurer mecaniquement serait se mentir.
|
||||
"""
|
||||
manques: list[str] = []
|
||||
scripts = sorted((RACINE / "scripts").glob("*.py"))
|
||||
textes = {f.name: f.read_text(encoding="utf-8", errors="ignore") for f in scripts}
|
||||
makefile = (RACINE / "Makefile").read_text(encoding="utf-8", errors="ignore")
|
||||
|
||||
# `ast`, pas une expression reguliere : les scripts commencent par un shebang, et
|
||||
# mon premier motif le prenait pour l'absence de docstring — 35 faux positifs d'un
|
||||
# coup. Lire du Python avec le parseur de Python.
|
||||
muets = []
|
||||
for f in scripts:
|
||||
try:
|
||||
doc = ast.get_docstring(ast.parse(textes[f.name])) or ""
|
||||
except SyntaxError:
|
||||
muets.append(f"{f.name} (illisible)")
|
||||
continue
|
||||
if len(doc.strip().splitlines()[0] if doc.strip() else "") < 30:
|
||||
muets.append(f.name)
|
||||
if muets:
|
||||
manques.append(f"{len(muets)} script(s) sans docstring explicative : "
|
||||
+ ", ".join(muets[:6]) + ("…" if len(muets) > 6 else ""))
|
||||
|
||||
injoignables = []
|
||||
for f in scripts:
|
||||
autres = "\n".join(v for k, v in textes.items() if k != f.name)
|
||||
module = f.stem
|
||||
if f"scripts/{f.name}" in makefile:
|
||||
continue
|
||||
if module in autres: # importe ou appele par un autre outil
|
||||
continue
|
||||
injoignables.append(f.name)
|
||||
if injoignables:
|
||||
manques.append(f"{len(injoignables)} script(s) qu'aucune cible ni aucun outil "
|
||||
f"n'atteint : " + ", ".join(injoignables[:6]))
|
||||
|
||||
muettes = [
|
||||
ligne.split(":")[0]
|
||||
for ligne in makefile.splitlines()
|
||||
if re.match(r"^[a-z][a-z0-9_-]*:", ligne) and "##" not in ligne
|
||||
]
|
||||
if muettes:
|
||||
manques.append(f"{len(muettes)} cible(s) make sans texte d'aide `##` : "
|
||||
+ ", ".join(sorted(muettes)[:6]) + ("…" if len(muettes) > 6 else ""))
|
||||
|
||||
sans_readme = sorted(
|
||||
d.name for d in (RACINE / "roles").iterdir()
|
||||
if d.is_dir() and not (d / "README.md").exists()
|
||||
)
|
||||
if sans_readme:
|
||||
manques.append(f"{len(sans_readme)} role(s) sans README : " + ", ".join(sans_readme[:6]))
|
||||
|
||||
if manques:
|
||||
return False, " | ".join(manques)
|
||||
|
||||
n_c = len([l for l in makefile.splitlines() if re.match(r"^[a-z][a-z0-9_-]*:", l)])
|
||||
n_r = len([d for d in (RACINE / "roles").iterdir() if d.is_dir()])
|
||||
return True, (f"{len(scripts)} scripts expliques et atteignables, "
|
||||
f"{n_c} cibles make documentees, {n_r} roles avec README.")
|
||||
|
||||
|
||||
# --- Registre des preuves : (id, titre, refs AFF, executeur) -----------------------
|
||||
#
|
||||
# executeur = liste de commandes argv (toutes doivent renvoyer 0), ou callable -> (ok, detail).
|
||||
|
|
@ -472,6 +563,8 @@ PREUVES: list[dict] = [
|
|||
"func": preuve_authentification},
|
||||
{"id": "P30", "titre": "SDN EVPN : zones, VNets et sous-reseaux derives", "refs": ["AFF-112"],
|
||||
"cmds": [[sys.executable, "scripts/devis_sdn.py", "--verifier"]]},
|
||||
{"id": "P31", "titre": "Documentation : tout ce que le depot FAIT est nomme", "refs": [],
|
||||
"func": preuve_documentation_outillage},
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue