111 lines
3.8 KiB
Python
111 lines
3.8 KiB
Python
"""Enrichit servers avec les infos Qualys manquantes.
|
|
|
|
Pour chaque server matche a un qualys_asset (via server_id ou hostname), comble:
|
|
- fqdn si vide
|
|
- os_family si vide (linux/windows)
|
|
- os_version si vide (qa.os)
|
|
- domain_ltd si vide (extrait du fqdn)
|
|
|
|
Usage:
|
|
python tools/enrich_servers_from_qualys.py [--dry-run] [--overwrite]
|
|
"""
|
|
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 infer_os_family(os_text):
|
|
if not os_text:
|
|
return None
|
|
low = os_text.lower()
|
|
if any(k in low for k in ("linux", "red hat", "centos", "debian", "ubuntu",
|
|
"oracle linux", "suse", "rocky", "alma")):
|
|
return "linux"
|
|
if "windows" in low:
|
|
return "windows"
|
|
return None
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--dry-run", action="store_true")
|
|
parser.add_argument("--overwrite", action="store_true",
|
|
help="Ecrase les valeurs existantes (defaut: ne remplit que les vides)")
|
|
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 s.id as sid, s.hostname,
|
|
s.fqdn as s_fqdn, s.os_family as s_osf, s.os_version as s_osv,
|
|
s.domain_ltd as s_dom,
|
|
qa.fqdn as q_fqdn, qa.os as q_os, qa.os_family as q_osf
|
|
FROM servers s
|
|
JOIN qualys_assets qa ON (qa.server_id = s.id
|
|
OR LOWER(qa.hostname) = LOWER(s.hostname))
|
|
""")).fetchall()
|
|
print(f"[INFO] {len(rows)} matches servers<->qualys_assets")
|
|
|
|
stats = {"updated": 0, "unchanged": 0}
|
|
field_counts = {"fqdn": 0, "os_family": 0, "os_version": 0, "domain_ltd": 0}
|
|
|
|
for r in rows:
|
|
updates = {}
|
|
|
|
# FQDN
|
|
new_fqdn = r.q_fqdn.strip() if r.q_fqdn else None
|
|
if new_fqdn and (args.overwrite or not (r.s_fqdn or "").strip()):
|
|
if new_fqdn != r.s_fqdn:
|
|
updates["fqdn"] = new_fqdn[:255]
|
|
|
|
# OS family
|
|
osf = r.q_osf or infer_os_family(r.q_os)
|
|
if osf and (args.overwrite or not (r.s_osf or "").strip()):
|
|
if osf != r.s_osf:
|
|
updates["os_family"] = osf
|
|
|
|
# OS version (texte)
|
|
if r.q_os and (args.overwrite or not (r.s_osv or "").strip()):
|
|
if r.q_os != r.s_osv:
|
|
updates["os_version"] = r.q_os[:200]
|
|
|
|
# Domain_ltd depuis FQDN
|
|
fqdn_source = new_fqdn or r.s_fqdn
|
|
if fqdn_source and "." in fqdn_source:
|
|
parts = fqdn_source.split(".", 1)
|
|
if len(parts) == 2 and parts[1]:
|
|
new_dom = parts[1].lower().strip()[:50]
|
|
if new_dom and (args.overwrite or not (r.s_dom or "").strip()):
|
|
if new_dom != r.s_dom:
|
|
updates["domain_ltd"] = new_dom
|
|
|
|
if not updates:
|
|
stats["unchanged"] += 1
|
|
continue
|
|
for k in updates:
|
|
field_counts[k] += 1
|
|
|
|
if args.dry_run:
|
|
print(f" DRY: {r.hostname:25s} {updates}")
|
|
else:
|
|
sets = ", ".join(f"{k}=:{k}" for k in updates)
|
|
params = dict(updates); params["sid"] = r.sid
|
|
conn.execute(text(f"UPDATE servers SET {sets} WHERE id=:sid"), params)
|
|
stats["updated"] += 1
|
|
|
|
conn.close()
|
|
print(f"\n[DONE] Maj: {stats['updated']} | Inchanges: {stats['unchanged']}")
|
|
print("Par champ:")
|
|
for k, n in field_counts.items():
|
|
if n:
|
|
print(f" {k:15s} {n}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|