#!/usr/bin/env python3 """Crée un dépôt d'instance frère depuis un modèle (socle ou modèle assemblé). Une instance = un dossier frère du moteur (`../OPS-`) avec un `plan/`. Ce script copie un modèle vers ce dossier, y fixe éventuellement l'`index` (le seed dont dérive tout l'adressage) en refusant une collision avec une instance fédérée existante, et imprime la marche à suivre. Il ne bascule PAS le symlink (geste explicite) ni ne `git init` (au choix de l'opérateur). Modèles disponibles : `exemples/modeles/*` du dépôt + ceux de `SETOPS_MODELES` (cf. scripts/modeles.py). Usage : python3 scripts/instance_creer.py --lister-modeles python3 scripts/instance_creer.py --nom OPS-ClientX --modele socle --index 4 """ from __future__ import annotations import argparse import os import shutil import sys from pathlib import Path RACINE = Path(__file__).resolve().parents[1] FRERES = RACINE.parent sys.path.insert(0, str(RACINE / "scripts")) import instances as _instances # noqa: E402 import modeles as _modeles # noqa: E402 def modeles_disponibles() -> dict[str, Path]: """{nom: chemin} des modèles découverts (dépôt + SETOPS_MODELES).""" return {m.name: m for m in _modeles.decouvrir()} def index_federes_utilises() -> set[int]: """Index déjà pris par des instances FÉDÉRÉES (une collision casserait le trunk).""" return {i["index"] for i in _instances.decouvrir() if i["federe"]} def _fixer_index(nomenclature: Path, index: int) -> None: """Écrit `index: N` dans la nomenclature sans reformater le reste (chirurgical).""" import re texte = nomenclature.read_text(encoding="utf-8") nouveau, n = re.subn(r"(?m)^index:.*$", f"index: {index}", texte) if not n: nouveau = re.sub(r"(?m)^(---\n)", rf"\g<1>index: {index}\n", texte, count=1) or texte nomenclature.write_text(nouveau, encoding="utf-8") def creer(nom: str, modele: str, index: int | None = None) -> Path: """Crée l'instance ../ depuis . Renvoie le chemin créé.""" if not nom or "/" in nom or nom.startswith("."): raise ValueError(f"Nom d'instance invalide : {nom!r} (dossier frère simple attendu, ex. OPS-ClientX).") dispo = modeles_disponibles() if modele not in dispo: raise ValueError(f"Modèle inconnu : {modele!r}. Disponibles : {', '.join(sorted(dispo)) or 'aucun'}.") cible = FRERES / nom if cible.exists(): raise ValueError(f"Existe déjà : {cible} — refus (ne rien écraser).") if index is not None and index in index_federes_utilises(): raise ValueError(f"Index {index} déjà utilisé par une instance fédérée — collision " f"(mêmes VLAN/VMID). Choisir un index libre (cf. make instances).") shutil.copytree(dispo[modele], cible, ignore=shutil.ignore_patterns(".git", "hosts.genere.yml")) if index is not None: nom_yml = cible / "plan" / "nomenclature.yml" if nom_yml.is_file(): _fixer_index(nom_yml, index) return cible def _suite(nom: str, index: int | None) -> None: print(f"\nInstance créée : ../{nom}") print("Prochaines étapes :") if index is None: print(f" 1. Fixer un index UNIQUE dans ../{nom}/plan/nomenclature.yml (cf. make instances).") print(f" 2. Régler l'identité (../{nom}/inventories/*/group_vars/all/10-intrants.yml :") print(" domaine, organisation, realm) et la cible Proxmox (proxmox.yml).") print(f" 3. Créer la voûte depuis le gabarit (…/group_vars/all/vault.yml.example) puis ansible-vault encrypt.") print(f" 4. make instance-utiliser NOM={nom} && make instancier-appliquer FORCE=1") print(f" (Optionnel : cd ../{nom} && git init, pour versionner l'instance.)") def main() -> int: p = argparse.ArgumentParser(description="Crée une instance Set-OPS depuis un modèle.") p.add_argument("--lister-modeles", action="store_true", help="Liste les modèles disponibles.") p.add_argument("--nom", help="Nom du dépôt d'instance frère (ex. OPS-ClientX).") p.add_argument("--modele", help="Modèle de départ (ex. socle, integral).") p.add_argument("--index", type=int, help="Index (seed d'adressage) ; refuse une collision fédérée.") args = p.parse_args() if args.lister_modeles: dispo = modeles_disponibles() if not dispo: print("Aucun modèle découvert.") return 0 pris = index_federes_utilises() for nom in sorted(dispo): print(f" {nom}") print(f"\nIndex fédérés déjà pris : {sorted(pris) or 'aucun'} (choisir un index libre).") return 0 if not args.nom or not args.modele: print("Usage : make instance-creer NOM=OPS-ClientX MODELE=socle [INDEX=N]", file=sys.stderr) print(" (make instance-modeles pour la liste des modèles)", file=sys.stderr) return 2 try: creer(args.nom, args.modele, args.index) except (ValueError, OSError) as exc: print(f"erreur: {exc}", file=sys.stderr) return 2 _suite(args.nom, args.index) return 0 if __name__ == "__main__": raise SystemExit(main())