396 lines
14 KiB
Python
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())
|