#!/usr/bin/env python3 """Registre des serveurs (VM) du plan Set-OPS. LE PLAN EST L'AUTORITE, ET CE N'EST PLUS L'INVERSE (corrige le 2026-09-06). Ce module a longtemps annonce « Phase 2 : l'inventaire reste AUTORITE [...] la generation depuis le plan viendra en Phase 3 ». La Phase 3 est faite depuis longtemps : `make instancier` genere `hosts.yml` DEPUIS ce registre, et la REGLE D'OR d'AGENTS.md interdit d'editer l'inventaire a la main. Lire l'ancienne phrase aujourd'hui, c'est croire l'exact contraire de la doctrine. `bootstrap` reste utile, mais c'est une manoeuvre de REPRISE, pas le flux normal : il (re)constitue `plan/serveurs.yml` depuis un inventaire existant — le geste qu'on fait une fois, quand on herite d'un ecosysteme dont le plan n'existe pas encore. """ from __future__ import annotations import os import argparse import sys from pathlib import Path import yaml from inventory_gui import charger_yaml, liste_hotes from inventory_rules import ( charger_nomenclature, charger_serveurs, fonction_seq, integrations_universelles, instance_courante, inventaire_de, reconcilier_serveur, valider_serveurs, ) RACINE = Path(__file__).resolve().parents[1] INSTANCE = instance_courante() FICHIER = INSTANCE / "plan/serveurs.yml" FICHIER_NOMENCLATURE = INSTANCE / "plan/nomenclature.yml" INVENTAIRE = inventaire_de(INSTANCE) # Champs de placement / dimensionnement NON derivables (proviennent du plan). CHAMPS_PLAN = [("noeud", "noeud"), ("stockage", "stockage"), ("disque_taille", "disque"), ("memoire", "memoire"), ("coeurs", "coeurs")] def ecrire(registre: dict) -> None: entete = ( "# Registre des serveurs (VM) du plan Set-OPS.\n" "# CE FICHIER EST L'AUTORITE : l'inventaire en est GENERE (make instancier).\n" "# Ne jamais editer instance/inventories/*/hosts.yml a la main.\n" "# Reconstitue ici depuis un inventaire existant par `make serveurs-bootstrap` —\n" "# manoeuvre de reprise, pas le flux normal.\n" "# VMID/IP/VLAN/passerelle sont DERIVES de la fonction via\n" "# instance/plan/nomenclature.yml (jamais stockes ici).\n" "---\n" ) with FICHIER.open("w", encoding="utf-8") as fichier: fichier.write(entete) yaml.safe_dump({"serveurs": registre.get("serveurs", {}) or {}}, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True) def hotes_inventaire() -> dict: return {h["nom"]: h for h in liste_hotes(charger_yaml(INVENTAIRE)) if h.get("nom")} def bootstrap() -> dict: serveurs = {} # Les universelles ne se recopient pas dans le plan (D-33) : les reinscrire ici # reconstituerait, a chaque bootstrap, les lignes qu'on vient de retirer. universelles = set(integrations_universelles()) for nom, h in hotes_inventaire().items(): fonction, _ = fonction_seq(nom) srv = {"fonction": fonction, "etat": h.get("etat", "planifie")} for cle_src, cle_dst in CHAMPS_PLAN: v = str(h.get(cle_src, "")).strip() if v: srv[cle_dst] = int(v) if v.isdigit() else v integrations = sorted(g for g in (h.get("groupes") or []) if g.startswith("client_") and g not in universelles) if integrations: srv["integrations"] = integrations serveurs[nom] = srv return {"serveurs": serveurs} def lister() -> None: registre = charger_serveurs(FICHIER) serveurs = registre.get("serveurs", {}) if not serveurs: print("Aucun serveur. Lance: make serveurs-bootstrap") return nomenclature = charger_nomenclature(FICHIER_NOMENCLATURE) hotes = hotes_inventaire() for nom, srv in serveurs.items(): rec = reconcilier_serveur(nom, srv, hotes.get(nom), nomenclature) d = rec["derive"] if rec["absent_inventaire"]: statut = "ABSENT de l'inventaire" elif rec["divergences"]: statut = "DIVERGENCE" else: statut = "reconcilie" print(f"{nom} [fonction {srv.get('fonction')}, etat {srv.get('etat', '?')}]" f" derive: vmid {d.get('vmid', '?')}, ip {d.get('adresse_ip', '?')}, vlan {d.get('vlan', '?')} -> {statut}") for dv in rec["divergences"]: print(f" ! {dv}") def verifier() -> int: registre = charger_serveurs(FICHIER) nomenclature = charger_nomenclature(FICHIER_NOMENCLATURE) valider_serveurs(registre, nomenclature) hotes = hotes_inventaire() divergents = [] for nom, srv in registre.get("serveurs", {}).items(): rec = reconcilier_serveur(nom, srv, hotes.get(nom), nomenclature) if rec["divergences"] or rec["absent_inventaire"]: divergents.append(nom) if divergents: print(f"Registre valide, mais {len(divergents)} serveur(s) non reconcilie(s): {', '.join(divergents)}") else: print("Registre des serveurs valide et reconcilie avec l'inventaire.") return 0 def main() -> int: parser = argparse.ArgumentParser(description="Registre des serveurs (VM) du plan Set-OPS.") sub = parser.add_subparsers(dest="commande", required=True) sub.add_parser("lister", help="Affiche les serveurs et leur reconciliation avec l'inventaire.") sub.add_parser("verifier", help="Valide le registre et signale les divergences.") sub.add_parser("bootstrap", help="(Re)genere instance/plan/serveurs.yml depuis l'inventaire actuel.") args = parser.parse_args() try: if args.commande == "lister": lister() elif args.commande == "verifier": return verifier() elif args.commande == "bootstrap": registre = bootstrap() valider_serveurs(registre, charger_nomenclature(FICHIER_NOMENCLATURE)) ecrire(registre) print(f"instance/plan/serveurs.yml genere depuis l'inventaire ({len(registre['serveurs'])} serveurs).") except Exception as exc: print(f"erreur: {exc}", file=sys.stderr) return 2 return 0 if __name__ == "__main__": raise SystemExit(main())