"""Ajoute colonne environnement sur servers et importe depuis CSV iTop. Stocke les 7 valeurs iTop verbatim: Développement, Intégration, Pré-Prod, Production, Recette, Test, Formation. Usage: python tools/import_environnement.py [ ...] [--dry-run] """ import os import csv import argparse from sqlalchemy import create_engine, text DATABASE_URL = os.getenv("DATABASE_URL_DEMO") or os.getenv("DATABASE_URL") \ or "postgresql://patchcenter:PatchCenter2026!@localhost:5432/patchcenter_demo" ITOP_ENVS = [ "Développement", "Intégration", "Pré-Prod", "Production", "Recette", "Test", "Formation", ] # Aliases courants -> valeur iTop canonique ENV_ALIASES = { "developpement": "Développement", "dev": "Développement", "integration": "Intégration", "int": "Intégration", "preprod": "Pré-Prod", "pre-prod": "Pré-Prod", "pre prod": "Pré-Prod", "prod": "Production", "production": "Production", "recette": "Recette", "rec": "Recette", "test": "Test", "tests": "Test", "formation": "Formation", } def norm_env(raw): if not raw: return None s = raw.strip() if not s or s in ("-", "(null)", "workgroup"): return None if s in ITOP_ENVS: return s return ENV_ALIASES.get(s.lower()) def main(): parser = argparse.ArgumentParser() parser.add_argument("csv_paths", nargs="+") parser.add_argument("--dry-run", action="store_true") args = parser.parse_args() engine = create_engine(DATABASE_URL) print(f"[INFO] DB: {DATABASE_URL.split('@')[-1]}") conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT") # 1. Ajoute colonne + CHECK (idempotent, toujours execute y compris dry-run) print("[INFO] Ajoute colonne environnement...") conn.execute(text("ALTER TABLE servers ADD COLUMN IF NOT EXISTS environnement varchar(20)")) conn.execute(text("ALTER TABLE servers DROP CONSTRAINT IF EXISTS servers_environnement_check")) allowed_sql = ", ".join(f"'{v}'" for v in ITOP_ENVS) conn.execute(text( f"ALTER TABLE servers ADD CONSTRAINT servers_environnement_check " f"CHECK (environnement IN ({allowed_sql}) OR environnement IS NULL)" )) print(f"[INFO] Envs iTop autorises: {ITOP_ENVS}") # 2. Relecture CSV -> update updated = 0 unchanged = 0 not_found = 0 unknown = set() for csv_path in args.csv_paths: print(f"\n[INFO] Lecture {csv_path}") with open(csv_path, "r", encoding="utf-8-sig", newline="") as f: sample = f.read(4096) f.seek(0) delim = ";" if sample.count(";") > sample.count(",") else "," reader = csv.DictReader(f, delimiter=delim) rows = list(reader) print(f"[INFO] {len(rows)} lignes (delim={delim!r})") for r in rows: hostname = (r.get("Nom") or r.get("Hostname") or "").strip() if not hostname or not any(c.isalpha() for c in hostname): continue raw = (r.get("Environnement") or r.get("Environment") or "").strip() new_env = norm_env(raw) if raw and new_env is None and raw not in ("-", "(null)", "workgroup", ""): unknown.add(raw) continue if new_env is None: continue srv = conn.execute(text("SELECT id, environnement FROM servers WHERE hostname=:h"), {"h": hostname}).fetchone() if not srv: not_found += 1 continue if srv.environnement == new_env: unchanged += 1 continue if args.dry_run: print(f" DRY: {hostname} {srv.environnement} -> {new_env}") else: conn.execute(text("UPDATE servers SET environnement=:e WHERE id=:sid"), {"e": new_env, "sid": srv.id}) updated += 1 conn.close() print(f"\n[DONE] Maj: {updated} | Inchanges: {unchanged} | Hors base: {not_found}") if unknown: print(f"[WARN] Valeurs non mappees: {unknown}") if __name__ == "__main__": main()