Le moteur reste independant des instances ET des modeles assembles (prives) : il ne contient que le modele public socle ; les modeles assembles sont DECOUVERTS au runtime via SETOPS_MODELES (depot prive de l'operateur), jamais embarques dans le code public. - scripts/instance_creer.py : copie un modele (exemples/modeles/* + SETOPS_MODELES) vers un depot frere ../<nom>, y fixe l'index, et refuse un nom existant, un modele inconnu, ou un index en collision avec une instance federee (verifie AVANT toute copie). .git et hosts.genere.yml non copies. - make instance-creer NOM=.. MODELE=.. [INDEX=N] ; make instance-modeles. - GUI (vue Reseau) : formulaire « Creer une instance depuis un modele » sous la flotte. POST /api/instance-creer ; /api/instances renvoie aussi modeles + index_pris. Ne bascule pas l'active. Corrige : gabarit de voute du labo complete (P18 a echoue en passant l'active sur le labo, dont le vault.yml.example etait reste a 17 cles ; aligne sur 23). Valide : garde-fous de creation testes en isolement (collision refusee avant copie, ecrasement refuse, modele inconnu refuse, aucune pollution) ; node --check ; make verifier rc=0 CONFORME 21/21 (active = labo). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
120 lines
5.1 KiB
Python
120 lines
5.1 KiB
Python
#!/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-<nom>`) 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 ../<nom> depuis <modele>. 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())
|