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>
This commit is contained in:
parent
83c05b8320
commit
e276e76f32
9 changed files with 241 additions and 6 deletions
41
CHANGELOG.md
41
CHANGELOG.md
|
|
@ -2,6 +2,47 @@
|
||||||
|
|
||||||
## 2026-08-06 — le chemin nord-sud devient dérivable
|
## 2026-08-06 — le chemin nord-sud devient dérivable
|
||||||
|
|
||||||
|
### `client_unbound` devient universel — et l'amorçage DNS trouve sa place
|
||||||
|
|
||||||
|
Une VM ne peut pas s'installer sans résoudre des noms : `apt` en dépend. Or le DNS
|
||||||
|
autoritatif du tenant (PowerDNS) répond **uniquement** pour la zone souveraine et refuse
|
||||||
|
le reste — il ne récurse pour personne. Il manquait donc un résolveur récursif, et
|
||||||
|
`client_unbound` est exactement l'outil écrit pour ça : zone interne déléguée à
|
||||||
|
l'autoritatif, récursion depuis la racine, aucune dépendance au résolveur d'un fournisseur.
|
||||||
|
|
||||||
|
Il rejoint `client_journal` et `client_metrique` parmi les intégrations universelles :
|
||||||
|
13 hôtes sur 14, déclarés **une fois** dans `meta/integration.yml`, et 8 déclarations
|
||||||
|
redondantes retirées du plan.
|
||||||
|
|
||||||
|
**L'exemption, et son revers.** `infra-dns-01` est exempté : PowerDNS occupe déjà son port
|
||||||
|
53, y ajouter Unbound produirait un conflit de liaison. Au passage,
|
||||||
|
`serveur_powerdns_listen_addresses` passe de `0.0.0.0` à l'adresse de l'hôte — lier toutes
|
||||||
|
les interfaces occupait aussi `127.0.0.1:53`, là où un résolveur local voudrait s'installer.
|
||||||
|
|
||||||
|
Mais l'appartenance au groupe est **aussi** ce qui ouvre le port 53 à la frontière. En
|
||||||
|
exemptant la machine, je lui retirais le droit de résoudre : le serveur qui devait servir de
|
||||||
|
DNS au tenant était le seul à ne pas pouvoir s'installer. `serveur_powerdns` déclare donc son
|
||||||
|
propre flux sortant — il ne récurse pour personne, mais il doit résoudre **pour lui-même**.
|
||||||
|
|
||||||
|
### Le résolveur d'amorçage, et pourquoi cloud-init ne suffisait pas
|
||||||
|
|
||||||
|
Nouvel intrant `dns_amorcage`, dérivé jusqu'à `make creer-vm`. Cloud-init l'écrit bien —
|
||||||
|
mesuré : `dns-nameservers 9.9.9.9 149.112.112.112` dans `50-cloud-init`. Et il reste **sans
|
||||||
|
effet** : `dns-nameservers` d'ifupdown exige `resolvconf`, absent du gabarit doré, qui
|
||||||
|
transporte en outre un `/etc/resolv.conf` figé de l'ancien monde. Installer `resolvconf`
|
||||||
|
demanderait `apt`, qui demande la résolution : la boucle se referme.
|
||||||
|
|
||||||
|
`serveur_debian` pose donc le résolveur en `pre_tasks`, **avant** `common_packages` — donc
|
||||||
|
avant le premier `apt`. Une garde tient le passage de relais : si `127.0.0.1` est déjà là,
|
||||||
|
`client_unbound` a basculé et le fichier n'est pas touché. Sans elle, chaque déploiement
|
||||||
|
aurait défait la bascule, et deux rôles se seraient disputé le même fichier sans fin.
|
||||||
|
|
||||||
|
**Un défaut créé puis corrigé en chemin.** La première version de `_intrants_communs()` lisait
|
||||||
|
`instance/` en dur : un test sur inventaire synthétique serait allé chercher les intrants de
|
||||||
|
la production. Le chemin dérive maintenant de l'inventaire reçu, et un test le prouve dans les
|
||||||
|
deux sens — avec inventaire, et sans.
|
||||||
|
|
||||||
|
|
||||||
### La première VM tenant, et les quatre défauts qu'elle a révélés
|
### La première VM tenant, et les quatre défauts qu'elle a révélés
|
||||||
|
|
||||||
`infra-pki-01` recréée pour éprouver la chaîne complète. Le CA est le bon premier service :
|
`infra-pki-01` recréée pour éprouver la chaîne complète. Le CA est le bon premier service :
|
||||||
|
|
|
||||||
2
Makefile
2
Makefile
|
|
@ -660,7 +660,7 @@ creer-vm: _instance-requise
|
||||||
VMID_MODELE="$(VMID_MODELE)" \
|
VMID_MODELE="$(VMID_MODELE)" \
|
||||||
FORMAT_DISQUE="$(FORMAT_DISQUE)" \
|
FORMAT_DISQUE="$(FORMAT_DISQUE)" \
|
||||||
DISQUE_PROXMOX="$(DISQUE_PROXMOX)" \
|
DISQUE_PROXMOX="$(DISQUE_PROXMOX)" \
|
||||||
DNS="$(DNS)" \
|
DNS="$${SETOPS_DNS:-$(DNS)}" \
|
||||||
DHCP="$(DHCP)" \
|
DHCP="$(DHCP)" \
|
||||||
CIUSER="$(CIUSER)" \
|
CIUSER="$(CIUSER)" \
|
||||||
CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" \
|
CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" \
|
||||||
|
|
|
||||||
66
docs/audit/preuve-2026-08-06.md
Normal file
66
docs/audit/preuve-2026-08-06.md
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Preuve de conformite — Set-OPS — 2026-08-06
|
||||||
|
|
||||||
|
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||||
|
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||||
|
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||||
|
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||||
|
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||||
|
|
||||||
|
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||||
|
- **Verdict** : ❌ NON CONFORME (29 OK · 1 echec · 0 saute)
|
||||||
|
|
||||||
|
## Preuves
|
||||||
|
|
||||||
|
| # | Preuve | Affirmations | Statut | Detail |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | |
|
||||||
|
| P02 | Tests unitaires (inventory_host) | — | ❌ ECHEC | rc=1 : AssertionError: ["SETOPS_VMID='93101'", "SETOPS_IP='10.0.2.11'", "SETOPS_CIDR='24'", "SETOPS_PASSERELLE='10.0.2.1'", "SETOPS_VLAN='13'", "SETOPS_PONT=''" |
|
||||||
|
| P03 | Diff-vide du plan (inventaire genere) | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | DIFF VIDE : le plan reproduit exactement l'inventaire actuel. Bascule possible. |
|
||||||
|
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||||
|
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||||
|
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||||
|
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||||
|
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 30 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||||
|
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 29 rôles, 70 flux, schéma + matrice OK. |
|
||||||
|
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||||
|
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||||
|
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||||
|
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||||
|
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||||
|
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||||
|
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 31 groupes (inventaire dechiffre et parse). |
|
||||||
|
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||||
|
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 24 secret(s) exige(s), tous presents. Voute reelle : 27 cle(s), aucun manque. |
|
||||||
|
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||||
|
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||||
|
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 2 instance(s) federee(s), aucun index en collision. |
|
||||||
|
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (19 sections). |
|
||||||
|
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 6 reseau(x), aucune collision avec la plage tenant. |
|
||||||
|
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 27 regles, 2 routes, admin=10.0.0.0/24,192.168.254.2/32,192.168.255.2/32. |
|
||||||
|
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 2 tenant(s), 36 groupe(s), 60 regle(s). |
|
||||||
|
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (2 exemption(s) derivee(s) du service rendu). |
|
||||||
|
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 0 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||||
|
| 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. |
|
||||||
|
|
||||||
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||||
|
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||||
|
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||||
|
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||||
|
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||||
|
|
||||||
|
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||||
|
|
||||||
|
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||||
|
comme declarations d'intention, non comme preuves :
|
||||||
|
|
||||||
|
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||||
|
contre une flotte vivante.
|
||||||
|
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||||
|
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||||
|
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||||
|
|
||||||
|
_Rapport genere le 2026-08-06._
|
||||||
|
|
@ -11,6 +11,39 @@
|
||||||
- ansible_facts.distribution == "Debian"
|
- ansible_facts.distribution == "Debian"
|
||||||
fail_msg: "Ce playbook est prévu pour Debian."
|
fail_msg: "Ce playbook est prévu pour Debian."
|
||||||
|
|
||||||
|
# Résolveur d'AMORÇAGE, posé avant `common_packages` — donc avant le premier `apt`.
|
||||||
|
# Sans lui, le déploiement échoue avant d'avoir commencé : le gabarit doré transporte
|
||||||
|
# un `/etc/resolv.conf` figé, et le `dns-nameservers` écrit par cloud-init reste sans
|
||||||
|
# effet faute de `resolvconf` (qu'on ne peut pas installer sans résolution — la boucle
|
||||||
|
# se referme). Mesuré le 2026-08-06 sur deux VM neuves.
|
||||||
|
#
|
||||||
|
# PASSAGE DE RELAIS : `client_unbound` reprend ce fichier plus tard pour le basculer
|
||||||
|
# vers `127.0.0.1`. La garde ci-dessous le respecte — sans elle, chaque déploiement
|
||||||
|
# défairait la bascule, et deux rôles se disputeraient le même fichier sans fin.
|
||||||
|
- name: Lire le résolveur en place
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: /etc/resolv.conf
|
||||||
|
register: serveur_debian_resolv_actuel
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
- name: Poser le résolveur d'amorçage (avant tout apt)
|
||||||
|
ansible.builtin.copy:
|
||||||
|
dest: /etc/resolv.conf
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
content: |
|
||||||
|
# Géré par Set-OPS — résolveur d'amorçage (intrant `dns_amorcage`).
|
||||||
|
# Remplacé par `client_unbound` lorsqu'il bascule vers le résolveur local.
|
||||||
|
{% for serveur in dns_amorcage.split(',') if serveur | trim %}
|
||||||
|
nameserver {{ serveur | trim }}
|
||||||
|
{% endfor %}
|
||||||
|
when:
|
||||||
|
- dns_amorcage is defined
|
||||||
|
- dns_amorcage | string | length > 0
|
||||||
|
- "'127.0.0.1' not in (serveur_debian_resolv_actuel.content | default('') | b64decode | string)"
|
||||||
|
|
||||||
roles:
|
roles:
|
||||||
- common_packages
|
- common_packages
|
||||||
- hosts_statiques
|
- hosts_statiques
|
||||||
|
|
|
||||||
16
roles/client_unbound/meta/integration.yml
Normal file
16
roles/client_unbound/meta/integration.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
---
|
||||||
|
# Politique d'integration. Declaree ICI, une fois, plutot que recopiee sur chaque
|
||||||
|
# serveur du plan. Voir docs/integrations-vm.md et docs/decisions-architecture.md (D-33).
|
||||||
|
integration:
|
||||||
|
universelle: true
|
||||||
|
raison: >-
|
||||||
|
Tout hote doit resoudre des noms — ne serait-ce que pour `apt`. Le DNS
|
||||||
|
autoritatif du tenant (PowerDNS) ne repond QUE pour la zone souveraine et
|
||||||
|
refuse le reste : sans resolveur recursif, une machine ne peut meme pas
|
||||||
|
s'installer. `client_unbound` delegue la zone interne a l'autoritatif et
|
||||||
|
recurse depuis la racine, sans dependre du resolveur d'un fournisseur.
|
||||||
|
# Le serveur autoritatif est exempte : PowerDNS occupe deja le port 53 sur cet
|
||||||
|
# hote. Y ajouter Unbound produirait un conflit de liaison, pas un resolveur.
|
||||||
|
# Il resout par le resolveur d'amorcage (`proxmox_clone_dns`), qui lui suffit :
|
||||||
|
# ses seuls besoins externes sont apt et NTP.
|
||||||
|
sauf_role: serveur_powerdns
|
||||||
|
|
@ -17,9 +17,12 @@ serveur_powerdns_soa_refresh: 3600
|
||||||
serveur_powerdns_soa_retry: 900
|
serveur_powerdns_soa_retry: 900
|
||||||
serveur_powerdns_soa_expire: 1209600
|
serveur_powerdns_soa_expire: 1209600
|
||||||
serveur_powerdns_soa_minimum: 3600
|
serveur_powerdns_soa_minimum: 3600
|
||||||
|
# L'adresse du tenant SEULEMENT, pas `0.0.0.0` : lier toutes les interfaces occupe aussi
|
||||||
|
# `127.0.0.1:53`, ou un resolveur recursif local voudrait s'installer. Ce n'est pas un
|
||||||
|
# probleme theorique — c'est la raison pour laquelle `client_unbound` exempte cet hote.
|
||||||
|
# PowerDNS reste autoritatif pur : il repond pour la zone souveraine et REFUSE le reste.
|
||||||
serveur_powerdns_listen_addresses:
|
serveur_powerdns_listen_addresses:
|
||||||
- "0.0.0.0"
|
- "{{ ansible_host | default('0.0.0.0') }}"
|
||||||
- "::"
|
|
||||||
serveur_powerdns_allow_axfr_ips: []
|
serveur_powerdns_allow_axfr_ips: []
|
||||||
|
|
||||||
# Enregistrements additionnels geres explicitement.
|
# Enregistrements additionnels geres explicitement.
|
||||||
|
|
|
||||||
|
|
@ -13,3 +13,21 @@ flux:
|
||||||
pair: flotte
|
pair: flotte
|
||||||
chiffrement: clair
|
chiffrement: clair
|
||||||
raison: "Résolution DNS interne en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips)."
|
raison: "Résolution DNS interne en TCP (réponses volumineuses, AXFR restreint par allow_axfr_ips)."
|
||||||
|
|
||||||
|
# L'autoritatif ne RÉCURSE pour personne — mais il doit résoudre POUR LUI-MÊME : `apt`,
|
||||||
|
# NTP, les certificats. Il est le seul hôte exempté de `client_unbound` (PowerDNS occupe
|
||||||
|
# déjà son port 53), donc rien d'autre ne lui ouvre ce flux. Sans cette déclaration, la
|
||||||
|
# frontière jette ses requêtes et la machine ne peut même pas s'installer — mesuré le
|
||||||
|
# 2026-08-06, sur la VM qui devait justement servir de DNS au tenant.
|
||||||
|
- sens: egress
|
||||||
|
port: 53
|
||||||
|
protocole: udp
|
||||||
|
pair: externe
|
||||||
|
chiffrement: clair
|
||||||
|
raison: "Résolution sortante du serveur autoritatif POUR SES PROPRES besoins (apt, NTP) — il ne récurse pour aucun autre hôte."
|
||||||
|
- sens: egress
|
||||||
|
port: 53
|
||||||
|
protocole: tcp
|
||||||
|
pair: externe
|
||||||
|
chiffrement: clair
|
||||||
|
raison: "Repli TCP de la résolution sortante du serveur autoritatif (réponses dépassant la taille UDP)."
|
||||||
|
|
|
||||||
|
|
@ -165,10 +165,45 @@ CHAMPS_PROXMOX: tuple[tuple, ...] = (
|
||||||
("SETOPS_NOEUD", "proxmox_noeud", False),
|
("SETOPS_NOEUD", "proxmox_noeud", False),
|
||||||
("SETOPS_COEURS", "proxmox_coeurs", False),
|
("SETOPS_COEURS", "proxmox_coeurs", False),
|
||||||
("SETOPS_MEMOIRE", "proxmox_memoire", False),
|
("SETOPS_MEMOIRE", "proxmox_memoire", False),
|
||||||
|
# Resolveur d'AMORCAGE, pose par cloud-init. Il ne sert qu'une fois : `client_unbound`
|
||||||
|
# bascule ensuite `/etc/resolv.conf` vers 127.0.0.1. Mais sans lui, la VM nait sans
|
||||||
|
# resolution de noms et `apt` ne peut meme pas installer Unbound — l'amorcage
|
||||||
|
# n'aboutit jamais. Le DNS autoritatif du tenant ne convient PAS : il refuse tout ce
|
||||||
|
# qui sort de la zone souveraine.
|
||||||
|
("SETOPS_DNS", "dns_amorcage", False),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def parametres_proxmox_hote(data: dict, host: str) -> list[str]:
|
def _intrants_communs(inventaire: Path | None) -> dict:
|
||||||
|
"""Intrants de `group_vars/all/` voisins de CET inventaire, visibles au CLONAGE.
|
||||||
|
|
||||||
|
L'inventaire genere ne porte que des variables d'HOTE ; un intrant commun comme
|
||||||
|
`dns_amorcage` y est donc invisible. Le clonage tourne sur localhost, avant qu'Ansible
|
||||||
|
ne resolve les group_vars de la cible : sans cette lecture, le parametre serait lu par
|
||||||
|
les roles mais jamais par la machine qui cree la VM.
|
||||||
|
|
||||||
|
Le chemin DERIVE de l'inventaire recu, jamais code en dur : lire `instance/` pendant
|
||||||
|
qu'on raisonne sur un autre inventaire ferait dependre le resultat de l'environnement
|
||||||
|
— et un test sur inventaire synthetique irait chercher les intrants de la production.
|
||||||
|
"""
|
||||||
|
if inventaire is None:
|
||||||
|
return {}
|
||||||
|
dossier = Path(inventaire).resolve().parent / "group_vars" / "all"
|
||||||
|
if not dossier.is_dir():
|
||||||
|
return {}
|
||||||
|
fusion: dict = {}
|
||||||
|
for f in sorted(dossier.glob("*.yml")):
|
||||||
|
try:
|
||||||
|
contenu = yaml.safe_load(f.read_text(encoding="utf-8"))
|
||||||
|
except (OSError, yaml.YAMLError):
|
||||||
|
continue # voute chiffree ou fichier illisible : sans objet ici
|
||||||
|
if isinstance(contenu, dict):
|
||||||
|
fusion.update(contenu)
|
||||||
|
return fusion
|
||||||
|
|
||||||
|
|
||||||
|
def parametres_proxmox_hote(data: dict, host: str,
|
||||||
|
inventaire: Path | None = None) -> list[str]:
|
||||||
"""Emet les parametres de clonage Proxmox d'un hote, lus depuis l'inventaire.
|
"""Emet les parametres de clonage Proxmox d'un hote, lus depuis l'inventaire.
|
||||||
|
|
||||||
Sortie shell-evaluable (`SETOPS_*='valeur'`) consommee par `make creer-vm`.
|
Sortie shell-evaluable (`SETOPS_*='valeur'`) consommee par `make creer-vm`.
|
||||||
|
|
@ -181,7 +216,7 @@ def parametres_proxmox_hote(data: dict, host: str) -> list[str]:
|
||||||
"`make instancier-appliquer`."
|
"`make instancier-appliquer`."
|
||||||
)
|
)
|
||||||
|
|
||||||
vars_for_host = variables_hote(data, host)
|
vars_for_host = _intrants_communs(inventaire) | variables_hote(data, host)
|
||||||
lignes: list[str] = []
|
lignes: list[str] = []
|
||||||
manquants: list[str] = []
|
manquants: list[str] = []
|
||||||
|
|
||||||
|
|
@ -450,7 +485,7 @@ def main() -> int:
|
||||||
elif args.command == "afficher":
|
elif args.command == "afficher":
|
||||||
afficher_hote(data, args.hote)
|
afficher_hote(data, args.hote)
|
||||||
elif args.command == "parametres-proxmox":
|
elif args.command == "parametres-proxmox":
|
||||||
for ligne in parametres_proxmox_hote(data, args.hote):
|
for ligne in parametres_proxmox_hote(data, args.hote, args.inventaire):
|
||||||
print(ligne)
|
print(ligne)
|
||||||
elif args.command == "playbooks":
|
elif args.command == "playbooks":
|
||||||
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
|
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
|
||||||
|
|
|
||||||
|
|
@ -95,6 +95,7 @@ def test_hote_complet_avec_noeud() -> None:
|
||||||
"SETOPS_NOEUD='noeud-a'",
|
"SETOPS_NOEUD='noeud-a'",
|
||||||
"SETOPS_COEURS='4'",
|
"SETOPS_COEURS='4'",
|
||||||
"SETOPS_MEMOIRE='2048'",
|
"SETOPS_MEMOIRE='2048'",
|
||||||
|
"SETOPS_DNS=''",
|
||||||
}
|
}
|
||||||
assert set(lignes) == attendu, lignes
|
assert set(lignes) == attendu, lignes
|
||||||
|
|
||||||
|
|
@ -105,6 +106,27 @@ def test_hote_sans_noeud_emet_noeud_vide() -> None:
|
||||||
assert "SETOPS_VMID='95201'" in lignes, lignes
|
assert "SETOPS_VMID='95201'" in lignes, lignes
|
||||||
|
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
|
||||||
def test_hote_absent_refuse() -> None:
|
def test_hote_absent_refuse() -> None:
|
||||||
try:
|
try:
|
||||||
parametres_proxmox_hote(_inventaire_factice(), "inconnu-01")
|
parametres_proxmox_hote(_inventaire_factice(), "inconnu-01")
|
||||||
|
|
@ -127,6 +149,7 @@ def main() -> int:
|
||||||
tests = [
|
tests = [
|
||||||
test_hote_complet_avec_noeud,
|
test_hote_complet_avec_noeud,
|
||||||
test_hote_sans_noeud_emet_noeud_vide,
|
test_hote_sans_noeud_emet_noeud_vide,
|
||||||
|
test_intrant_commun_lu_a_cote_de_l_inventaire_recu,
|
||||||
test_hote_absent_refuse,
|
test_hote_absent_refuse,
|
||||||
test_champ_requis_manquant_refuse,
|
test_champ_requis_manquant_refuse,
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue