insemination : la cle d'amorcage, bornee au meme groupe que le flux
Le flux etait declare et applique ; il manquait l'IDENTITE. Le runner du SITE
atteignait la porte de ops-01 sans avoir de cle.
LE PIEGE COMPTE PLUS QUE LE CORRECTIF. La cle s'injecte au CLONAGE, et `creer-vm`
cree TOUTES les machines d'un tenant. L'injecter a chaque clonage aurait donne a
l'hebergeur un acces SSH a la flotte entiere de chaque locataire, en silence — ca
aurait defait a la couche IDENTITE ce que le pare-feu venait de borner a la couche
RESEAU. Le meme critere gouverne donc les deux : porter `serveur_ops_tenant`.
`SETOPS_CLES_AMORCAGE` est vide partout ailleurs. Elle S'AJOUTE a celle de
l'exploitant, elle ne la remplace pas : c'est l'humain qui arme.
La cle vient du PLAN DU SITE, pas du disque local : materialiser depuis le poste
et depuis le runner doit produire la meme VM.
DEUX COUCHES MANGEAIENT LES ESPACES. Proxmox rendait `SSH public key validation
error` — message muet sur la cause. Isole par un CONTROLE (rejouer sans la cle :
la tache passe), puis par la mesure de ce qui arrivait au module :
"sshkeys": "ssh-ed25519" <- le premier mot, rien d'autre
J'ai accuse `make` d'abord ; c'etait `ansible-playbook -e cle=valeur`, qui decoupe
AU SHLEX. D'ou l'environnement pour le transport et `-e '{...}'` en JSON pour
l'entree. (Un scalaire YAML plie ne produit pas non plus de saut de ligne.)
TROIS TESTS QUI NE GARDAIENT RIEN. `test_inventory_host` inscrit ses tests dans une
liste explicite ; mes deux nouveaux n'y etaient pas — definis, jamais joues. La
garde d'exhaustivite ajoutee en a trouve un TROISIEME le jour meme,
`test_etiquette_vlan_repli_et_vide_explicite`, jamais inscrit depuis sa creation :
inscrit, il levait un KeyError sur une fixture qu'il lisait mal. Un test non
inscrit est pire qu'un test absent : on croit l'avoir.
PREUVE, AVEC SON CONTROLE NEGATIF :
runner du SITE -> ops-01 ops-01 10.17.19.41/24 entre
runner du SITE -> 10.17.19.21 Connection timed out refuse
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
d3ba520500
commit
ef832d11d6
6 changed files with 241 additions and 4 deletions
76
CHANGELOG.md
76
CHANGELOG.md
|
|
@ -1,5 +1,81 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-28 — L'insémination aboutit, et trois tests qui ne gardaient rien
|
||||
|
||||
**52 preuves.** Le flux était déclaré et appliqué ; il manquait l'**identité**. Le runner
|
||||
du SITE pouvait atteindre la porte de `ops-01` et n'avait pas de clé :
|
||||
|
||||
```
|
||||
ansible@10.17.19.41: Permission denied (publickey)
|
||||
```
|
||||
|
||||
**Le piège, et pourquoi il compte plus que le correctif.** La clé s'injecte par cloud-init,
|
||||
**au clonage** — or `creer-vm` crée *toutes* les machines d'un tenant. L'injecter à chaque
|
||||
clonage aurait donné à l'hébergeur un accès SSH à la **flotte entière de chaque
|
||||
locataire**, en silence, sans qu'aucune règle ne le dise. *Ça aurait défait à la couche
|
||||
identité ce que le pare-feu venait de borner à la couche réseau — et deux couches qui ne
|
||||
déclarent pas la même politique, c'est une politique qu'on ne peut plus lire.*
|
||||
|
||||
**Le même critère gouverne les deux** : porter `serveur_ops_tenant`. `inventory_host.py`
|
||||
émet `SETOPS_CLES_AMORCAGE` pour cet hôte, et une chaîne vide partout ailleurs. La clé
|
||||
vient du **plan du SITE**, pas du disque local : on matérialise depuis le runner comme
|
||||
depuis le poste, et un `lookup` local rendrait deux clés différentes selon qui agit.
|
||||
|
||||
Elle **s'ajoute**, elle ne remplace pas : l'exploitant garde son accès à la machine qu'il
|
||||
vient de créer. C'est l'humain qui arme, pas le site.
|
||||
|
||||
### Deux couches mangeaient les espaces d'une clé publique, en silence
|
||||
|
||||
Une clé porte des espaces — type, matériel, commentaire. Proxmox répondait :
|
||||
|
||||
```
|
||||
500 Internal Server Error: SSH public key validation error
|
||||
```
|
||||
|
||||
Un message qui ne parle ni de `make`, ni de `shlex`, ni d'espaces. Ce qui l'a isolé n'est
|
||||
pas la lecture du code mais un **contrôle** : rejouer `cloner-vm` *sans* la clé — la tâche
|
||||
passe. Puis la mesure de ce qui arrivait vraiment au module : `"sshkeys": "ssh-ed25519"`.
|
||||
**Le premier mot, rien d'autre.**
|
||||
|
||||
Deux causes, et j'ai accusé la mauvaise d'abord. Une variable `make` passée à un sous-make
|
||||
est un chemin fragile pour une valeur à espaces — vrai, mais ce n'était pas ça. Le
|
||||
coupable est `ansible-playbook -e clé=valeur`, qui découpe la chaîne **au shlex** : tout ce
|
||||
qui suit le premier espace devient d'autres paires. D'où la forme retenue :
|
||||
l'environnement pour le transport, et `-e '{"…": "…"}'` en **JSON** pour l'entrée dans
|
||||
Ansible — le seul mode de `-e` qui ne découpe rien.
|
||||
|
||||
*Un scalaire YAML plié (`>-`) ne produit pas non plus de saut de ligne : `'\n'` y reste
|
||||
deux caractères. Vérifié en base64 sur les trois formes candidates.*
|
||||
|
||||
### Trois tests qui ne gardaient rien
|
||||
|
||||
`test_inventory_host` déclare ses tests dans une **liste explicite**. Les deux que j'ai
|
||||
écrits pour la nouvelle garde y étaient absents : définis, verts en apparence, **jamais
|
||||
joués**. J'ai donc ajouté la garde qui manquait — la liste doit couvrir tout ce que le
|
||||
fichier définit.
|
||||
|
||||
Elle a trouvé un **troisième** cas le jour même : `test_etiquette_vlan_repli_et_vide_explicite`,
|
||||
jamais inscrit depuis sa création. Inscrit, il a levé un `KeyError` — il cherchait
|
||||
`hotes_actifs` dans une fixture qui range ses hôtes sous `hotes_planifies`. Il gardait le
|
||||
comportement SDN de l'étiquette VLAN, et il ne gardait rien. *Un test non inscrit est pire
|
||||
qu'un test absent : on croit l'avoir.*
|
||||
|
||||
### La preuve, avec son contrôle négatif
|
||||
|
||||
```
|
||||
runner du SITE -> ops-01 ops-01 10.17.19.41/24 ✔ entré
|
||||
runner du SITE -> 10.17.19.21 Connection timed out ✘ refusé
|
||||
```
|
||||
|
||||
Le chemin est large d'exactement une machine — et la première ligne est une **livraison**,
|
||||
pas une poignée de main : `ops-01` s'est nommée.
|
||||
|
||||
*`ops-01` est née avant ce mécanisme ; sa clé a donc été posée une fois à la main. Toute VM
|
||||
matérialisée à partir d'ici la reçoit de cloud-init, ou ne la reçoit pas — selon ce que le
|
||||
plan dit d'elle.*
|
||||
|
||||
make verifier : vert. make prouver : CONFORME, 52 OK, 0 echec.
|
||||
|
||||
## 2026-08-28 — Le lien d'insémination, déclaré — et un test rouge depuis trois jours
|
||||
|
||||
**52 preuves.** L'insémination avait un nom depuis ce matin ; elle n'avait pas de flux. Le
|
||||
|
|
|
|||
21
Makefile
21
Makefile
|
|
@ -50,6 +50,25 @@ DNS ?=
|
|||
DHCP ?= false
|
||||
CIUSER ?=
|
||||
CLE_SSH_PUBLIQUE ?=
|
||||
# LA CLE D'AMORCAGE VOYAGE PAR L'ENVIRONNEMENT, ET ARRIVE EN JSON. MESURE (2026-08-28).
|
||||
#
|
||||
# Une cle publique contient des ESPACES — type, materiel, commentaire — et deux couches
|
||||
# les mangent en silence :
|
||||
#
|
||||
# `ansible-playbook -e cle=valeur` decoupe la chaine AU SHLEX : tout ce qui suit le
|
||||
# premier espace devient d'autres paires cle=valeur. Le module recevait
|
||||
# `"sshkeys": "ssh-ed25519"` — le premier mot, rien d'autre.
|
||||
#
|
||||
# Et une variable `make` passee a un sous-make est un chemin tout aussi fragile pour
|
||||
# une valeur a espaces.
|
||||
#
|
||||
# Proxmox repondait `500 SSH public key validation error` : un message qui ne parle ni
|
||||
# de shlex, ni de make, ni d'espaces. Le contrôle qui l'a isole : rejouer `cloner-vm`
|
||||
# SANS la cle — la tache passe.
|
||||
#
|
||||
# D'ou la forme retenue : l'environnement (`SETOPS_CLES_AMORCAGE`, exporte par
|
||||
# `creer-vm`) pour le transport, et `-e '{"...": "..."}'` en JSON pour l'entree dans
|
||||
# Ansible — le seul mode de `-e` qui ne decoupe rien.
|
||||
PONT_PROXMOX ?=
|
||||
VLAN ?=
|
||||
DEMARRER ?=
|
||||
|
|
@ -972,6 +991,7 @@ cloner-vm: ansible-runtime ## Clone une VM depuis le gabarit dore — HOTE=<nom>
|
|||
[[ -n "$(DOMAINE_RECHERCHE)" ]] && extra_vars+=( -e proxmox_clone_domaines_recherche="$(DOMAINE_RECHERCHE)" ); \
|
||||
[[ -n "$(CIUSER)" ]] && extra_vars+=( -e proxmox_clone_ciuser="$(CIUSER)" ); \
|
||||
[[ -n "$(CLE_SSH_PUBLIQUE)" ]] && extra_vars+=( -e proxmox_clone_cle_publique_fichier="$(CLE_SSH_PUBLIQUE)" ); \
|
||||
[[ -n "$${SETOPS_CLES_AMORCAGE:-}" ]] && extra_vars+=( -e "$$(python3 -c 'import json,os; print(json.dumps({"proxmox_clone_cles_amorcage": os.environ["SETOPS_CLES_AMORCAGE"]}))')" ); \
|
||||
[[ -n "$(PONT_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_pont="$(PONT_PROXMOX)" ); \
|
||||
extra_vars+=( -e proxmox_clone_vlan="$(VLAN)" ); \
|
||||
[[ -n "$(DEMARRER)" ]] && extra_vars+=( -e proxmox_clone_demarrer="$(DEMARRER)" ); \
|
||||
|
|
@ -1006,6 +1026,7 @@ creer-vm: _instance-requise ## Cree une VM et attend qu'elle soit joignable —
|
|||
fi; \
|
||||
params="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote $(HOTE))"; \
|
||||
eval "$$params"; \
|
||||
export SETOPS_CLES_AMORCAGE; \
|
||||
$(MAKE) cloner-vm \
|
||||
HOTE="$(HOTE)" \
|
||||
VMID="$$SETOPS_VMID" \
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@
|
|||
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||
|
||||
- **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ❌ NON CONFORME (51 OK · 1 echec · 0 saute)
|
||||
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||
- **Verdict** : ✅ CONFORME (52 OK · 0 echec · 0 saute)
|
||||
|
||||
## Preuves
|
||||
|
||||
|
|
@ -45,7 +45,7 @@
|
|||
| P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 51 scripts expliques et atteignables, 104 cibles make documentees, 61 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 40 exigence(s) de role, toutes satisfaites (126 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ❌ ECHEC | rc=1 : decrivent la MEME ecoute. |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (38 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (26 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
|
|
|
|||
|
|
@ -135,6 +135,38 @@
|
|||
- proxmox_clone_cle_publique is defined
|
||||
- proxmox_clone_cle_publique | length > 0
|
||||
|
||||
# LA CLE D'AMORCAGE S'AJOUTE, ELLE NE REMPLACE PAS.
|
||||
#
|
||||
# L'exploitant doit garder son acces a la machine qu'il vient de creer : ecraser sa
|
||||
# cle par celle du runner rendrait le runner seul maitre du terrain, ce qui est
|
||||
# exactement l'inverse de la doctrine (c'est l'humain qui arme, cf.
|
||||
# docs/filiation-emancipation.md). Deux cles, deux lignes, et cloud-init les pose
|
||||
# toutes les deux.
|
||||
#
|
||||
# Elle n'arrive ICI que pour le runner d'un tenant : `inventory_host.py` la rend vide
|
||||
# partout ailleurs, et cette tache ne se declenche donc pas.
|
||||
#
|
||||
# DEUX PIEGES, MESURES LE 2026-08-28, ET LE MEME MESSAGE POUR LES DEUX :
|
||||
# 500 Internal Server Error: SSH public key validation error
|
||||
#
|
||||
# 1. LE SEPARATEUR. Un scalaire PLIE (`>-`) ne produit pas de saut de ligne : `'\n'`
|
||||
# y reste deux caracteres, une barre et un « n ». Seul le scalaire YAML entre
|
||||
# GUILLEMETS le convertit avant que Jinja ne le lise (verifie en base64 sur les
|
||||
# trois formes candidates).
|
||||
#
|
||||
# 2. LA LISTE PEUT N'AVOIR QU'UN ELEMENT — et c'est le cas courant. Sur cette flotte,
|
||||
# `CLE_SSH_PUBLIQUE` est vide : la cle de l'exploitant vient du GABARIT DORE, pas
|
||||
# de cloud-init. Une simple concatenation rendait donc une valeur commencant par
|
||||
# un saut de ligne, que Proxmox refuse tout autant. On assemble une LISTE et on
|
||||
# laisse `select` retirer les vides — la forme est juste qu'il y ait zero, une ou
|
||||
# deux cles.
|
||||
- name: Ajouter la cle d'amorcage du runner de SITE (insemination)
|
||||
ansible.builtin.set_fact:
|
||||
proxmox_clone_sshkeys: "{{ [proxmox_clone_sshkeys | default('') | trim, proxmox_clone_cles_amorcage | trim] | select | join('\n') }}"
|
||||
when:
|
||||
- proxmox_clone_cles_amorcage is defined
|
||||
- proxmox_clone_cles_amorcage | length > 0
|
||||
|
||||
- name: Construire les serveurs DNS Cloud-Init
|
||||
ansible.builtin.set_fact:
|
||||
proxmox_clone_nameservers: >-
|
||||
|
|
|
|||
|
|
@ -254,9 +254,54 @@ def parametres_proxmox_hote(data: dict, host: str,
|
|||
"Regenere l'inventaire (`make instancier-appliquer`)."
|
||||
)
|
||||
|
||||
# LA CLE D'AMORCAGE — POSEE SUR LE RUNNER D'UN TENANT, ET SUR LUI SEUL.
|
||||
#
|
||||
# Le runner du SITE doit pouvoir entrer chez le runner du tenant pour l'amorcer :
|
||||
# c'est l'insemination, et son flux est declare dans
|
||||
# `roles/serveur_ops_site/meta/flux.yml`. Il lui faut donc une identite sur cette
|
||||
# machine — une cle publique, qui n'ouvre rien par elle-meme.
|
||||
#
|
||||
# LE CRITERE EST LE MEME QUE CELUI DU FLUX : porter `serveur_ops_tenant`. C'est ce
|
||||
# qui empeche le geste de deraper. `creer-vm` cree TOUTES les machines d'un tenant ;
|
||||
# injecter cette cle a chaque clonage donnerait au site un acces SSH a la flotte
|
||||
# entiere de chaque locataire, sans qu'aucune regle ne le dise — et ca deferait a la
|
||||
# couche IDENTITE ce que le pare-feu borne a la couche RESEAU. Deux couches qui ne
|
||||
# declarent pas la meme politique, c'est une politique qu'on ne peut plus lire.
|
||||
#
|
||||
# La cle vient du PLAN DU SITE, pas du disque local : on materialise depuis le runner
|
||||
# comme depuis le poste de l'exploitant, et un `lookup` local rendrait deux valeurs
|
||||
# differentes selon qui agit — la VM nee du poste n'accepterait pas le runner.
|
||||
lignes.append(f"SETOPS_CLES_AMORCAGE='{_cle_amorcage(data, host)}'")
|
||||
|
||||
return lignes
|
||||
|
||||
|
||||
GROUPE_RUNNER_TENANT = "serveur_ops_tenant"
|
||||
|
||||
|
||||
def _cle_amorcage(data: dict, host: str) -> str:
|
||||
"""La cle publique du runner du SITE, si cet hote est le runner de son tenant.
|
||||
|
||||
Rend '' partout ailleurs — et aussi quand le depot de l'hebergeur n'est pas monte
|
||||
ou ne declare pas de runner : un ecosysteme se materialise alors comme avant, avec
|
||||
la seule cle de l'exploitant. Degrader, jamais deviner.
|
||||
"""
|
||||
if GROUPE_RUNNER_TENANT not in groupes_hote(data, host):
|
||||
return ""
|
||||
try:
|
||||
import underlay as underlay_mod
|
||||
cle = str((underlay_mod.lire_plan_site("10-intrants.yml") or {})
|
||||
.get("runner_cle_publique") or "").strip()
|
||||
except Exception:
|
||||
return ""
|
||||
if "'" in cle:
|
||||
raise ValueError(
|
||||
f"`runner_cle_publique` du site contient une apostrophe ({host}) : "
|
||||
"la valeur passe par une commande shell, elle ne peut pas en porter."
|
||||
)
|
||||
return cle
|
||||
|
||||
|
||||
def _rang_des_couches() -> dict[str, int]:
|
||||
"""Rang de chaque groupe dans `docs/couches-deploiement.yml`. {} si le fichier manque.
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,10 @@ def test_etiquette_vlan_repli_et_vide_explicite() -> None:
|
|||
rattachement — panne muette, decouverte au premier paquet.
|
||||
"""
|
||||
inv = _inventaire_factice()
|
||||
hote = inv["all"]["children"]["hotes_actifs"]["hosts"]["app-01"]
|
||||
# LA FIXTURE RANGE SES HOTES DANS `hotes_planifies`. Ce test cherchait
|
||||
# `hotes_actifs` et levait donc un KeyError — mais il n'etait inscrit dans aucune
|
||||
# liste, donc il n'a jamais tourne (corrige le 2026-08-28).
|
||||
hote = inv["all"]["children"]["hotes_planifies"]["hosts"]["app-01"]
|
||||
|
||||
# 1. Cle absente : on retombe sur proxmox_vlan.
|
||||
lignes = parametres_proxmox_hote(inv, "app-01")
|
||||
|
|
@ -101,10 +104,57 @@ def test_hote_complet_avec_noeud() -> None:
|
|||
# `parametres-proxmox` — un export de plus est un changement de contrat, et
|
||||
# c'est bien lui qui l'a signale.
|
||||
"SETOPS_DOMAINE=''",
|
||||
# Ajoute le 2026-08-28 : la cle publique du runner de SITE, posee sur le SEUL
|
||||
# hote qui porte `serveur_ops_tenant` (insemination). Vide ici, et c'est le
|
||||
# point — `app-01` n'est le runner de personne.
|
||||
"SETOPS_CLES_AMORCAGE=''",
|
||||
}
|
||||
assert set(lignes) == attendu, lignes
|
||||
|
||||
|
||||
def test_cle_d_amorcage_seulement_pour_le_runner_du_tenant() -> None:
|
||||
"""La cle du runner de SITE ne se pose QUE sur le runner du tenant.
|
||||
|
||||
C'EST LA GARDE QUI EMPECHE LE GESTE DE DERAPER. `creer-vm` cree TOUTES les machines
|
||||
d'un tenant ; si cette cle sortait pour chacune, l'hebergeur obtiendrait un acces SSH
|
||||
a la flotte entiere de chaque locataire — en silence, et sans qu'aucune regle de
|
||||
pare-feu ne le dise. Le critere doit rester le meme que celui du flux : porter
|
||||
`serveur_ops_tenant`.
|
||||
"""
|
||||
import inventory_host as ih
|
||||
|
||||
inv = _inventaire_factice()
|
||||
inv["all"]["children"]["serveur_ops_tenant"] = {"hosts": {"app-01": None}}
|
||||
|
||||
cle = "ssh-ed25519 AAAATEST commentaire@avec-espaces"
|
||||
lu = getattr(ih, "_cle_amorcage")
|
||||
|
||||
import underlay as u
|
||||
original = u.lire_plan_site
|
||||
u.lire_plan_site = lambda nom: {"runner_cle_publique": cle}
|
||||
try:
|
||||
assert lu(inv, "app-01") == cle, "le runner du tenant doit recevoir la cle"
|
||||
assert lu(inv, "app-02") == "", "une machine ordinaire ne doit RIEN recevoir"
|
||||
finally:
|
||||
u.lire_plan_site = original
|
||||
|
||||
|
||||
def test_cle_d_amorcage_absente_du_site_ne_casse_rien() -> None:
|
||||
"""Un ecosysteme dont l'hebergeur ne declare pas de runner se materialise comme
|
||||
avant. Degrader, jamais deviner."""
|
||||
import inventory_host as ih
|
||||
import underlay as u
|
||||
|
||||
inv = _inventaire_factice()
|
||||
inv["all"]["children"]["serveur_ops_tenant"] = {"hosts": {"app-01": None}}
|
||||
original = u.lire_plan_site
|
||||
u.lire_plan_site = lambda nom: {}
|
||||
try:
|
||||
assert getattr(ih, "_cle_amorcage")(inv, "app-01") == ""
|
||||
finally:
|
||||
u.lire_plan_site = original
|
||||
|
||||
|
||||
def test_hote_sans_noeud_emet_noeud_vide() -> None:
|
||||
lignes = parametres_proxmox_hote(_inventaire_factice(), "app-02")
|
||||
assert "SETOPS_NOEUD=''" in lignes, lignes
|
||||
|
|
@ -151,13 +201,26 @@ def test_champ_requis_manquant_refuse() -> None:
|
|||
|
||||
|
||||
def main() -> int:
|
||||
# LA LISTE EST EXPLICITE, DONC ELLE PEUT OUBLIER. Deux tests ecrits le 2026-08-28
|
||||
# ont ete definis sans etre inscrits ici : ils passaient pour verts en n'etant
|
||||
# jamais joues. On verifie donc que la liste couvre TOUT ce que ce fichier definit —
|
||||
# un test non inscrit est pire qu'un test absent, parce qu'on croit l'avoir.
|
||||
tests = [
|
||||
test_hote_complet_avec_noeud,
|
||||
# Jamais inscrit depuis sa creation, donc jamais joue : trouve par la garde
|
||||
# ci-dessous, le jour meme ou elle a ete ecrite.
|
||||
test_etiquette_vlan_repli_et_vide_explicite,
|
||||
test_cle_d_amorcage_seulement_pour_le_runner_du_tenant,
|
||||
test_cle_d_amorcage_absente_du_site_ne_casse_rien,
|
||||
test_hote_sans_noeud_emet_noeud_vide,
|
||||
test_intrant_commun_lu_a_cote_de_l_inventaire_recu,
|
||||
test_hote_absent_refuse,
|
||||
test_champ_requis_manquant_refuse,
|
||||
]
|
||||
definis = {n for n, v in sorted(globals().items())
|
||||
if n.startswith("test_") and callable(v)}
|
||||
oublies = definis - {t.__name__ for t in tests}
|
||||
assert not oublies, f"test(s) definis mais jamais joues : {sorted(oublies)}"
|
||||
for test in tests:
|
||||
test()
|
||||
print(f"ok {test.__name__}")
|
||||
|
|
|
|||
Loading…
Reference in a new issue