patchcenter/tools/link_prod_nonprod.py

98 lines
3.3 KiB
Python

"""Lie les serveurs non-prod (r/t/d) a leur equivalent prod (p/i) par convention SANEF.
Regle : v[r|t|d]XXXX matche v[p|i]XXXX avec XXXX identique.
Ex: vrpaians1 <-> vppaians1
Ecrit dans server_correspondance (source='auto').
Usage:
python tools/link_prod_nonprod.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"
NONPROD_PREFIXES = {"r": "recette", "t": "test", "d": "dev"}
PROD_PREFIXES = ["p", "i"] # p=prod, i=integration (prod chez SANEF)
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")
# Tous les serveurs non-prod (2eme lettre r/t/d)
rows = conn.execute(text("""
SELECT id, hostname FROM servers
WHERE LENGTH(hostname) >= 3
AND LOWER(SUBSTRING(hostname, 1, 1)) = 'v'
AND LOWER(SUBSTRING(hostname, 2, 1)) IN ('r','t','d')
ORDER BY hostname
""")).fetchall()
print(f"[INFO] {len(rows)} serveurs non-prod (v[rtd]*)")
# Cache hostname -> id pour lookup rapide
all_servers = {}
for r in conn.execute(text("SELECT id, LOWER(hostname) as h FROM servers")).fetchall():
all_servers[r.h] = r.id
stats = {"linked": 0, "already_linked": 0, "no_prod_match": 0}
no_match = []
for r in rows:
h = r.hostname.lower()
env_char = h[1] # r/t/d
rest = h[2:] # reste apres v + r/t/d
prod_id = None
prod_hostname = None
for pchar in PROD_PREFIXES:
candidate = f"v{pchar}{rest}"
if candidate in all_servers:
prod_id = all_servers[candidate]
prod_hostname = candidate
break
if not prod_id:
no_match.append(r.hostname)
stats["no_prod_match"] += 1
continue
# Existe deja ?
existing = conn.execute(text(
"SELECT id FROM server_correspondance WHERE prod_server_id=:p AND nonprod_server_id=:n"
), {"p": prod_id, "n": r.id}).fetchone()
if existing:
stats["already_linked"] += 1
continue
env_name = NONPROD_PREFIXES.get(env_char, env_char)
if args.dry_run:
print(f" DRY: {r.hostname} ({env_name}) <-> {prod_hostname} (prod)")
else:
conn.execute(text("""
INSERT INTO server_correspondance
(prod_server_id, nonprod_server_id, environment_code, source, note)
VALUES (:p, :n, :env, 'auto', 'Auto-link convention v[rtd]XXX -> v[pi]XXX')
"""), {"p": prod_id, "n": r.id, "env": env_name})
stats["linked"] += 1
conn.close()
print(f"\n[DONE] Liens: {stats['linked']} | Deja lies: {stats['already_linked']} "
f"| Sans match prod: {stats['no_prod_match']}")
if no_match and len(no_match) < 50:
print(f"\n[INFO] Non-prod sans equivalent prod ({len(no_match)}):")
for h in no_match[:30]:
print(f" {h}")
if __name__ == "__main__":
main()