]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Implemented cleaning of target LDAP instance for mirror-ldap
authorFrank Brehm <frank@brehm-online.com>
Thu, 20 Oct 2022 15:03:54 +0000 (17:03 +0200)
committerFrank Brehm <frank@brehm-online.com>
Thu, 20 Oct 2022 15:03:54 +0000 (17:03 +0200)
lib/pp_admintools/app/ldap.py
lib/pp_admintools/app/mirror_ldap.py

index e2a1ac66a1edcb34007da94bb80f77040071361d..3c707d99e617ab23d0763cceb2c52fddc1d9bcd0 100644 (file)
@@ -53,7 +53,7 @@ from ..config.ldap import LdapConnectionInfo, LdapConfiguration
 # rom ..config.ldap import DEFAULT_PORT_LDAP, DEFAULT_PORT_LDAPS
 from ..config.ldap import DEFAULT_TIMEOUT
 
-__version__ = '0.9.0'
+__version__ = '0.10.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -792,10 +792,49 @@ class BaseLdapApplication(BaseDPXApplication):
         if result:
             result = sorted(result, key=cmp_to_key(self.compare_ldap_dns))
 
-        if self.verbose > 2 and result:
+        if self.verbose > 3 and result:
             LOG.debug(_("Result:") + '\n' + pp(result))
         return result
 
+    # -------------------------------------------------------------------------
+    def get_all_entry_dns_hash(self, inst):
+        """Get Object classes and DNs of all entries in the given LDAP instance."""
+
+        connect_info = self.cfg.ldap_connection[inst]
+        base_dn = connect_info.base_dn
+        ldap = self.ldap_connection[inst]
+
+        result = CIDict()
+        attributes = ['objectClass']
+        ldap_filter = '(objectClass=*)'
+
+        req_status, req_result, req_response, req_whatever = ldap.search(
+            search_base=base_dn, search_scope=SUBTREE, search_filter=ldap_filter,
+            get_operational_attributes=False, attributes=attributes,
+            time_limit=self.cfg.ldap_timeout)
+
+        if req_status:
+            if self.verbose > 5:
+                msg = _("Result of searching for DNs of all entries:")
+                LOG.debug(msg + '\n' + pp(req_result))
+            for entry in req_response:
+                if self.verbose > 4:
+                    LOG.debug(_("Got a response entry:") + ' ' + pp(entry))
+
+                dn = entry['dn']
+                object_classes = FrozenCIStringSet(entry['attributes']['objectClass'])
+                result[dn] = {
+                    'childs': CIStringSet(),
+                    'dn': dn,
+                    'object_classes': object_classes,
+                    'path': list(reversed(self.re_dn_separator.split(dn))),
+                }
+
+        else:
+            LOG.warn("Got no entry DNs.")
+
+        return result
+
     # -------------------------------------------------------------------------
     def get_user_dn(self, user, inst):
 
index 9f04ef9c9b8a1954510bd197a6344c41fb530307..1b3c8595a3e3b306eebf4a1e4774fe01502455a6 100644 (file)
@@ -11,13 +11,19 @@ from __future__ import absolute_import
 # Standard modules
 import sys
 import logging
+import copy
+import time
+
+from functools import cmp_to_key
 
 # Third party modules
 # from ldap3 import MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE
 
 # Own modules
 # from fb_tools.common import to_bool, is_sequence, pp
-# from fb_tools.common import pp
+from fb_tools.common import pp
+# from fb_tools.collections import FrozenCIStringSet, CIStringSet, CIDict
+from fb_tools.collections import CIDict, CIStringSet
 
 from ..xlate import XLATOR
 
@@ -28,7 +34,7 @@ from .ldap import BaseLdapApplication
 from ..argparse_actions import NonNegativeItegerOptionAction
 from ..argparse_actions import LimitedFloatOptionAction
 
-__version__ = '0.3.0'
+__version__ = '0.4.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -66,7 +72,8 @@ class MirrorLdapApplication(BaseLdapApplication):
         self.tgt_connect_info = None
 
         self.src_dns = []
-        self.tgt_dns_current = []
+        self.tgt_dns_current = CIDict()
+        self.registered_tgt_dns = CIDict()
 
         self.limit = 0
         self.wait_after_write = self.default_wait_after_write
@@ -212,7 +219,13 @@ class MirrorLdapApplication(BaseLdapApplication):
         self.empty_line()
         LOG.info("I'm walking, yes indeed I'm walking ...")
 
