2026-06-24 20:17:46 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Tests stdlib (sans pytest) pour inventory_host.parametres_proxmox_hote.
|
|
|
|
|
|
|
|
|
|
Lancer : python3 scripts/tests/test_inventory_host.py
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import sys
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
|
|
|
|
|
|
|
|
|
|
from inventory_host import parametres_proxmox_hote # noqa: E402
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _inventaire_factice() -> dict:
|
|
|
|
|
return {
|
|
|
|
|
"all": {
|
|
|
|
|
"children": {
|
|
|
|
|
"hotes_planifies": {
|
|
|
|
|
"hosts": {
|
|
|
|
|
"app-01": {
|
|
|
|
|
"ansible_host": "10.0.2.11",
|
|
|
|
|
"ansible_user": "ansible",
|
|
|
|
|
"proxmox_cidr": 24,
|
|
|
|
|
"proxmox_coeurs": 4,
|
|
|
|
|
"proxmox_disque_taille": "160G",
|
|
|
|
|
"proxmox_memoire": 2048,
|
|
|
|
|
"proxmox_noeud": "noeud-a",
|
|
|
|
|
"proxmox_passerelle": "10.0.2.1",
|
|
|
|
|
"proxmox_stockage": "stockage-a",
|
|
|
|
|
"proxmox_vlan": 13,
|
|
|
|
|
"proxmox_vmid": 93101,
|
|
|
|
|
},
|
|
|
|
|
# app-02 : sans proxmox_noeud (champ optionnel).
|
|
|
|
|
"app-02": {
|
|
|
|
|
"ansible_host": "10.0.2.21",
|
|
|
|
|
"proxmox_cidr": 24,
|
|
|
|
|
"proxmox_disque_taille": "16G",
|
|
|
|
|
"proxmox_passerelle": "10.0.2.1",
|
|
|
|
|
"proxmox_stockage": "stockage-a",
|
|
|
|
|
"proxmox_vlan": 15,
|
|
|
|
|
"proxmox_vmid": 95201,
|
|
|
|
|
},
|
|
|
|
|
# incomplet-01 : il manque l'IP (champ requis).
|
|
|
|
|
"incomplet-01": {
|
|
|
|
|
"proxmox_vmid": 99901,
|
|
|
|
|
"proxmox_cidr": 24,
|
|
|
|
|
"proxmox_passerelle": "10.0.2.1",
|
|
|
|
|
"proxmox_vlan": 99,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
"serveur_postgresql": {"hosts": {"app-01": {}}},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
réseau : le VNet d'une VM se dérive, un hyperviseur a plusieurs pattes (D-55 à D-59)
Le pont n'était pas seulement non portable, il était faux. proxmox_clone_pont
faisait naître les VM sur vmbr1 avec une étiquette VLAN — l'ancien monde. En SDN
une VM appartient à son VNet ; c'est ce qu'il a fallu corriger à la main sur
infra-pki-01, et les treize suivantes auraient suivi.
deriver_nomenclature() expose désormais la zone de sécurité, instancier en dérive
proxmox_pont et une étiquette VIDE — le VNet porte déjà le tag, en poser un
second donnerait un double étiquetage. La chaîne va jusqu'à make creer-vm :
SETOPS_PONT='t11appl', SETOPS_VLAN=''.
Trois pièges. Un doublon dans le Makefile passait PONT_PROXMOX deux fois dans la
même cible, la seconde vide aurait écrasé la valeur dérivée. Un repli naïf sur
proxmox_vlan aurait fait revenir l'étiquette en SDN : le repli ne s'applique que
si la clé est ABSENTE, jamais si elle est présente et vide. Et le test unitaire
est tombé, à raison — il couvre maintenant cette distinction.
D-55 : le dépôt réseau porte le contrat entre l'Alliance et ses hébergeurs, et
abstrait le matériel en encapsulant chaque tenant dans sa zone EVPN. Mesuré : un
tenant est à deux valeurs de la portabilité complète (noeud, stockage).
D-57 : l'interface sysadmin d'un hyperviseur (vmbr0, 10.0.0.41/.43/.47) n'a pas
de route par défaut ; celle-ci vit sur vlan40, vers la frontière. On n'atteint
l'administration que depuis son propre domaine de diffusion. Ça tranche la
question de la sortie des nœuds laissée ouverte ce matin — option A, mais sur une
interface dédiée, ce qui lève l'objection qui la bloquait.
D-58 : un hôte déclare par quelle interface (`via`) chaque réseau lui arrive ; le
devis en dérive un port par interface et son type — trunk 11,40 sur bond3, accès
VLAN 10 sur vmbr0. Sans ça, ajouter le VLAN 10 le remettait sur le trunk du
transport, soit le domaine qu'on venait d'en sortir.
D-59 : un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont.
Régression créée puis corrigée : le modèle public, qui ne déclare aucun
hyperviseur, n'émettait plus rien pour ce port. Il émet maintenant tout
l'underlay en disant que c'est un repli.
30 preuves OK, 4 tests unitaires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:20:56 -04:00
|
|
|
def test_etiquette_vlan_repli_et_vide_explicite() -> None:
|
|
|
|
|
"""Absent -> repli sur proxmox_vlan ; present et VIDE -> reste vide.
|
|
|
|
|
|
|
|
|
|
C'est la distinction qui protege du double etiquetage : en SDN le VNet porte
|
|
|
|
|
deja le tag, et `proxmox_etiquette_vlan: ""` DIT qu'il ne faut pas en poser un
|
|
|
|
|
second. Un repli naif (`a or b`) ferait revenir l'etiquette et casserait le
|
|
|
|
|
rattachement — panne muette, decouverte au premier paquet.
|
|
|
|
|
"""
|
|
|
|
|
inv = _inventaire_factice()
|
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
2026-08-28 13:45:44 -04:00
|
|
|
# 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"]
|
réseau : le VNet d'une VM se dérive, un hyperviseur a plusieurs pattes (D-55 à D-59)
Le pont n'était pas seulement non portable, il était faux. proxmox_clone_pont
faisait naître les VM sur vmbr1 avec une étiquette VLAN — l'ancien monde. En SDN
une VM appartient à son VNet ; c'est ce qu'il a fallu corriger à la main sur
infra-pki-01, et les treize suivantes auraient suivi.
deriver_nomenclature() expose désormais la zone de sécurité, instancier en dérive
proxmox_pont et une étiquette VIDE — le VNet porte déjà le tag, en poser un
second donnerait un double étiquetage. La chaîne va jusqu'à make creer-vm :
SETOPS_PONT='t11appl', SETOPS_VLAN=''.
Trois pièges. Un doublon dans le Makefile passait PONT_PROXMOX deux fois dans la
même cible, la seconde vide aurait écrasé la valeur dérivée. Un repli naïf sur
proxmox_vlan aurait fait revenir l'étiquette en SDN : le repli ne s'applique que
si la clé est ABSENTE, jamais si elle est présente et vide. Et le test unitaire
est tombé, à raison — il couvre maintenant cette distinction.
D-55 : le dépôt réseau porte le contrat entre l'Alliance et ses hébergeurs, et
abstrait le matériel en encapsulant chaque tenant dans sa zone EVPN. Mesuré : un
tenant est à deux valeurs de la portabilité complète (noeud, stockage).
D-57 : l'interface sysadmin d'un hyperviseur (vmbr0, 10.0.0.41/.43/.47) n'a pas
de route par défaut ; celle-ci vit sur vlan40, vers la frontière. On n'atteint
l'administration que depuis son propre domaine de diffusion. Ça tranche la
question de la sortie des nœuds laissée ouverte ce matin — option A, mais sur une
interface dédiée, ce qui lève l'objection qui la bloquait.
D-58 : un hôte déclare par quelle interface (`via`) chaque réseau lui arrive ; le
devis en dérive un port par interface et son type — trunk 11,40 sur bond3, accès
VLAN 10 sur vmbr0. Sans ça, ajouter le VLAN 10 le remettait sur le trunk du
transport, soit le domaine qu'on venait d'en sortir.
D-59 : un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont.
Régression créée puis corrigée : le modèle public, qui ne déclare aucun
hyperviseur, n'émettait plus rien pour ce port. Il émet maintenant tout
l'underlay en disant que c'est un repli.
30 preuves OK, 4 tests unitaires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:20:56 -04:00
|
|
|
|
|
|
|
|
# 1. Cle absente : on retombe sur proxmox_vlan.
|
|
|
|
|
lignes = parametres_proxmox_hote(inv, "app-01")
|
|
|
|
|
assert "SETOPS_VLAN='13'" in lignes, lignes
|
|
|
|
|
|
|
|
|
|
# 2. Cle presente et vide (SDN) : aucune etiquette, malgre proxmox_vlan renseigne.
|
|
|
|
|
hote["proxmox_etiquette_vlan"] = ""
|
|
|
|
|
hote["proxmox_pont"] = "t17serv"
|
|
|
|
|
lignes = parametres_proxmox_hote(inv, "app-01")
|
|
|
|
|
assert "SETOPS_VLAN=''" in lignes, lignes
|
|
|
|
|
assert "SETOPS_PONT='t17serv'" in lignes, lignes
|
|
|
|
|
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
def test_hote_complet_avec_noeud() -> None:
|
|
|
|
|
lignes = parametres_proxmox_hote(_inventaire_factice(), "app-01")
|
|
|
|
|
attendu = {
|
|
|
|
|
"SETOPS_VMID='93101'",
|
|
|
|
|
"SETOPS_IP='10.0.2.11'",
|
|
|
|
|
"SETOPS_CIDR='24'",
|
|
|
|
|
"SETOPS_PASSERELLE='10.0.2.1'",
|
|
|
|
|
"SETOPS_VLAN='13'",
|
réseau : le VNet d'une VM se dérive, un hyperviseur a plusieurs pattes (D-55 à D-59)
Le pont n'était pas seulement non portable, il était faux. proxmox_clone_pont
faisait naître les VM sur vmbr1 avec une étiquette VLAN — l'ancien monde. En SDN
une VM appartient à son VNet ; c'est ce qu'il a fallu corriger à la main sur
infra-pki-01, et les treize suivantes auraient suivi.
deriver_nomenclature() expose désormais la zone de sécurité, instancier en dérive
proxmox_pont et une étiquette VIDE — le VNet porte déjà le tag, en poser un
second donnerait un double étiquetage. La chaîne va jusqu'à make creer-vm :
SETOPS_PONT='t11appl', SETOPS_VLAN=''.
Trois pièges. Un doublon dans le Makefile passait PONT_PROXMOX deux fois dans la
même cible, la seconde vide aurait écrasé la valeur dérivée. Un repli naïf sur
proxmox_vlan aurait fait revenir l'étiquette en SDN : le repli ne s'applique que
si la clé est ABSENTE, jamais si elle est présente et vide. Et le test unitaire
est tombé, à raison — il couvre maintenant cette distinction.
D-55 : le dépôt réseau porte le contrat entre l'Alliance et ses hébergeurs, et
abstrait le matériel en encapsulant chaque tenant dans sa zone EVPN. Mesuré : un
tenant est à deux valeurs de la portabilité complète (noeud, stockage).
D-57 : l'interface sysadmin d'un hyperviseur (vmbr0, 10.0.0.41/.43/.47) n'a pas
de route par défaut ; celle-ci vit sur vlan40, vers la frontière. On n'atteint
l'administration que depuis son propre domaine de diffusion. Ça tranche la
question de la sortie des nœuds laissée ouverte ce matin — option A, mais sur une
interface dédiée, ce qui lève l'objection qui la bloquait.
D-58 : un hôte déclare par quelle interface (`via`) chaque réseau lui arrive ; le
devis en dérive un port par interface et son type — trunk 11,40 sur bond3, accès
VLAN 10 sur vmbr0. Sans ça, ajouter le VLAN 10 le remettait sur le trunk du
transport, soit le domaine qu'on venait d'en sortir.
D-59 : un VLAN qui ne porte que des adresses d'hôte n'a pas besoin de pont.
Régression créée puis corrigée : le modèle public, qui ne déclare aucun
hyperviseur, n'émettait plus rien pour ce port. Il émet maintenant tout
l'underlay en disant que c'est un repli.
30 preuves OK, 4 tests unitaires.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:20:56 -04:00
|
|
|
"SETOPS_PONT=''",
|
2026-06-24 20:17:46 -04:00
|
|
|
"SETOPS_STOCKAGE='stockage-a'",
|
|
|
|
|
"SETOPS_DISQUE='160G'",
|
|
|
|
|
"SETOPS_NOEUD='noeud-a'",
|
2026-06-30 10:07:04 -04:00
|
|
|
"SETOPS_COEURS='4'",
|
|
|
|
|
"SETOPS_MEMOIRE='2048'",
|
dns : client_unbound universel, et un resolveur d'amorcage
Une VM ne peut pas s'installer sans resoudre des noms. PowerDNS repond
UNIQUEMENT pour la zone souveraine et ne recurse pour personne : il manquait un
resolveur recursif. `client_unbound` est l'outil ecrit pour ca — il rejoint
`client_journal` et `client_metrique` parmi les integrations universelles.
`infra-dns-01` est exempte (PowerDNS occupe son port 53), et
`serveur_powerdns_listen_addresses` passe de 0.0.0.0 a l'adresse de l'hote pour
laisser 127.0.0.1:53 libre. Mais l'appartenance au groupe est AUSSI ce qui ouvre
le port 53 a la frontiere : en exemptant la machine, je lui retirais le droit de
resoudre. `serveur_powerdns` declare donc son propre flux sortant — il ne
recurse pour personne, mais doit resoudre pour lui-meme.
Nouvel intrant `dns_amorcage`, derive jusqu'a `make creer-vm`. Cloud-init
l'ecrit bien mais sans effet : `dns-nameservers` exige `resolvconf`, absent du
gabarit, et installer resolvconf demande apt, qui demande la resolution.
`serveur_debian` pose donc le resolveur en pre_tasks, avant le premier apt, avec
une garde qui respecte la bascule ulterieure de client_unbound.
Defaut corrige en chemin : `_intrants_communs()` lisait `instance/` en dur ; le
chemin derive maintenant de l'inventaire recu, et un test le prouve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:41:58 -04:00
|
|
|
"SETOPS_DNS=''",
|
2026-08-09 10:26:44 -04:00
|
|
|
# Ajoute le 2026-08-09 : le domaine de recherche cloud-init se derive de
|
|
|
|
|
# `domaine_interne`, comme le DNS. Ce test epingle le CONTRAT de
|
|
|
|
|
# `parametres-proxmox` — un export de plus est un changement de contrat, et
|
|
|
|
|
# c'est bien lui qui l'a signale.
|
|
|
|
|
"SETOPS_DOMAINE=''",
|
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
2026-08-28 13:45:44 -04:00
|
|
|
# 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=''",
|
2026-06-24 20:17:46 -04:00
|
|
|
}
|
|
|
|
|
assert set(lignes) == attendu, lignes
|
|
|
|
|
|
|
|
|
|
|
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
2026-08-28 13:45:44 -04:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
def test_hote_sans_noeud_emet_noeud_vide() -> None:
|
|
|
|
|
lignes = parametres_proxmox_hote(_inventaire_factice(), "app-02")
|
|
|
|
|
assert "SETOPS_NOEUD=''" in lignes, lignes
|
|
|
|
|
assert "SETOPS_VMID='95201'" in lignes, lignes
|
|
|
|
|
|
|
|
|
|
|
dns : client_unbound universel, et un resolveur d'amorcage
Une VM ne peut pas s'installer sans resoudre des noms. PowerDNS repond
UNIQUEMENT pour la zone souveraine et ne recurse pour personne : il manquait un
resolveur recursif. `client_unbound` est l'outil ecrit pour ca — il rejoint
`client_journal` et `client_metrique` parmi les integrations universelles.
`infra-dns-01` est exempte (PowerDNS occupe son port 53), et
`serveur_powerdns_listen_addresses` passe de 0.0.0.0 a l'adresse de l'hote pour
laisser 127.0.0.1:53 libre. Mais l'appartenance au groupe est AUSSI ce qui ouvre
le port 53 a la frontiere : en exemptant la machine, je lui retirais le droit de
resoudre. `serveur_powerdns` declare donc son propre flux sortant — il ne
recurse pour personne, mais doit resoudre pour lui-meme.
Nouvel intrant `dns_amorcage`, derive jusqu'a `make creer-vm`. Cloud-init
l'ecrit bien mais sans effet : `dns-nameservers` exige `resolvconf`, absent du
gabarit, et installer resolvconf demande apt, qui demande la resolution.
`serveur_debian` pose donc le resolveur en pre_tasks, avant le premier apt, avec
une garde qui respecte la bascule ulterieure de client_unbound.
Defaut corrige en chemin : `_intrants_communs()` lisait `instance/` en dur ; le
chemin derive maintenant de l'inventaire recu, et un test le prouve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:41:58 -04:00
|
|
|
def test_intrant_commun_lu_a_cote_de_l_inventaire_recu() -> None:
|
|
|
|
|
"""`dns_amorcage` vit dans group_vars/all, pas dans les vars d'hote.
|
|
|
|
|
|
|
|
|
|
Et il doit etre lu A COTE de l'inventaire passe en argument : lire `instance/` en
|
|
|
|
|
dur ferait dependre le resultat de l'environnement, et un test synthetique irait
|
|
|
|
|
chercher les intrants de la production.
|
|
|
|
|
"""
|
|
|
|
|
import tempfile
|
|
|
|
|
with tempfile.TemporaryDirectory() as tmp:
|
|
|
|
|
base = Path(tmp)
|
|
|
|
|
(base / "group_vars" / "all").mkdir(parents=True)
|
|
|
|
|
(base / "group_vars" / "all" / "10-intrants.yml").write_text(
|
|
|
|
|
"dns_amorcage: 9.9.9.9\n", encoding="utf-8")
|
|
|
|
|
inv = base / "hosts.yml"
|
|
|
|
|
inv.write_text("all: {children: {}}\n", encoding="utf-8")
|
|
|
|
|
lignes = parametres_proxmox_hote(_inventaire_factice(), "app-01", inv)
|
|
|
|
|
assert "SETOPS_DNS='9.9.9.9'" in lignes, lignes
|
|
|
|
|
# Sans inventaire, aucun intrant commun n'est devine.
|
|
|
|
|
assert "SETOPS_DNS=''" in parametres_proxmox_hote(_inventaire_factice(), "app-01")
|
|
|
|
|
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
def test_hote_absent_refuse() -> None:
|
|
|
|
|
try:
|
|
|
|
|
parametres_proxmox_hote(_inventaire_factice(), "inconnu-01")
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
assert "absent" in str(exc), exc
|
|
|
|
|
else:
|
|
|
|
|
raise AssertionError("un hote absent doit lever ValueError")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_champ_requis_manquant_refuse() -> None:
|
|
|
|
|
try:
|
|
|
|
|
parametres_proxmox_hote(_inventaire_factice(), "incomplet-01")
|
|
|
|
|
except ValueError as exc:
|
|
|
|
|
assert "manquants" in str(exc) and "ansible_host" in str(exc), exc
|
|
|
|
|
else:
|
|
|
|
|
raise AssertionError("un champ requis manquant doit lever ValueError")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
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
2026-08-28 13:45:44 -04:00
|
|
|
# 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.
|
2026-06-24 20:17:46 -04:00
|
|
|
tests = [
|
|
|
|
|
test_hote_complet_avec_noeud,
|
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
2026-08-28 13:45:44 -04:00
|
|
|
# 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,
|
2026-06-24 20:17:46 -04:00
|
|
|
test_hote_sans_noeud_emet_noeud_vide,
|
dns : client_unbound universel, et un resolveur d'amorcage
Une VM ne peut pas s'installer sans resoudre des noms. PowerDNS repond
UNIQUEMENT pour la zone souveraine et ne recurse pour personne : il manquait un
resolveur recursif. `client_unbound` est l'outil ecrit pour ca — il rejoint
`client_journal` et `client_metrique` parmi les integrations universelles.
`infra-dns-01` est exempte (PowerDNS occupe son port 53), et
`serveur_powerdns_listen_addresses` passe de 0.0.0.0 a l'adresse de l'hote pour
laisser 127.0.0.1:53 libre. Mais l'appartenance au groupe est AUSSI ce qui ouvre
le port 53 a la frontiere : en exemptant la machine, je lui retirais le droit de
resoudre. `serveur_powerdns` declare donc son propre flux sortant — il ne
recurse pour personne, mais doit resoudre pour lui-meme.
Nouvel intrant `dns_amorcage`, derive jusqu'a `make creer-vm`. Cloud-init
l'ecrit bien mais sans effet : `dns-nameservers` exige `resolvconf`, absent du
gabarit, et installer resolvconf demande apt, qui demande la resolution.
`serveur_debian` pose donc le resolveur en pre_tasks, avant le premier apt, avec
une garde qui respecte la bascule ulterieure de client_unbound.
Defaut corrige en chemin : `_intrants_communs()` lisait `instance/` en dur ; le
chemin derive maintenant de l'inventaire recu, et un test le prouve.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-06 21:41:58 -04:00
|
|
|
test_intrant_commun_lu_a_cote_de_l_inventaire_recu,
|
2026-06-24 20:17:46 -04:00
|
|
|
test_hote_absent_refuse,
|
|
|
|
|
test_champ_requis_manquant_refuse,
|
|
|
|
|
]
|
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
2026-08-28 13:45:44 -04:00
|
|
|
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)}"
|
2026-06-24 20:17:46 -04:00
|
|
|
for test in tests:
|
|
|
|
|
test()
|
|
|
|
|
print(f"ok {test.__name__}")
|
|
|
|
|
print(f"{len(tests)} tests passes.")
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|