From 3d4a3e29ad41c322e114ccf028bea2ddcba23110 Mon Sep 17 00:00:00 2001 From: huesser Date: Fri, 12 Jun 2026 14:49:11 +0200 Subject: [PATCH] Simplified everything --- docker-compose.yml | 4 +- logic/scripts/ad_lookup.py | 153 +++++++++++++++++++------------------ 2 files changed, 81 insertions(+), 76 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 79cbcbb..3f821f1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -14,7 +14,9 @@ services: # Beginning of name of the downloaded archivebroup information file - BEGIN_NAME_GROUPINFO_JSON_FILE=size_by_ownergroup_and_number_of_copies_ - ERROR_LOGFILE=error.log - # To connect with AD + # AD variables + - AD_SERVER=d.psi.ch + - AD_SEARCH_BASE=DC=d,DC=psi,DC=ch - AD_USER=${AD_USER} - AD_PASSWORD=${AD_PASSWORD} ports: diff --git a/logic/scripts/ad_lookup.py b/logic/scripts/ad_lookup.py index 7da2690..ee7f88f 100644 --- a/logic/scripts/ad_lookup.py +++ b/logic/scripts/ad_lookup.py @@ -1,16 +1,22 @@ """ Dieses Skript liest eine JSON-Datei mit Gruppeninformationen ein und fragt für jede Gruppe -das zugehörige Department im Active Directory (AD) ab. Department ist in Form der entsprechenden -Zahl (z.B. 6000 fuer CPS) angegeben. +das zugehörige Department im Active Directory (AD) ab. Das Department ist in Form der entsprechenden +Zahl (z.B. 6000 für CPS) angegeben. -Es unterscheidet dabei zwischen zwei Hauptfällen: +Die JSON-Datei ist eine Liste von Dictionaries, welche folgende Form haben: + +{"ownerGroup":"p18973","copies":"oneCopyBig","size":"6.85468397803E11","packedSize":"6.8563388416E11"} + +Es werden zwei Hauptfälle unterscheidet: 1. a-Gruppen (z.B. a-groupname): Diese Gruppen enthalten direkt das Attribut 'department'. 2. p-Gruppen (z.B. p12345): Diese Gruppen sind jeweils einer Beamline zugeordnet. Jede - Beamline besitzt ein 'department' Attribut. Das Skripte ermittelt zuerst die Beamline - und danach das Department. Die Zuordnung Beamline -> Department wird gecached, was - AD Anfragen spart. + Beamline besitzt ein 'department'-Attribut. Das Skript ermittelt zuerst die Beamline + und danach das Department. Die Zuordnung von Beamline zu Department wird gecached, + um AD-Anfragen zu minimieren, da es viele p-Gruppen aber nur wenige Beamlines gibt. -Die Ergebnisse (oder Fehlermeldungen für nicht zuordenbare Gruppen) werden ausgegeben. +Am Schluss wird eine Liste der eingelesenen Dictionaries ausgegeben, die allerdings durch +einen 'department' Eintrag ergänzt wurden. Bei den p-Gruppen wird zusätzlich noch ein 'beamline' +Eintrag hinzugefügt. """ import json import os @@ -21,42 +27,45 @@ from ldap3 import ALL, Connection, Server # --- Konfiguration --- AD_SERVER = 'd.psi.ch' +AD_SERVER = os.environ.get('AD_SERVER') AD_USER = os.environ.get('AD_USER') AD_PASSWORD = os.environ.get('AD_PASSWORD') AD_SEARCH_BASE = 'DC=d,DC=psi,DC=ch' +AD_SEARCH_BASE = os.environ.get('AD_SEARCH_BASE') FILE_PATH = os.environ.get('GROUPINFO_JSON_LNK_NAME_WITH_PATH') # FILE_PATH = "/data/" + os.environ.get('GROUPINFO_JSON_LNK_NAME') def lookup_ad(): try: - # 1. Datenbasis aus lokaler JSON-Datei laden + # Datenbasis aus der via Umgebungsvariable definierten JSON-Datei laden if FILE_PATH is None: - print("ERROR: Environment variable GROUPINFO_JSON_LNK_NAME_WITH_PATH is not set.", file=sys.stderr, flush=True) + print("ERROR: Environment variable GROUPINFO_JSON_LNK_NAME_WITH_PATH is not set.", + file=sys.stderr, flush=True) return if not os.path.exists(FILE_PATH): - print(f"ERROR: Datei {FILE_PATH} nicht gefunden!", file=sys.stderr, flush=True) + print(f"ERROR: File {FILE_PATH} not found!", file=sys.stderr, flush=True) return with open(FILE_PATH, 'r') as f: items = json.load(f) - print(f"INFO: {len(items)} Einträge geladen.", file=sys.stderr, flush=True) + print(f"INFO: {len(items)} entries loaded.", file=sys.stderr, flush=True) - # 2. AD-Verbindung initialisieren (SSL, Port 636) + # AD-Verbindung via SSL (Port 636) initialisieren server = Server(AD_SERVER, port=636, use_ssl=True, get_info=ALL, connect_timeout=10) with Connection(server, user=AD_USER, password=AD_PASSWORD, auto_bind=True) as conn: - # Interner Cache zur Speicherung der Zuordnung von p-Gruppe zu Beamline - beamline2department_cache = {} + # Cache für die Zuordnung von Beamline zu Department + beamline_department_cache = {} count = 0 for item in items: count += 1 group_name = item.get('ownerGroup', '') - # --- Fall A: Direkte a-Gruppen --- + # --- Fall A: direkte a-Gruppen --- if group_name.startswith('a-'): search_filter = f'(&(objectClass=group)(cn={group_name}))' conn.search( @@ -71,86 +80,80 @@ def lookup_ad(): else: item['department'] = "not_found" - # --- Fall B: Zweistufige p-Gruppen --- + # --- Fall B: zweistufige p-Gruppen --- elif group_name.startswith('p') and group_name[1:2].isdigit(): - if group_name in beamline2department_cache: - beamline = beamline2department_cache[group_name] - else: - # Stufe 1: memberOf-Attribut des p-Objekts abfragen, um übergeordnete Gruppe zu finden - p_filter = f'(cn={group_name})' - conn.search( - search_base=AD_SEARCH_BASE, - search_filter=p_filter, - attributes=['memberOf'] - ) + beamline = "not_found" # Initialisierung - beamline = "not_found" - if conn.entries and 'memberOf' in conn.entries[0] and conn.entries[0].memberOf: - # Suche spezifisch nach dem Beamline-Serviceordner - selected_dn = None - for dn in conn.entries[0].memberOf: - dn_str = str(dn) - if 'OU=Beamlines,OU=Experiment,OU=IT' in dn_str: - selected_dn = dn_str - break - - # Fallback, falls der spezifische Ordner nicht in memberOf gefunden wurde - if selected_dn is None: - selected_dn = str(conn.entries[0].memberOf[0]) - - # Zielgruppe (CN) aus dem Distinguished Name extrahieren - try: - beamline = selected_dn.split(',')[0].split('=')[1] - beamline2department_cache[group_name] = beamline - except IndexError: - print(f"WARN: Format von DN '{selected_dn}' unerwartet.", file=sys.stderr, flush=True) - beamline = "format_error" - - item['beamline'] = beamline - - # Stufe 2: Department anhand der ermittelten beamline (aus memberOf) bestimmen - search_filter = f'(&(objectClass=group)(cn={beamline}))' + # Stufe 1: 'memberOf' der p-Gruppe abfragen, um die zugehörige Beamline-Gruppe zu finden + p_filter = f'(cn={group_name})' conn.search( search_base=AD_SEARCH_BASE, - search_filter=search_filter, - attributes=['department'] + search_filter=p_filter, + attributes=['memberOf'] ) - if conn.entries: - res = str(conn.entries[0].department) if 'department' in conn.entries[0] else "no-dept" - item['department'] = res + if conn.entries and 'memberOf' in conn.entries[0] and conn.entries[0].memberOf: + selected_dn = None + for dn in conn.entries[0].memberOf: + dn_str = str(dn) + if 'OU=Beamlines,OU=Experiment,OU=IT' in dn_str: + selected_dn = dn_str + break + + if selected_dn is None: + selected_dn = str(conn.entries[0].memberOf[0]) + + try: + beamline = selected_dn.split(',')[0].split('=')[1] + except IndexError: + print(f"WARN: Format von DN '{selected_dn}' unerwartet.", file=sys.stderr, flush=True) + beamline = "format_error" + + item['beamline'] = beamline + + # Stufe 2: Department für die ermittelte Beamline abfragen (mit Caching) + if beamline in beamline_department_cache: + item['department'] = beamline_department_cache[beamline] else: - item['department'] = "not_found" + search_filter = f'(&(objectClass=group)(cn={beamline}))' + conn.search( + search_base=AD_SEARCH_BASE, + search_filter=search_filter, + attributes=['department'] + ) + + if conn.entries: + res = str(conn.entries[0].department) if 'department' in conn.entries[0] else "no-dept" + beamline_department_cache[beamline] = res # Department im Cache speichern + item['department'] = res + else: + beamline_department_cache[beamline] = "not_found" + item['department'] = "not_found" # --- Fall C: Abweichende Schemata --- else: - print(f"WARN: Unbekanntes Gruppen-Schema: {group_name}", file=sys.stderr) + print(f"WARN: Unbekanntes Gruppen-Schema: {group_name}", file=sys.stderr, flush=True) item['department'] = "unknown_schema" continue + + # Fortschritt alle 500 Einträge im stderr protokollieren + if count % 500 == 0: + print(f"INFO: progress {count}/{len(items)}...", file=sys.stderr, flush=True) - # Fortschritt alle 500 Einträge im stderr protokollieren - if count % 500 == 0: - print(f"Fortschritt: {count}/{len(items)}...", file=sys.stderr, flush=True) + # Kompletter JSON-Output (momentan auskommentiert): + print(json.dumps(items)) - # 3. Finales Ergebnis auswerten - # Kompletten JSON-Output drucken (momentan auskommentiert): - # print(json.dumps(items)) - - # Test-Output: Alle nicht gefundenen bzw. leeren Zuweisungen drucken + # Alle Gruppen mit leerem Department-Feld ausgeben for item in items: - # Debug (auskommentiert): - # print(f"group: {item['ownerGroup']} -> {item['department']} -> type: {type(item['department'])}") - if item['department'] == "[]": + if item.get('department') == "[]": if 'beamline' in item: - print(f"LEER -> {item['ownerGroup']} -> {item['beamline']}") + print(f"LEER -> {item['ownerGroup']} (Beamline: {item['beamline']})", file=sys.stderr, flush=True) else: - print(f"LEER -> {item['ownerGroup']} has no beamline") + print(f"LEER -> {item['ownerGroup']} (hat keine Beamline)", file=sys.stderr, flush=True) + except Exception as e: print(f"ERROR: {str(e)}", file=sys.stderr, flush=True) - # Bei Fehler optional Items als JSON ausgeben (auskommentiert) - # if 'items' in locals(): - # print(json.dumps(items)) if __name__ == "__main__":