diff --git a/apps/tesm/pyproject.toml b/apps/tesm/pyproject.toml index d09fd02..3d36c57 100644 --- a/apps/tesm/pyproject.toml +++ b/apps/tesm/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "tesm" -version = "2.8.0" +version = "2.8.1" description = "TESM -- Verwaltung und Ueberwachung von PoE-Endgeraeten und Aruba-Switchen" requires-python = ">=3.11" dependencies = [ diff --git a/apps/tesm/src/tesm/services/legacy_transfer.py b/apps/tesm/src/tesm/services/legacy_transfer.py index 7b02f7f..ec3dc2b 100644 --- a/apps/tesm/src/tesm/services/legacy_transfer.py +++ b/apps/tesm/src/tesm/services/legacy_transfer.py @@ -16,10 +16,18 @@ eingelesen. Ein eigener Schreibpfad fuer Altdaten waere eine zweite Stelle, an der Fremdeingaben in die Datenbank gelangen -- und die zweite Stelle ist die, die niemand pflegt. -Uebersetzt werden Clients, Switche, Zugangsdaten und die DHCP-Konfiguration. -Was in der Datei sonst noch steht, wird nicht stillschweigend verworfen, -sondern im Bericht benannt -- damit sichtbar bleibt, was von Hand nachzuziehen -ist. +Uebersetzt werden Clients, Switche, Zugangsdaten, die DHCP-Konfiguration und +die Freigabe-Zuordnungen. Was in der Datei sonst noch steht, wird nicht +stillschweigend verworfen, sondern im Bericht benannt -- damit sichtbar bleibt, +was von Hand nachzuziehen ist. + +Die Freigaben standen bis 2.8.0 auf der Liste "von Hand nachtragen", obwohl es +dafuer keinen Grund gab: der Vorgaenger fuehrt sie in +``ldap_fileshare_mappings`` mit denselben vier Angaben, die diese Anwendung in +``fileshare_mappings`` braucht (Verzeichnisgruppe, deren Name, Beschriftung, +UNC-Pfad), und einen Import dafuer gibt es hier ohnehin. Was sich uebernehmen +laesst, soll man nicht abtippen muessen -- ein UNC-Pfad von Hand ist ein +Tippfehler, der erst auffaellt, wenn jemand die Freigabe braucht. """ from __future__ import annotations @@ -35,8 +43,10 @@ from typing import Any LEGACY_KDF_ITERATIONS = 390000 LEGACY_APP_TAG = "tesm" -#: Diese Abschnitte werden uebersetzt. -SUPPORTED = ("credentials", "switches", "devices", "dhcp") +#: Diese Abschnitte werden uebersetzt. "ldap" nur zum Teil -- daraus kommen die +#: Freigabe-Zuordnungen, waehrend Servereinstellungen und Gruppen-Zuordnungen +#: dort bleiben (siehe KNOWN_UNSUPPORTED). +SUPPORTED = ("credentials", "switches", "devices", "dhcp", "ldap") #: Was der alte Export sonst enthaelt. Wird gemeldet, nicht uebersetzt. KNOWN_UNSUPPORTED: dict[str, str] = { @@ -46,9 +56,12 @@ KNOWN_UNSUPPORTED: dict[str, str] = { "Zweck. Konten neu anlegen." ), "groups": "Gruppen und Rechte: die Rechteschlüssel wurden neu geschnitten.", + # Nur noch der Teil, der wirklich nicht uebertragbar ist: Server, Bind-Konto + # und die Gruppen-Zuordnung (dort zeigen die Namen auf Gruppen, deren + # Rechteschluessel neu geschnitten wurden). Die Freigaben kommen mit. "ldap": ( - "Verzeichnisdienst: Einstellungen, Gruppen- und Freigabe-Zuordnungen von " - "Hand übertragen." + "Verzeichnisdienst: Server, Bind-Konto und die Gruppen-Zuordnung von Hand " + "übertragen. Die Freigabe-Zuordnungen wurden übernommen." ), "nginx": "Webserver: Domain, Ports und Zertifikat von Hand übertragen.", "logs": "Auditlog: bleibt bewusst beim alten System.", @@ -272,6 +285,48 @@ def _devices(items: Any, credentials: list[dict[str, Any]], report: Report) -> l return ergebnis +def _freigaben(data: Any, report: Report) -> list[dict[str, Any]]: + """Freigabe-Zuordnungen aus dem Abschnitt "ldap" des alten Exports. + + Der Vorgaenger legt sie unter ``ldap.fileshare_mappings`` ab; beim Lesen + einer Installation stehen sie unmittelbar in der Tabelle. Beides landet + hier, damit es nur eine Stelle gibt, die die Felder umbenennt. + """ + if isinstance(data, dict): + eintraege = _liste(data.get("fileshare_mappings")) + else: + eintraege = _liste(data) + ergebnis: list[dict[str, Any]] = [] + ohne_pfad = 0 + for eintrag in eintraege: + # "share_label"/"share_unc" heissen hier "label"/"unc" -- dieselbe + # Angabe, kuerzerer Name, weil die Tabelle schon "fileshare" heisst. + unc = str(eintrag.get("share_unc") or eintrag.get("unc") or "").strip() + gruppe = str(eintrag.get("ad_group_dn") or "").strip() + if not unc or not gruppe: + ohne_pfad += 1 + continue + beschriftung = str(eintrag.get("share_label") or eintrag.get("label") or "").strip() + ergebnis.append( + { + "ad_group_dn": gruppe, + "ad_group_name": str(eintrag.get("ad_group_name") or "").strip() or gruppe, + # Ohne Beschriftung der letzte Teil des Pfades -- besser als + # eine Freigabe ohne Namen. Beide Trenner: der Vorgaenger + # speichert "//server/share", getippt wird "\server\share". + "label": beschriftung + or unc.replace(chr(92), "/").rstrip("/").rsplit("/", 1)[-1], + "unc": unc, + } + ) + if ohne_pfad: + report.note( + f"{ohne_pfad} Freigabe-Zuordnung(en) ohne Verzeichnisgruppe oder UNC-Pfad " + "übersprungen." + ) + return ergebnis + + def _dhcp(data: Any, report: Report) -> tuple[dict[str, Any], dict[str, str]]: """DHCP-Teil uebersetzen. Rueckgabe: ``(dhcp, settings)``.""" if not isinstance(data, dict): @@ -382,6 +437,10 @@ def convert(legacy: dict[str, Any]) -> tuple[dict[str, Any], Report]: if devices: payload["devices"] = devices + freigaben = _freigaben(legacy.get("ldap") or legacy.get("fileshare"), report) + if freigaben: + payload["fileshare"] = freigaben + dhcp, settings = _dhcp(legacy.get("dhcp"), report) if dhcp: payload["dhcp"] = dhcp @@ -417,8 +476,8 @@ def convert(legacy: dict[str, Any]) -> tuple[dict[str, Any], Report]: if not payload: raise LegacyTransferError( - "Die Datei enthält keine Clients, Switche, Zugangsdaten oder DHCP-Daten. " - "Beim Export im alten TESM diese Kategorien mitwählen." + "Die Datei enthält keine Clients, Switche, Zugangsdaten, DHCP-Daten oder " + "Freigaben. Beim Export im alten TESM diese Kategorien mitwählen." ) return payload, report @@ -634,6 +693,21 @@ def read_installation(root: Path) -> tuple[dict[str, Any], Report]: else: report.note("Tabelle 'credentials' fehlt in der alten Datenbank.") + # Die Freigaben reisen im Abschnitt "ldap" -- genau dort, wo der alte + # Export sie auch hat. So liest die Uebersetzung beide Wege gleich. + if "ldap_fileshare_mappings" in vorhanden: + freigaben = [ + dict(row) + for row in conn.execute( + "SELECT ad_group_dn, ad_group_name, share_label, share_unc " + "FROM ldap_fileshare_mappings" + ).fetchall() + ] + if freigaben: + payload["ldap"] = {"fileshare_mappings": freigaben} + else: + report.note("Tabelle 'ldap_fileshare_mappings' fehlt in der alten Datenbank.") + dhcp = _alt_dhcp(conn, vorhanden) if dhcp: payload["dhcp"] = dhcp @@ -644,9 +718,12 @@ def read_installation(root: Path) -> tuple[dict[str, Any], Report]: if not payload: raise LegacyTransferError( f"In {root} stehen keine übernehmbaren Daten (Clients, Switche, " - "Zugangsdaten, DHCP)." + "Zugangsdaten, DHCP, Freigaben)." ) for abschnitt, inhalt in payload.items(): + if abschnitt == "ldap" and isinstance(inhalt, dict): + report.took("Freigaben", len(_liste(inhalt.get("fileshare_mappings")))) + continue report.took(abschnitt, len(inhalt) if isinstance(inhalt, list) else 1) return payload, report diff --git a/apps/tesm/src/tesm/services/transfer.py b/apps/tesm/src/tesm/services/transfer.py index 7195afb..8dfe62e 100644 --- a/apps/tesm/src/tesm/services/transfer.py +++ b/apps/tesm/src/tesm/services/transfer.py @@ -534,19 +534,33 @@ def apply_import( if "fileshare" in wanted: for entry in payload.get("fileshare", []): + gruppe = str(entry.get("ad_group_dn") or "") + pfad = str(entry.get("unc") or "") + if not gruppe or not pfad: + bump("fileshare", "skipped") + continue + # Vorher nachsehen, damit der Bericht die Wahrheit sagt: bisher + # stand dort auch bei einer frischen Installation "2 aktualisiert", + # obwohl zwei Zuordnungen **angelegt** wurden. Wer einen Import + # prueft, liest genau diese Zahlen. + vorhanden = Database.value( + conn, + "SELECT 1 FROM fileshare_mappings WHERE ad_group_dn=? AND unc=? LIMIT 1", + (gruppe, pfad), + ) conn.execute( "INSERT INTO fileshare_mappings (ad_group_dn, ad_group_name, label, unc, created_at) " "VALUES (?,?,?,?,?) ON CONFLICT(ad_group_dn, unc) DO UPDATE SET " "label=excluded.label, ad_group_name=excluded.ad_group_name", ( - str(entry.get("ad_group_dn") or ""), + gruppe, str(entry.get("ad_group_name") or ""), str(entry.get("label") or ""), - str(entry.get("unc") or ""), + pfad, stamp, ), ) - bump("fileshare", "updated") + bump("fileshare", "updated" if vorhanden else "created") if "groups" in wanted: for entry in payload.get("groups", []):