This repository has been archived on 2026-06-26. You can view files and clone it, but cannot push or open issues or pull requests.
Set-OPS/scripts/inventory_host.py

396 lines
14 KiB
Python

#!/usr/bin/env python3
"""Gere les hotes d'un inventaire Ansible statique Set-OPS."""
from __future__ import annotations
import argparse
from pathlib import Path
import sys
import yaml
from inventory_rules import (
GROUPE_HOTES_ACTIFS,
GROUPE_HOTES_PLANIFIES,
GROUPES_ETAT_HOTE,
charger_dependances,
est_groupe_operationnel,
)
def charger_inventaire(path: Path) -> dict:
if not path.exists():
return {"all": {"children": {}}}
with path.open("r", encoding="utf-8") as inventory_file:
data = yaml.safe_load(inventory_file) or {}
if not isinstance(data, dict):
raise ValueError(f"{path} ne contient pas une table YAML.")
data.setdefault("all", {})
data["all"].setdefault("children", {})
return data
def ecrire_inventaire(path: Path, data: dict) -> None:
with path.open("w", encoding="utf-8") as inventory_file:
yaml.safe_dump(
data,
inventory_file,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
def enfants(data: dict) -> dict:
return data.setdefault("all", {}).setdefault("children", {})
def assurer_groupe(data: dict, group: str) -> dict:
group_data = enfants(data).setdefault(group, {})
group_data.setdefault("hosts", {})
return group_data
def variables_hote(data: dict, host: str) -> dict:
merged: dict = {}
for group_data in enfants(data).values():
hosts = group_data.get("hosts", {})
if host in hosts and isinstance(hosts[host], dict):
merged.update(hosts[host])
return merged
def tous_les_hotes(data: dict) -> list[str]:
hosts: set[str] = set()
for group_data in enfants(data).values():
hosts.update(group_data.get("hosts", {}))
return sorted(hosts)
def groupes_hote(data: dict, host: str) -> list[str]:
groups: list[str] = []
for group, group_data in enfants(data).items():
if host in group_data.get("hosts", {}):
groups.append(group)
return groups
def retirer_hote_de_tous_les_groupes(data: dict, host: str) -> None:
for group_data in enfants(data).values():
group_data.get("hosts", {}).pop(host, None)
def ajouter_hote(
data: dict,
host: str,
groups: list[str],
ip: str | None,
ansible_user: str | None,
vmid: str | None,
) -> None:
vars_for_host = variables_hote(data, host)
if ip:
vars_for_host["ansible_host"] = ip
if ansible_user:
vars_for_host["ansible_user"] = ansible_user
if vmid:
vars_for_host["proxmox_vmid"] = int(vmid) if vmid.isdigit() else vmid
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
if ip:
assurer_groupe(data, GROUPE_HOTES_ACTIFS)["hosts"][host] = dict(vars_for_host)
assurer_groupe(data, GROUPE_HOTES_PLANIFIES)["hosts"].pop(host, None)
else:
assurer_groupe(data, GROUPE_HOTES_PLANIFIES)["hosts"][host] = dict(vars_for_host)
assurer_groupe(data, GROUPE_HOTES_ACTIFS)["hosts"].pop(host, None)
def definir_groupes_hote(data: dict, host: str, groups: list[str]) -> None:
vars_for_host = variables_hote(data, host)
current_groups = groupes_hote(data, host)
if not current_groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
retirer_hote_de_tous_les_groupes(data, host)
for group in current_groups:
if group in GROUPES_ETAT_HOTE:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
for group in groups:
assurer_groupe(data, group)["hosts"][host] = dict(vars_for_host)
def analyser_groupes(raw_groups: str) -> list[str]:
groups = [group.strip() for group in raw_groups.replace(",", " ").split() if group.strip()]
if not groups:
raise ValueError("Au moins un groupe est requis.")
return groups
def afficher_hote(data: dict, host: str) -> None:
groups = groupes_hote(data, host)
if not groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
yaml.safe_dump(
{
"hote": host,
"groupes": groups,
"variables": variables_hote(data, host),
},
sys.stdout,
default_flow_style=False,
sort_keys=False,
allow_unicode=True,
)
def afficher_playbooks_hote(data: dict, host: str, playbook_dir: Path) -> None:
groups = groupes_hote(data, host)
if not groups:
raise ValueError(f"Hote absent de l'inventaire: {host}")
missing_playbooks: list[str] = []
playbooks: list[Path] = []
for group in groups:
if not est_groupe_operationnel(group):
continue
playbook = playbook_dir / f"{group}.yml"
if playbook.exists():
playbooks.append(playbook)
else:
missing_playbooks.append(group)
if missing_playbooks:
missing = ", ".join(missing_playbooks)
raise ValueError(f"Playbook de groupe manquant pour: {missing}")
if not playbooks:
raise ValueError(f"Aucun playbook de groupe opérationnel pour: {host}")
for playbook in playbooks:
print(playbook)
def verifier_hote_actif(data: dict, host: str) -> None:
hosts = enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {})
if host not in hosts:
raise ValueError(f"Hote non actif dans l'inventaire: {host}")
def groupe_a_hote_actif(data: dict, group: str) -> bool:
active_hosts = set(enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {}))
group_hosts = set(enfants(data).get(group, {}).get("hosts", {}))
return bool(active_hosts & group_hosts)
def verifier_dependances_groupes(data: dict, dependencies: dict, groups: list[str]) -> None:
missing: list[str] = []
for group in sorted(set(groups)):
for required_group in dependencies.get(group, {}).get("requiert_groupes_actifs", []):
if not groupe_a_hote_actif(data, required_group):
missing.append(f"{group} requiert {required_group} actif")
if missing:
raise ValueError("Dependances manquantes: " + "; ".join(missing))
def exiger_dependances(dependencies: dict, path: Path | None) -> None:
if not path:
raise ValueError("Fichier de dependances requis: utiliser --dependances.")
if not dependencies:
raise ValueError(f"Aucune dependance chargee depuis {path}.")
def verifier_dependances_hote(data: dict, dependencies: dict, host: str) -> None:
groups = [group for group in groupes_hote(data, host) if est_groupe_operationnel(group)]
verifier_dependances_groupes(data, dependencies, groups)
def verifier_dependances_actives(data: dict, dependencies: dict) -> None:
active_hosts = set(enfants(data).get(GROUPE_HOTES_ACTIFS, {}).get("hosts", {}))
active_groups: set[str] = set()
for group, group_data in enfants(data).items():
if not est_groupe_operationnel(group):
continue
if active_hosts & set(group_data.get("hosts", {})):
active_groups.add(group)
verifier_dependances_groupes(data, dependencies, sorted(active_groups))
def verifier_dependances_connues(data: dict, dependencies: dict, playbook_dir: Path | None) -> None:
for group, config in dependencies.items():
if playbook_dir and not (playbook_dir / f"{group}.yml").exists():
raise ValueError(f"Playbook de groupe manquant pour dependance: {group}")
for required_group in config.get("requiert_groupes_actifs", []):
if playbook_dir and not (playbook_dir / f"{required_group}.yml").exists():
raise ValueError(f"Playbook de groupe manquant pour prerequis: {required_group}")
def verifier_coherence_hotes(data: dict) -> None:
vmids: dict[str, str] = {}
for host in tous_les_hotes(data):
groups = groupes_hote(data, host)
state_groups = [group for group in groups if group in GROUPES_ETAT_HOTE]
operational_groups = [group for group in groups if est_groupe_operationnel(group)]
vars_for_host = variables_hote(data, host)
if not state_groups and not operational_groups:
continue
if len(state_groups) != 1:
raise ValueError(
f"{host} doit appartenir exactement a un groupe d'etat: "
f"{GROUPE_HOTES_ACTIFS} ou {GROUPE_HOTES_PLANIFIES}"
)
if not operational_groups:
raise ValueError(f"{host} doit appartenir a au moins un groupe operationnel.")
if GROUPE_HOTES_ACTIFS in state_groups and not vars_for_host.get("ansible_host"):
raise ValueError(f"{host} est actif mais n'a pas ansible_host.")
vmid = vars_for_host.get("proxmox_vmid")
if vmid in (None, ""):
continue
vmid_key = str(vmid)
if vmid_key in vmids and vmids[vmid_key] != host:
raise ValueError(f"VMID en double: {vmid_key} pour {vmids[vmid_key]} et {host}")
vmids[vmid_key] = host
def verifier_playbooks_groupes(data: dict, playbook_dir: Path) -> None:
missing_playbooks: list[str] = []
for group, group_data in enfants(data).items():
hosts = group_data.get("hosts", {})
if not hosts:
continue
if not est_groupe_operationnel(group):
continue
playbook = playbook_dir / f"{group}.yml"
if not playbook.exists():
missing_playbooks.append(group)
if missing_playbooks:
missing = ", ".join(sorted(missing_playbooks))
raise ValueError(f"Playbook de groupe manquant pour: {missing}")
verifier_coherence_hotes(data)
def main() -> int:
parser = argparse.ArgumentParser(description="Gere les hotes d'un inventaire Ansible statique Set-OPS.")
parser.add_argument("--inventaire", required=True, type=Path)
parser.add_argument("--dependances", type=Path)
subparsers = parser.add_subparsers(dest="command", required=True)
add_parser = subparsers.add_parser("ajouter", help="Ajoute ou met a jour un hote dans un ou plusieurs groupes.")
add_parser.add_argument("--hote", required=True)
add_parser.add_argument("--groupes", required=True)
add_parser.add_argument("--adresse-ip")
add_parser.add_argument("--utilisateur-ansible")
add_parser.add_argument("--vmid")
add_parser.add_argument("--dossier-playbooks", type=Path)
groups_parser = subparsers.add_parser("definir-groupes", help="Remplace l'appartenance aux groupes pour un hote.")
groups_parser.add_argument("--hote", required=True)
groups_parser.add_argument("--groupes", required=True)
groups_parser.add_argument("--dossier-playbooks", type=Path)
show_parser = subparsers.add_parser("afficher", help="Affiche les groupes et variables d'un hote.")
show_parser.add_argument("--hote", required=True)
playbooks_parser = subparsers.add_parser("playbooks", help="Affiche les playbooks de groupes pour un hote.")
playbooks_parser.add_argument("--hote", required=True)
playbooks_parser.add_argument("--dossier-playbooks", required=True, type=Path)
active_parser = subparsers.add_parser("verifier-actif", help="Verifie qu'un hote est actif.")
active_parser.add_argument("--hote", required=True)
host_dependencies_parser = subparsers.add_parser(
"verifier-dependances-hote",
help="Verifie les prerequis actifs requis par les groupes d'un hote.",
)
host_dependencies_parser.add_argument("--hote", required=True)
group_dependencies_parser = subparsers.add_parser(
"verifier-dependances-groupe",
help="Verifie les prerequis actifs requis par un groupe.",
)
group_dependencies_parser.add_argument("--groupe", required=True)
verify_parser = subparsers.add_parser(
"verifier-playbooks",
help="Verifie que les groupes operationnels avec hotes ont un playbook homonyme.",
)
verify_parser.add_argument("--dossier-playbooks", required=True, type=Path)
dependencies_parser = subparsers.add_parser(
"verifier-dependances",
help="Verifie le registre de dependances et les prerequis des groupes actifs.",
)
dependencies_parser.add_argument("--dossier-playbooks", required=True, type=Path)
args = parser.parse_args()
try:
data = charger_inventaire(args.inventaire)
dependencies = charger_dependances(args.dependances)
if args.command == "ajouter":
ajouter_hote(
data,
args.hote,
analyser_groupes(args.groupes),
args.adresse_ip,
args.utilisateur_ansible,
args.vmid,
)
if args.dossier_playbooks:
verifier_playbooks_groupes(data, args.dossier_playbooks)
if args.dependances:
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
verifier_dependances_actives(data, dependencies)
ecrire_inventaire(args.inventaire, data)
elif args.command == "definir-groupes":
definir_groupes_hote(data, args.hote, analyser_groupes(args.groupes))
if args.dossier_playbooks:
verifier_playbooks_groupes(data, args.dossier_playbooks)
if args.dependances:
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
verifier_dependances_actives(data, dependencies)
ecrire_inventaire(args.inventaire, data)
elif args.command == "afficher":
afficher_hote(data, args.hote)
elif args.command == "playbooks":
afficher_playbooks_hote(data, args.hote, args.dossier_playbooks)
elif args.command == "verifier-actif":
verifier_hote_actif(data, args.hote)
elif args.command == "verifier-dependances-hote":
exiger_dependances(dependencies, args.dependances)
verifier_dependances_hote(data, dependencies, args.hote)
elif args.command == "verifier-dependances-groupe":
exiger_dependances(dependencies, args.dependances)
verifier_dependances_groupes(data, dependencies, [args.groupe])
elif args.command == "verifier-playbooks":
verifier_playbooks_groupes(data, args.dossier_playbooks)
elif args.command == "verifier-dependances":
exiger_dependances(dependencies, args.dependances)
verifier_dependances_connues(data, dependencies, args.dossier_playbooks)
verifier_dependances_actives(data, dependencies)
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())