From 987e21377ba15272fe3a65f1e4283c04b839ba0a Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Tue, 14 Apr 2026 18:09:26 +0200 Subject: [PATCH] Add move_esxi_extras + move_chassis scripts move_esxi_extras: identifie ESXi par description (patch_owner_details contient ESXi/hebergeant/hyperviseur) pour les PDP BAC_* et autres hyperviseurs non presents dans le CSV Hyperviseur iTop. Deplace vers hypervisors (kind=hypervisor). move_chassis: deplace les chassis (CPEM*) vers une table chassis dediee (non patchables, pas d'agent Qualys). --- tools/move_chassis.py | 93 +++++++++++++++++++++++++++++++++++++++ tools/move_esxi_extras.py | 78 ++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 tools/move_chassis.py create mode 100644 tools/move_esxi_extras.py diff --git a/tools/move_chassis.py b/tools/move_chassis.py new file mode 100644 index 0000000..eff9da4 --- /dev/null +++ b/tools/move_chassis.py @@ -0,0 +1,93 @@ +"""Move chassis (enclosures hardware type CPEM*) vers table chassis dediee. + +Les chassis ne sont pas des OS patchables (pas d'agent Qualys, pas de SSH). +On les sort de servers pour garder servers propre, mais on garde la tracabilite CMDB. + +Usage: + python tools/move_chassis.py [--dry-run] [--pattern CPEM%] +""" +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("--dry-run", action="store_true") + parser.add_argument("--pattern", default="CPEM%", + help="ILIKE pattern pour hostname chassis (default: CPEM%%)") + args = parser.parse_args() + + engine = create_engine(DATABASE_URL) + print(f"[INFO] DB: {DATABASE_URL.split('@')[-1]}") + print(f"[INFO] Pattern: {args.pattern}") + + conn = engine.connect().execution_options(isolation_level="AUTOCOMMIT") + + conn.execute(text(""" + CREATE TABLE IF NOT EXISTS chassis ( + id SERIAL PRIMARY KEY, + hostname citext UNIQUE NOT NULL, + fqdn varchar(255), + site varchar(100), + domain_ltd varchar(50), + description text, + responsable_nom text, + moved_from_server_id integer, + created_at timestamptz DEFAULT now() + ) + """)) + + rows = conn.execute(text( + "SELECT id, hostname, fqdn, site, domain_ltd, patch_owner_details, responsable_nom " + "FROM servers WHERE hostname ILIKE :p " + " OR patch_owner_details ILIKE '%chassis%' " + " OR patch_owner_details ILIKE '%enclosure%'" + ), {"p": args.pattern}).fetchall() + print(f"[INFO] {len(rows)} candidats chassis trouves") + + moved = 0 + skipped = 0 + for r in rows: + if args.dry_run: + print(f" DRY: {r.hostname} -- {(r.patch_owner_details or '')[:80]}") + continue + try: + conn.execute(text(""" + INSERT INTO chassis + (hostname, fqdn, site, domain_ltd, description, responsable_nom, moved_from_server_id) + VALUES (:h, :fqdn, :site, :domain, :desc, :resp, :sid) + ON CONFLICT (hostname) DO UPDATE SET + description=EXCLUDED.description, + moved_from_server_id=EXCLUDED.moved_from_server_id + """), { + "h": r.hostname, "fqdn": r.fqdn, "site": r.site, + "domain": r.domain_ltd, "desc": r.patch_owner_details, + "resp": r.responsable_nom, "sid": r.id, + }) + 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": r.id}) + except Exception: + pass + conn.execute(text("DELETE FROM servers WHERE id=:sid"), {"sid": r.id}) + moved += 1 + except Exception as e: + print(f" [ERR] {r.hostname}: {str(e)[:150]}") + skipped += 1 + + conn.close() + print(f"[DONE] Deplaces: {moved} | Ignores: {skipped}") + + +if __name__ == "__main__": + main() diff --git a/tools/move_esxi_extras.py b/tools/move_esxi_extras.py new file mode 100644 index 0000000..a25d8cf --- /dev/null +++ b/tools/move_esxi_extras.py @@ -0,0 +1,78 @@ +"""Move ESXi/Hyperviseurs identifiés par leur description (pas dans le CSV Hyperviseur iTop). + +Cherche les serveurs dont la description mentionne 'ESXi' ou 'hebergeant' +et les déplace vers la table hypervisors. + +Usage: + python tools/move_esxi_extras.py [--dry-run] +""" +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("--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") + + rows = conn.execute(text(""" + SELECT id, hostname, fqdn, site, domain_ltd, patch_owner_details, responsable_nom + FROM servers + WHERE patch_owner_details ILIKE '%ESXi%' + OR patch_owner_details ILIKE '%hebergeant%' + OR patch_owner_details ILIKE '%hyperviseur%' + """)).fetchall() + + print(f"[INFO] {len(rows)} candidats trouves") + + moved = 0 + skipped = 0 + for r in rows: + if args.dry_run: + print(f" DRY: {r.hostname} -- {(r.patch_owner_details or '')[:80]}") + continue + try: + conn.execute(text(""" + INSERT INTO hypervisors + (hostname, fqdn, site, domain_ltd, description, responsable_nom, moved_from_server_id, kind) + VALUES (:h, :fqdn, :site, :domain, :desc, :resp, :sid, 'hypervisor') + ON CONFLICT (hostname) DO UPDATE SET + description=EXCLUDED.description, + moved_from_server_id=EXCLUDED.moved_from_server_id + """), { + "h": r.hostname, "fqdn": r.fqdn, "site": r.site, + "domain": r.domain_ltd, "desc": r.patch_owner_details, + "resp": r.responsable_nom, "sid": r.id, + }) + 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": r.id}) + except Exception: + pass + conn.execute(text("DELETE FROM servers WHERE id=:sid"), {"sid": r.id}) + moved += 1 + except Exception as e: + print(f" [ERR] {r.hostname}: {str(e)[:150]}") + skipped += 1 + + conn.close() + print(f"[DONE] Deplaces: {moved} | Ignores: {skipped}") + + +if __name__ == "__main__": + main()