#!/usr/bin/env python3 """Inventaire dynamique des machines de l'HEBERGEUR, lu dans `underlay.yml`. ansible-playbook -i scripts/site_inventaire.py playbooks/groupes/serveur_ops_site.yml POURQUOI DYNAMIQUE, ET PAS UN `hosts.yml` GENERE. La regle du depot est qu'un inventaire de tenant est un ARTEFACT GENERE, jamais edite a la main : on edite le plan, puis `make instancier`. Cette regle existe parce qu'un tenant DERIVE tout de son index — le fichier intermediaire est le prix a payer pour rendre la derivation inspectable. Un site ne derive de rien. Sa declaration EST deja sa forme finale : `underlay.yml` dit l'adresse, le nœud, le pont, les services. Materialiser un second fichier qui repete la meme chose ne rendrait rien plus inspectable — ca creerait seulement un artefact de plus a garder en phase, et une occasion de plus de le laisser deriver de sa source. LES GROUPES SONT LES SERVICES. Une machine qui declare `services: [serveur_ops_site]` atterrit dans le groupe `serveur_ops_site`, et `playbooks/groupes/serveur_ops_site.yml` la trouve sans qu'on ait rien a cabler. C'est la meme convention que pour les tenants : ce qui se partage entre un site et un tenant, ce sont les ROLES, pas la forme du plan. """ from __future__ import annotations import argparse import json import sys from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parent)) import underlay as U # noqa: E402 # L'inventaire des tenants passe par ce compte ; le site n'a aucune raison d'en differer. UTILISATEUR_DEFAUT = "ansible" # LE SOCLE S'APPLIQUE AUSSI AU SITE. Une machine de l'hebergeur n'est pas d'une autre # espece : elle veut le meme durcissement SSH, les memes horloges, le meme pare-feu que # n'importe quelle machine de la flotte. L'oublier lui aurait donne un SSH par defaut et # aucune regle de frontiere — la machine la plus puissante du site, la moins protegee. GROUPE_SOCLE = "serveur_debian" def inventaire() -> dict: u = U.charger() if u is None: # Aucun underlay monte : un inventaire VIDE, pas une erreur. Ansible doit pouvoir # etre lance sans site sans que ca ressemble a une panne. return {"_meta": {"hostvars": {}}} # La carte est-elle seulement valide ? Un inventaire tire d'un underlay fautif # produirait des machines plausibles et fausses — le pire des deux mondes. erreurs = [e for e in U.valider(u) if e.startswith("machine")] if erreurs: raise SystemExit("underlay.yml refuse :\n - " + "\n - ".join(erreurs)) par_reseau = {r.get("nom"): r for r in U.reseaux(u)} hostvars: dict[str, dict] = {} groupes: dict[str, list[str]] = {} for m in U.machines(u): nom = m["nom"] r = par_reseau.get(m.get("reseau"), {}) hostvars[nom] = { "ansible_host": str(m["ip"]), "ansible_user": m.get("utilisateur", UTILISATEUR_DEFAUT), # De quoi qu'un role puisse se situer sans redemander la carte. "site_reseau": m.get("reseau"), "site_pont": r.get("pont"), "site_noeud": m.get("noeud"), "proxmox_vmid": m.get("vmid"), # CE QUI DISTINGUE CES MACHINES DE TOUTES LES AUTRES. Un role partage entre # un site et un tenant peut avoir besoin de savoir de quel cote il tourne — # par exemple pour ne PAS chercher un plan la ou il n'y en a pas. "setops_site": True, } groupes.setdefault(GROUPE_SOCLE, []).append(nom) for service in m.get("services") or []: groupes.setdefault(service, []).append(nom) out: dict = {g: {"hosts": h} for g, h in groupes.items()} out["site"] = {"hosts": sorted(hostvars)} out["_meta"] = {"hostvars": hostvars} return out def main() -> int: ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--list", action="store_true") ap.add_argument("--host") a = ap.parse_args() if a.host: print(json.dumps(inventaire().get("_meta", {}).get("hostvars", {}).get(a.host, {}))) else: print(json.dumps(inventaire(), indent=2, sort_keys=True)) return 0 if __name__ == "__main__": raise SystemExit(main())