console chez les locataires, collision de noms, et un fichier ecrase
Les deux locataires declarent leur console derriere oauth2-proxy. En oidc le vestibule n ecoute que la boucle locale : le gabarit annoncait cette protection en s en remettant au pare-feu, qui ouvrait le port depuis l edge — la console etait joignable sans passer par la passerelle. COLLISION REELLE : serveur_oauth2_proxy est mono-instance par machine, et la table des noms publics etait indexee par GROUPE. La passerelle de la vigie se croyait la console ; son URL de retour OIDC aurait vise l autre machine. Une application nomme un COUPLE (machine, role). P67 ne le voyait pas parce qu elle indexait comme la derivation qu elle garde — une garde qui reproduit le raisonnement qu elle verifie ne verifie rien. ET J AI ECRASE group_vars/serveur_ops.yml sans le regarder : 68 lignes detruites, deux preuves tombees, et une explication fausse construite dessus. Le git diff qui m avait rassure portait un glob developpe par le shell parent : il n a rien matche et n a rien dit. Une commande qui ne trouve rien et une commande qui trouve que rien n a change rendent le meme silence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
0529951381
commit
e93e8f4c1d
8 changed files with 180 additions and 39 deletions
68
CHANGELOG.md
68
CHANGELOG.md
|
|
@ -1,5 +1,73 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-09-15 (5) — La console chez les locataires, une collision de noms, et un fichier que j'ai ecrase
|
||||
|
||||
Les deux locataires declarent desormais leur console derriere leur SSO. Le chemin a
|
||||
traverse une collision reelle et une faute de ma part.
|
||||
|
||||
### `console.<domaine>`, derriere oauth2-proxy
|
||||
|
||||
ops-01 serveur_ops_gui_actif: true, auth: oidc
|
||||
le GUI sur 127.0.0.1:8765, le vestibule nginx sur 127.0.0.1:8090
|
||||
la passerelle sur 4180, publiee par l'edge
|
||||
|
||||
`oidc` ET PAS `locale` : ces ecosystemes ont un annuaire. Cette console lance des
|
||||
deploiements et peut RASER — un groupe se revoque sans deploiement (D-66), un mot de passe
|
||||
partage devant ce pouvoir est un accident qui attend.
|
||||
|
||||
ET LE VESTIBULE N'ECOUTE PLUS QUE LA BOUCLE LOCALE EN `oidc`. Le gabarit annoncait cette
|
||||
protection — *« lui seul doit pouvoir frapper ce port »* — en s'en remettant a
|
||||
`meta/flux.yml`, qui ouvre le 8090 depuis `[edge, admin]`. L'edge pouvait donc joindre la
|
||||
console SANS passer par la passerelle. Un port qui n'est pas ouvert ne se contourne pas ;
|
||||
une regle, si.
|
||||
|
||||
### UNE COLLISION DE NOMS QUE LA GARDE NE VOYAIT PAS
|
||||
|
||||
`serveur_oauth2_proxy` est mono-instance par machine : une devant la vigie sur le noeud de
|
||||
supervision, une devant la console sur le runner. La table des noms publics etait indexee
|
||||
par GROUPE :
|
||||
|
||||
mon-01 serveur_oauth2_proxy_hostname = console.chezlepro.internal
|
||||
ops-01 serveur_oauth2_proxy_hostname = console.chezlepro.internal
|
||||
|
||||
La passerelle de la VIGIE se croyait la console. Son URL de retour OIDC aurait vise l'autre
|
||||
machine, et le SSO de la supervision serait tombe — pour un service auquel on n'avait pas
|
||||
touche.
|
||||
|
||||
**Une application nomme un COUPLE (machine, role).** `instancier` et `site_inventaire` en
|
||||
ont la clef juste desormais.
|
||||
|
||||
ET P67 NE LE VOYAIT PAS, parce qu'elle indexait par groupe **comme la derivation qu'elle
|
||||
garde** : elle comparait la valeur a elle-meme. *Une garde qui reproduit le raisonnement
|
||||
qu'elle verifie ne verifie rien.* Elle compare maintenant par machine, et elle a ete
|
||||
eprouvee en lui remettant la faute sous les yeux.
|
||||
|
||||
### CE QUE J'AI CASSE, ET LA MESURE QUI ME L'A CACHE
|
||||
|
||||
J'ai ecrit `group_vars/serveur_ops.yml` avec `cat >` **sans regarder s'il existait**. Il
|
||||
existait : 84 lignes chez Chezlepro, 77 chez TechnoLibre. J'en ai detruit 68 — la liste
|
||||
des depots du genome, la branche `master` de TechnoLibre, l'amont de la forge, la source
|
||||
de la cle de voute.
|
||||
|
||||
Deux preuves sont tombees (P05, P32). **J'ai d'abord accuse le retrait des forges de la
|
||||
veille**, et construit une explication complete : « le site expose l'amont, le locataire
|
||||
ne le porte pas ». Elle etait fausse — le fichier restaure le portait deja.
|
||||
|
||||
LA MESURE QUI M'A RASSURE A TORT :
|
||||
|
||||
(cd $e && git diff -- inventories/*/group_vars/serveur_ops.yml)
|
||||
|
||||
Le glob est developpe par le shell PARENT, ou `inventories/` n'existe pas. Le motif est
|
||||
passe tel quel, n'a rien matche, et `git diff` n'a rien affiche. J'ai lu ce silence comme
|
||||
« aucun fichier ecrase ». C'est `git status` — sans glob — qui a fini par montrer le `M`.
|
||||
|
||||
Une commande qui ne trouve rien et une commande qui trouve que rien n'a change rendent le
|
||||
meme silence. Encore la meme forme que les six replis des deux derniers jours, cette
|
||||
fois dans mes propres mains.
|
||||
|
||||
Restaure par `git checkout`, puis les trois lignes de la console ajoutees SANS toucher au
|
||||
reste. Harnais : **77 OK**.
|
||||
|
||||
## 2026-09-15 (4) — La console d'exploitation est allumee, et publiee par l'edge
|
||||
|
||||
https://console.genese.internal/ 401 sans justificatif, la console avec
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@
|
|||
| 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 | ⚪ SAUTE | Voute chiffree sans ANSIBLE_VAULT_PASSWORD_FILE (prerequis AFF-026). |
|
||||
| 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 : 30 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 31 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 29 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 : 6 instance(s) federee(s), aucun index en collision. |
|
||||
|
|
@ -44,7 +44,7 @@
|
|||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, 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, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 67 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 36 exigence(s) de role, toutes satisfaites (142 cle(s) declaree(s) par l'instance). |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 36 exigence(s) de role, toutes satisfaites (144 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 36 revendication(s) de port, aucune collision entre roles co-localises (35 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (41 genere(s) exempte(s)). |
|
||||
| P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 4 application(s) exigeant une base l'ont toutes (3 entree(s) au registre). |
|
||||
|
|
@ -78,8 +78,8 @@
|
|||
| P63 | cloud-init nait avec la VM et ne lui survit pas | — | ✅ OK | cloud-init est au gabarit (la premiere seconde), absent du socle (pas de va-et-vient), et retire par le durcissement — avec la garde qui verifie que le reseau s |
|
||||
| P64 | Sondes de supervision : declarees ET deposees | — | ✅ OK | 40 sonde(s) declaree(s) ET deposee(s), chacune avec sa raison et son `ttl` : client_journal/journaux, client_metrique/metriques, client_pki/certificat, serveur_ |
|
||||
| P65 | Depots tiers : demandes au cache, jamais en HTTPS direct | — | ✅ OK | 4 depot(s) tiers relaye(s) par le cache, aucun role ne les vise en https:// ecrit en dur. |
|
||||
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 3 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (5 exposition(s)). |
|
||||
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 12 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
|
||||
| P66 | Clients OIDC : chaque URI vise un nom que le plan expose | — | ✅ OK | 4 client(s) OIDC, toutes leurs URI visent un FQDN que le plan expose (6 exposition(s)). |
|
||||
| P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 13 service(s) expose(s) portent le nom du plan, sur 2 inventaire(s) : instance, SITE. |
|
||||
| P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. |
|
||||
| P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. |
|
||||
| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. |
|
||||
|
|
|
|||
|
|
@ -60,6 +60,14 @@ vault_icingaweb2_admin: ""
|
|||
# accident qui attend.
|
||||
vault_setops_gui_admin: ""
|
||||
|
||||
# LE SECRET OIDC DE LA CONSOLE D'EXPLOITATION (mode `oidc`).
|
||||
#
|
||||
# Le GUI de Set-OPS n'a aucune authentification a lui : la passerelle est sa seule
|
||||
# serrure, et ce secret est ce qui la lie a Keycloak. Vide chez un ecosysteme sans
|
||||
# annuaire — un SITE — qui emploie alors le vestibule local.
|
||||
vault_setops_console_oidc: ""
|
||||
|
||||
|
||||
|
||||
# LE COMPTE DE METRIQUES DE POSTGRESQL — lecture seule, role `pg_monitor`.
|
||||
#
|
||||
|
|
|
|||
|
|
@ -240,6 +240,22 @@ serveur_ops_gui_port: 8765
|
|||
# LE PORT QUE L'EDGE VA CHERCHER. C'est nginx qui l'ouvre, jamais le GUI lui-meme.
|
||||
serveur_ops_gui_port_public: 8090
|
||||
|
||||
# SUR QUOI CE PORT S'OUVRE — ET CA DEPEND DU VESTIBULE (2026-09-15).
|
||||
#
|
||||
# En `locale`, nginx EST le vestibule : il doit etre joignable par l'edge, donc il ecoute
|
||||
# sur toutes les interfaces et `meta/flux.yml` ouvre le 8090 depuis `[edge, admin]`.
|
||||
#
|
||||
# En `oidc`, la passerelle SSO se tient devant et c'est ELLE qui est publiee. Si nginx
|
||||
# restait ouvert, l'edge pourrait frapper le 8090 directement et CONTOURNER la passerelle
|
||||
# — la console serait alors accessible sans authentification a qui atteint l'hote. Le
|
||||
# commentaire du gabarit annoncait cette protection ; rien ne la tenait.
|
||||
#
|
||||
# On la tient ici, a l'ecoute, et pas par une regle : un port qui n'est pas ouvert ne se
|
||||
# contourne pas, alors qu'une regle se deplace. La regle du 8090 reste declaree pour le
|
||||
# mode `locale`, et devient simplement sans objet en `oidc`.
|
||||
serveur_ops_gui_nginx_bind: >-
|
||||
{{ '127.0.0.1:' if serveur_ops_gui_auth == 'oidc' else '' }}
|
||||
|
||||
# DEUX VESTIBULES, ET LE PREMIER EST LE BON.
|
||||
#
|
||||
# `oidc` — `serveur_oauth2_proxy` devant, donc Keycloak, donc un GROUPE d'annuaire.
|
||||
|
|
@ -268,9 +284,19 @@ serveur_ops_gui_admin_motdepasse: ""
|
|||
serveur_ops_gui_htpasswd: "/etc/nginx/.setops-gui.htpasswd"
|
||||
serveur_ops_gui_realm: "Console Set-OPS"
|
||||
|
||||
# LE NOM SOUS LEQUEL L'EDGE LE SERT — derive du plan par `instancier` et par
|
||||
# `site_inventaire`, comme tout nom public (P67). Le defaut n'est qu'un repli.
|
||||
serveur_ops_hostname: "console.{{ domaine_interne }}"
|
||||
# LE NOM SOUS LEQUEL LA CONSOLE EST SERVIE.
|
||||
#
|
||||
# EN `oidc`, LE PLAN LE DONNE A LA PASSERELLE, PAS AU RUNNER. C'est elle qui est publiee ;
|
||||
# `serveur_ops_hostname` sert alors au `server_name` du vestibule local, qui doit
|
||||
# reconnaitre l'en-tete `Host` que la passerelle relaie telle quelle. On le derive donc du
|
||||
# nom de la passerelle POSEE SUR CETTE MACHINE — une seule source, pas deux conventions.
|
||||
#
|
||||
# EN `locale`, le plan expose le runner lui-meme et `instancier` pose directement
|
||||
# `serveur_ops_hostname` : ce `default` ne s'applique alors pas.
|
||||
#
|
||||
# Le dernier repli garde la convention du depot, pour un ecosysteme qui n'expose rien.
|
||||
serveur_ops_hostname: >-
|
||||
{{ serveur_oauth2_proxy_hostname | default('console.' ~ domaine_interne, true) }}
|
||||
|
||||
# Le vestibule et de quoi hacher son mot de passe.
|
||||
serveur_ops_gui_paquets:
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@
|
|||
# passer tout chemin qu'un `location` plus specifique attraperait en premier. Une
|
||||
# serrure qu'on contourne par la porte d'a cote n'est pas une serrure.
|
||||
server {
|
||||
listen {{ serveur_ops_gui_port_public }};
|
||||
listen {{ serveur_ops_gui_nginx_bind }}{{ serveur_ops_gui_port_public }};
|
||||
{% if not serveur_ops_gui_nginx_bind %}
|
||||
listen [::]:{{ serveur_ops_gui_port_public }};
|
||||
{% endif %}
|
||||
server_name {{ serveur_ops_hostname }};
|
||||
{% if serveur_ops_gui_auth == 'locale' %}
|
||||
|
||||
|
|
@ -18,10 +20,13 @@ server {
|
|||
auth_basic_user_file {{ serveur_ops_gui_htpasswd }};
|
||||
{% else %}
|
||||
|
||||
# AVEC ANNUAIRE : c'est `serveur_oauth2_proxy` qui se tient devant, et lui seul doit
|
||||
# pouvoir frapper ce port. Le pare-feu de l'hote le dit deja (`meta/flux.yml`), et on
|
||||
# ne le redit pas ici : deux couches qui declarent la meme politique finissent par
|
||||
# en declarer deux differentes.
|
||||
# AVEC ANNUAIRE : `serveur_oauth2_proxy` se tient devant, sur la MEME machine, et
|
||||
# c'est lui qui est publie. Ce vhost n'ecoute alors que la boucle locale
|
||||
# (`serveur_ops_gui_nginx_bind`) — un port qui n'est pas ouvert ne se contourne pas.
|
||||
#
|
||||
# LA PREMIERE ECRITURE S'EN REMETTAIT AU PARE-FEU, et le pare-feu ouvrait ce port
|
||||
# depuis `[edge, admin]` : l'edge pouvait donc joindre la console SANS passer par la
|
||||
# passerelle. Le commentaire annoncait une protection que rien ne tenait.
|
||||
{% endif %}
|
||||
|
||||
# LA CONSOLE DEPLOIE, ET UN DEPLOIEMENT EST LONG. Les delais par defaut de nginx
|
||||
|
|
|
|||
|
|
@ -211,12 +211,31 @@ def generer() -> dict:
|
|||
# On derive donc `<groupe>_hostname` de l'exposition declaree, quand elle est UNIQUE
|
||||
# (deux expositions ne designent aucun nom canonique : on laisse le role choisir).
|
||||
# `setdefault` cote hote : une valeur posee a la main dans les group_vars gagne encore.
|
||||
hostname_par_groupe: dict = {}
|
||||
# PAR HOTE *ET* PAR GROUPE, PAS PAR GROUPE SEUL (2026-09-15).
|
||||
#
|
||||
# Un ROLE peut servir DEUX applications sur DEUX machines. `serveur_oauth2_proxy` en
|
||||
# est l'exemple : une instance devant la vigie sur le noeud de supervision, une autre
|
||||
# devant la console sur le runner. Deux passerelles, deux clients Keycloak, deux noms.
|
||||
#
|
||||
# Indexee par groupe seul, la table gardait le DERNIER nom rencontre et le donnait a
|
||||
# TOUS les hotes du groupe. Mesure du 2026-09-15 sur Chezlepro :
|
||||
#
|
||||
# mon-01 serveur_oauth2_proxy_hostname = console.chezlepro.internal
|
||||
# ops-01 serveur_oauth2_proxy_hostname = console.chezlepro.internal
|
||||
#
|
||||
# La passerelle de la VIGIE se croyait donc la console : son URL de retour OIDC aurait
|
||||
# vise l'autre machine, et le SSO de la supervision serait tombe. Le defaut du
|
||||
# 2026-09-10 — « le service porte le nom que son role devine » — reapparaissait par
|
||||
# une autre porte, et P67 ne le voyait pas puisqu'elle comparait a la valeur qu'elle
|
||||
# derivait elle-meme de la meme facon.
|
||||
#
|
||||
# Une application nomme un COUPLE (machine, role). C'est la clef juste.
|
||||
hostname_par_hote_groupe: dict = {}
|
||||
for app in apps.values():
|
||||
exp = [str(f).strip() for f in (app.get("expose") or []) if str(f).strip()]
|
||||
groupe = str(app.get("groupe") or "")
|
||||
if groupe and len(exp) == 1:
|
||||
hostname_par_groupe[groupe] = exp[0]
|
||||
groupe, hote = str(app.get("groupe") or ""), str(app.get("hote") or "")
|
||||
if groupe and hote and len(exp) == 1:
|
||||
hostname_par_hote_groupe[(hote, groupe)] = exp[0]
|
||||
|
||||
# La patte de la frontiere que les tenants traversent — calculee une fois.
|
||||
_temps_frontiere: list = []
|
||||
|
|
@ -366,9 +385,10 @@ def generer() -> dict:
|
|||
hostvars.setdefault("chrony_serveurs", _temps_frontiere)
|
||||
|
||||
# Nom public de chaque service porte par cet hote, tel que le plan l'expose.
|
||||
for groupe_expose in sorted(groupes & set(hostname_par_groupe)):
|
||||
hostvars.setdefault(f"{groupe_expose}_hostname",
|
||||
hostname_par_groupe[groupe_expose])
|
||||
for groupe_expose in sorted(groupes):
|
||||
fqdn = hostname_par_hote_groupe.get((nom, groupe_expose))
|
||||
if fqdn:
|
||||
hostvars.setdefault(f"{groupe_expose}_hostname", fqdn)
|
||||
# Le resolveur de l'ecosysteme doit savoir a qui demander la zone de l'hebergeur.
|
||||
if "serveur_resolveur" in groupes and zones_deleguees:
|
||||
hostvars["serveur_resolveur_zones_deleguees"] = zones_deleguees
|
||||
|
|
|
|||
|
|
@ -3507,14 +3507,15 @@ def preuve_nom_public_vient_du_plan() -> tuple[bool, str]:
|
|||
verifies = 0
|
||||
for etiquette, attendu, rendu in lots:
|
||||
enfants, hv = rendu["enfants"], rendu["hv"]
|
||||
for groupe, fqdn in sorted(attendu.items()):
|
||||
for hote in sorted((enfants.get(groupe) or {}).get("hosts") or {}):
|
||||
vu = str((hv.get(hote) or {}).get(f"{groupe}_hostname") or "")
|
||||
verifies += 1
|
||||
if vu != fqdn:
|
||||
fautes.append(f"[{etiquette}] {hote} porte {groupe} sans "
|
||||
f"`{groupe}_hostname` = `{fqdn}` "
|
||||
f"(inventaire : {vu or 'absent'})")
|
||||
for (hote, groupe), fqdn in sorted(attendu.items()):
|
||||
if hote not in ((enfants.get(groupe) or {}).get("hosts") or {}):
|
||||
continue # le plan declare l'app ailleurs que la ou le groupe est pose
|
||||
vu = str((hv.get(hote) or {}).get(f"{groupe}_hostname") or "")
|
||||
verifies += 1
|
||||
if vu != fqdn:
|
||||
fautes.append(f"[{etiquette}] {hote} porte {groupe} sans "
|
||||
f"`{groupe}_hostname` = `{fqdn}` "
|
||||
f"(inventaire : {vu or 'absent'})")
|
||||
if fautes:
|
||||
return False, ("Un service ne porte pas le nom que le plan lui donne — ses URL de "
|
||||
"retour viseront l'ancien :\n - " + "\n - ".join(fautes[:6]))
|
||||
|
|
@ -3526,16 +3527,24 @@ def preuve_nom_public_vient_du_plan() -> tuple[bool, str]:
|
|||
|
||||
|
||||
def _noms_exposes(f_app) -> dict:
|
||||
"""Groupe -> le nom unique que le plan lui donne. Deux noms : aucun canonique."""
|
||||
"""(machine, groupe) -> le nom unique que le plan lui donne.
|
||||
|
||||
PAR HOTE, ET PAS PAR GROUPE SEUL (2026-09-15). Cette preuve indexait par groupe, comme
|
||||
la derivation qu'elle garde — elle validait donc la meme erreur des deux cotes. Sur
|
||||
Chezlepro, `serveur_oauth2_proxy` sert la vigie sur un hote et la console sur un
|
||||
autre : les deux ont recu le DERNIER nom, et la preuve a rendu vert.
|
||||
|
||||
Une garde qui reproduit le raisonnement qu'elle verifie ne verifie rien.
|
||||
"""
|
||||
apps = (yaml.safe_load(f_app.read_text(encoding="utf-8")) or {}).get("applications") or {}
|
||||
attendu: dict[str, str] = {}
|
||||
attendu: dict[tuple[str, str], str] = {}
|
||||
for a in apps.values():
|
||||
if not isinstance(a, dict):
|
||||
continue
|
||||
exp = [str(f).strip() for f in (a.get("expose") or []) if str(f).strip()]
|
||||
g = str(a.get("groupe") or "")
|
||||
if g and len(exp) == 1:
|
||||
attendu[g] = exp[0]
|
||||
g, h = str(a.get("groupe") or ""), str(a.get("hote") or "")
|
||||
if g and h and len(exp) == 1:
|
||||
attendu[(h, g)] = exp[0]
|
||||
return attendu
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -519,12 +519,16 @@ def inventaire() -> dict:
|
|||
# UNE SEULE EXPOSITION, sinon rien : deux noms ne designent aucun canonique, et le
|
||||
# role garde alors la main. `setdefault` cote hote : une valeur ecrite a la main dans
|
||||
# les variables d'une machine gagne encore.
|
||||
_hostname_par_groupe: dict = {}
|
||||
# PAR HOTE *ET* PAR GROUPE (2026-09-15) — un role peut servir deux applications sur
|
||||
# deux machines, et la table indexee par groupe seul donnait le DERNIER nom a tous.
|
||||
# Voir `instancier.py`, ou le meme defaut a fait croire a la passerelle de la vigie
|
||||
# qu'elle etait la console.
|
||||
_hostname_par_hote_groupe: dict = {}
|
||||
for _app in applications.values():
|
||||
_exp = [str(f).strip() for f in (_app.get("expose") or []) if str(f).strip()]
|
||||
_g = str(_app.get("groupe") or "")
|
||||
if _g and len(_exp) == 1:
|
||||
_hostname_par_groupe[_g] = _exp[0]
|
||||
_g, _h = str(_app.get("groupe") or ""), str(_app.get("hote") or "")
|
||||
if _g and _h and len(_exp) == 1:
|
||||
_hostname_par_hote_groupe[(_h, _g)] = _exp[0]
|
||||
|
||||
hostvars: dict[str, dict] = {}
|
||||
groupes: dict[str, list[str]] = {}
|
||||
|
|
@ -759,9 +763,10 @@ def inventaire() -> dict:
|
|||
**(srv.get("variables") or {}),
|
||||
}
|
||||
# Nom public de chaque service porte par cet hote, tel que le plan l'expose.
|
||||
for _groupe_expose in sorted(_groupes_de(nom) & set(_hostname_par_groupe)):
|
||||
hostvars[nom].setdefault(f"{_groupe_expose}_hostname",
|
||||
_hostname_par_groupe[_groupe_expose])
|
||||
for _groupe_expose in sorted(_groupes_de(nom)):
|
||||
_fqdn = _hostname_par_hote_groupe.get((nom, _groupe_expose))
|
||||
if _fqdn:
|
||||
hostvars[nom].setdefault(f"{_groupe_expose}_hostname", _fqdn)
|
||||
for _socle in GROUPE_SOCLE:
|
||||
groupes.setdefault(_socle, []).append(nom)
|
||||
if "serveur_backup_site" in _groupes_de(nom):
|
||||
|
|
|
|||
Loading…
Reference in a new issue