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).
This commit is contained in:
Pierre & Lumière 2026-04-14 18:09:26 +02:00
parent d3b7fc2e22
commit 987e21377b
2 changed files with 171 additions and 0 deletions

93
tools/move_chassis.py Normal file
View File

@ -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()

78
tools/move_esxi_extras.py Normal file
View File

@ -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()