-        self.clean_target_instance()
+        try:
+            self.clean_target_instance()
+
+        except KeyboardInterrupt:
+            msg = _("Got a {}:").format('KeyboardInterrupt') + ' ' + _("Interrupted on demand.")
+            LOG.error(msg)
+            self.exit(10)
 
     # -------------------------------------------------------------------------
     def clean_target_instance(self):
@@ -224,6 +237,8 @@ class MirrorLdapApplication(BaseLdapApplication):
             "(except the base DN entry, of course)."))
 
         self.get_current_tgt_entries()
+        self.clean_tgt_non_struct_entries()
+        self.clean_tgt_struct_entries()
 
     # -------------------------------------------------------------------------
     def get_current_tgt_entries(self):
@@ -231,7 +246,87 @@ class MirrorLdapApplication(BaseLdapApplication):
 
         LOG.debug(_("Trying to get DNs of all entries in the target LDAP instance."))
 
-        result = self.get_all_entry_dns(self.tgt_instance)
+        self.tgt_dns_current = self.get_all_entry_dns_hash(self.tgt_instance)
+
+        for dn in sorted(list(self.tgt_dns_current.keys()), key=cmp_to_key(self.compare_ldap_dns)):
+            self.register_dn_tokens(dn, self.tgt_dns_current[dn], self.tgt_dns_current)
+
+        if self.verbose > 4:
+            LOG.debug("Current target entries:\n" + pp(self.tgt_dns_current.dict()))
+
+    # -------------------------------------------------------------------------
+    def register_dn_tokens(self, dn, entry, registry):
+
+        if self.verbose > 4:
+            LOG.debug("Trying to register DN {!r} ...".format(dn))
+
+        parent_tokens = copy.copy(entry['path'])[0:-1]
+        if not parent_tokens:
+            registry[dn]['parent'] = None
+            return
+        parent_dn = ','.join(reversed(parent_tokens))
+        if self.verbose > 4:
+            LOG.debug("Parent DN: {!r}.".format(parent_dn))
+        registry[dn]['parent'] = parent_dn
+        if parent_dn not in registry:
+            if self.verbose > 1:
+                LOG.debug("Entry {!r} seems to be a the root DN.".format(dn))
+            return
+
+        if not 'childs' not in registry[parent_dn]:
+            registry[parent_dn]['childs'] = CIStringSet()
+        registry[parent_dn]['childs'].add(dn)
+
+    # -------------------------------------------------------------------------
+    def clean_tgt_non_struct_entries(self):
+        """Removing all non structural entries in target instance.
+
+        Structural entries are entries without any childs.
+        """
+
+        self.empty_line()
+        LOG.info(_("Removing all non structural entries from target LDAP instance."))
+        if not self.quiet:
+            time.sleep(2)
+        self.empty_line()
+
+        for dn in sorted(list(self.tgt_dns_current.keys()), key=cmp_to_key(self.compare_ldap_dns)):
+            entry = self.tgt_dns_current[dn]
+            if 'childs' not in entry:
+                LOG.error("Found entry {dn!r}:\n{e}".format(dn=dn, e=pp(entry)))
+                self.exit(5)
+            if entry['childs']:
+                if self.verbose > 1:
+                    LOG.debug(_(
+                        "Entry {!r} is a structural entry, will not be removed "
+                        "at this point.").format(dn))
+                continue
+            self.delete_entry(self.tgt_instance, dn)
+            if self.wait_after_write and not self.simulate:
+                time.sleep(self.wait_after_write)
+
+    # -------------------------------------------------------------------------
+    def clean_tgt_struct_entries(self):
+        """Removing all structural entries in target instance.
+
+        Structural entries are entries without any childs.
+        """
+
+        self.empty_line()
+        LOG.info(_("Removing all structural entries from target LDAP instance."))
+        if not self.quiet:
+            time.sleep(2)
+        self.empty_line()
+
+        dns = sorted(list(self.tgt_dns_current.keys()), key=cmp_to_key(self.compare_ldap_dns))
+
+        for dn in list(reversed(dns[1:])):
+            entry = self.tgt_dns_current[dn]
+            if not entry['childs']:
+                continue
+            self.delete_entry(self.tgt_instance, dn)
+            if self.wait_after_write and not self.simulate:
+                time.sleep(self.wait_after_write)
 
 
 # =============================================================================