From 0a00c401d791e7b9379ce0dde8bd5afcb6ddae0b Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Tue, 14 Apr 2026 17:40:24 +0200 Subject: [PATCH] Add hypervisors table + move script (preserves VM->hypervisor link via vcenter_vm_name) --- tools/move_hypervisors.py | 126 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 tools/move_hypervisors.py diff --git a/tools/move_hypervisors.py b/tools/move_hypervisors.py new file mode 100644 index 0000000..5c9fe81 --- /dev/null +++ b/tools/move_hypervisors.py @@ -0,0 +1,126 @@ +"""Move hypervisors from servers table to a dedicated hypervisors table. + +Usage: + python tools/move_hypervisors.py +""" +import csv +import os +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" + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("csv_path", help="Hyperviseur CSV iTop") + args = parser.parse_args() + + engine = create_engine(DATABASE_URL) + print(f"[INFO] DB: {DATABASE_URL.split('@')[-1]}") + print(f"[INFO] CSV: {args.csv_path}") + + with open(args.csv_path, "r", encoding="utf-8-sig", newline="") as f: + reader = csv.DictReader(f) + rows = list(reader) + print(f"[INFO] {len(rows)} lignes CSV") + + conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT") + + # Crée la table si pas existante + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS hypervisors ( + id SERIAL PRIMARY KEY, + hostname citext UNIQUE NOT NULL, + fqdn varchar(255), + cluster_name varchar(100), + underlying_server varchar(100), + site varchar(100), + domain_ltd varchar(50), + description text, + responsable_nom text, + moved_from_server_id integer, + created_at timestamptz DEFAULT now() + ) + """)) + + moved = 0 + not_in_servers = 0 + skipped = 0 + + for r in rows: + hostname = (r.get("Nom") or r.get("Hostname") or "").strip() + if not hostname or hostname in ("(null)",) or not any(c.isalpha() for c in hostname): + skipped += 1 + continue + + srv = conn.execute(text( + "SELECT id, fqdn, site, domain_ltd, patch_owner_details, responsable_nom FROM servers WHERE hostname=:h" + ), {"h": hostname}).fetchone() + + if not srv: + # Pas en base servers, on insère quand même dans hypervisors + try: + conn.execute(text(""" + INSERT INTO hypervisors (hostname, cluster_name, underlying_server, description, responsable_nom) + VALUES (:h, :cl, :srv, :desc, :resp) + ON CONFLICT (hostname) DO NOTHING + """), { + "h": hostname, + "cl": (r.get("vCluster->Nom") or "").strip()[:100] or None, + "srv": (r.get("Serveur->Nom") or "").strip()[:100] or None, + "desc": (r.get("Description") or "").strip() or None, + "resp": None, + }) + not_in_servers += 1 + except Exception as e: + print(f" [ERR insert] {hostname}: {str(e)[:120]}") + skipped += 1 + continue + + try: + # Insert dans hypervisors + conn.execute(text(""" + INSERT INTO hypervisors + (hostname, fqdn, cluster_name, underlying_server, site, domain_ltd, + description, responsable_nom, moved_from_server_id) + VALUES (:h, :fqdn, :cl, :usrv, :site, :domain, :desc, :resp, :sid) + ON CONFLICT (hostname) DO UPDATE SET + cluster_name=EXCLUDED.cluster_name, + underlying_server=EXCLUDED.underlying_server, + moved_from_server_id=EXCLUDED.moved_from_server_id + """), { + "h": hostname, "fqdn": srv.fqdn, + "cl": (r.get("vCluster->Nom") or "").strip()[:100] or None, + "usrv": (r.get("Serveur->Nom") or "").strip()[:100] or None, + "site": srv.site, "domain": srv.domain_ltd, + "desc": (r.get("Description") or srv.patch_owner_details or "").strip()[:1000] or None, + "resp": srv.responsable_nom, + "sid": srv.id, + }) + + # Nettoie les FK puis delete du servers + for tbl, col in [("server_ips", "server_id"), ("qualys_assets", "server_id"), + ("server_audit", "server_id"), ("server_audit_full", "server_id"), + ("server_correspondance", "server_id"), ("cluster_members", "server_id"), + ("patch_history", "server_id"), ("patch_sessions", "server_id"), + ("patch_validation", "server_id"), ("quickwin_entries", "server_id"), + ("quickwin_server_config", "server_id"), ("server_specifics", "server_id"), + ("server_pairs", "server_a_id"), ("server_pairs", "server_b_id")]: + try: + conn.execute(text(f"DELETE FROM {tbl} WHERE {col}=:sid"), {"sid": srv.id}) + except Exception: + pass + conn.execute(text("DELETE FROM servers WHERE id=:sid"), {"sid": srv.id}) + moved += 1 + except Exception as e: + print(f" [ERR move] {hostname}: {str(e)[:150]}") + skipped += 1 + + conn.close() + print(f"[DONE] Deplaces: {moved} | Inseres directement (pas en servers): {not_in_servers} | Ignores: {skipped}") + + +if __name__ == "__main__": + main()