Add link_qualys_by_ip: lie qualys_assets a servers via IP quand hostname mismatch (cas node3->vdameasxt3)
This commit is contained in:
parent
0dc9b07edd
commit
36c638c8ce
81
tools/link_qualys_by_ip.py
Normal file
81
tools/link_qualys_by_ip.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
"""Lie qualys_assets a servers via IP quand le hostname ne match pas.
|
||||||
|
|
||||||
|
Cas d'usage : Qualys enregistre l'asset sous un display name (ex 'node3') alors
|
||||||
|
que le vrai serveur dans iTop/PatchCenter s'appelle differemment (ex 'vdameasxt3').
|
||||||
|
On retombe sur le matching par IP via server_ips.
|
||||||
|
|
||||||
|
Ne touche QUE les qualys_assets dont server_id est NULL.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tools/link_qualys_by_ip.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")
|
||||||
|
|
||||||
|
# Recupere les qualys_assets sans lien serveur, avec une IP
|
||||||
|
rows = conn.execute(text("""
|
||||||
|
SELECT id, qualys_asset_id, hostname, name, fqdn, ip_address::text as ip
|
||||||
|
FROM qualys_assets
|
||||||
|
WHERE server_id IS NULL
|
||||||
|
AND ip_address IS NOT NULL
|
||||||
|
ORDER BY hostname
|
||||||
|
""")).fetchall()
|
||||||
|
print(f"[INFO] {len(rows)} qualys_assets sans server_id avec une IP")
|
||||||
|
|
||||||
|
stats = {"linked": 0, "no_match": 0, "ambiguous": 0}
|
||||||
|
no_match = []
|
||||||
|
ambiguous = []
|
||||||
|
|
||||||
|
for r in rows:
|
||||||
|
# IP peut etre 'a.b.c.d' ou 'a.b.c.d/32', strip /XX
|
||||||
|
ip = r.ip.split("/")[0]
|
||||||
|
# Cherche server_ips matching cette IP
|
||||||
|
matches = conn.execute(text("""
|
||||||
|
SELECT s.id, s.hostname FROM servers s
|
||||||
|
JOIN server_ips si ON si.server_id = s.id
|
||||||
|
WHERE si.ip_address = CAST(:ip AS inet)
|
||||||
|
"""), {"ip": ip}).fetchall()
|
||||||
|
|
||||||
|
if len(matches) == 0:
|
||||||
|
stats["no_match"] += 1
|
||||||
|
no_match.append((r.hostname or r.name, ip))
|
||||||
|
continue
|
||||||
|
if len(matches) > 1:
|
||||||
|
stats["ambiguous"] += 1
|
||||||
|
ambiguous.append((r.hostname, ip, [m.hostname for m in matches]))
|
||||||
|
continue
|
||||||
|
|
||||||
|
sid = matches[0].id
|
||||||
|
srv_name = matches[0].hostname
|
||||||
|
if args.dry_run:
|
||||||
|
print(f" DRY: {r.hostname or r.name:25s} ({ip}) -> server '{srv_name}' (id={sid})")
|
||||||
|
else:
|
||||||
|
conn.execute(text("UPDATE qualys_assets SET server_id=:sid WHERE id=:qid"),
|
||||||
|
{"sid": sid, "qid": r.id})
|
||||||
|
print(f" OK : {r.hostname or r.name:25s} ({ip}) -> '{srv_name}'")
|
||||||
|
stats["linked"] += 1
|
||||||
|
|
||||||
|
conn.close()
|
||||||
|
print(f"\n[DONE] Lies: {stats['linked']} | Sans match IP: {stats['no_match']} | Ambigus: {stats['ambiguous']}")
|
||||||
|
if ambiguous:
|
||||||
|
print("\n[WARN] Cas ambigus (plusieurs servers ont la meme IP) :")
|
||||||
|
for h, ip, srvs in ambiguous[:10]:
|
||||||
|
print(f" {h} ({ip}) -> {srvs}")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user