]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Retrieving existing entries in target LDAP instance for mirror-ldap
authorFrank Brehm <frank@brehm-online.com>
Thu, 20 Oct 2022 12:21:39 +0000 (14:21 +0200)
committerFrank Brehm <frank@brehm-online.com>
Thu, 20 Oct 2022 12:21:39 +0000 (14:21 +0200)
lib/pp_admintools/app/__init__.py
lib/pp_admintools/app/ldap.py
lib/pp_admintools/app/mirror_ldap.py

index fa23a62c2193e6676bc20860c7a295f606315a38..68c128d2110e5e7d4de1acbc89251f7185b8c302 100644 (file)
@@ -26,7 +26,7 @@ LOG = logging.getLogger(__name__)
 _ = XLATOR.gettext
 ngettext = XLATOR.ngettext
 
-__version__ = '0.4.2'
+__version__ = '0.5.0'
 
 
 # =============================================================================
@@ -71,6 +71,12 @@ class BaseDPXApplication(FbConfigApplication):
             env_prefix=env_prefix, config_dir=config_dir
         )
 
+    # -------------------------------------------------------------------------
+    def empty_line(self):
+        """Print out an empty line on stdout, if not in quiet mode."""
+        if not self.quiet:
+            print()
+
     # -------------------------------------------------------------------------
     def post_init(self):
         """
index 0b186ca375da4b2770ca46e04d413ed2c492266b..e2a1ac66a1edcb34007da94bb80f77040071361d 100644 (file)
@@ -18,6 +18,8 @@ try:
 except ImportError:
     from pathlib2 import Path
 
+from functools import cmp_to_key
+
 # Third party modules
 from ldap3 import Server, Connection, DSA, IP_V4_PREFERRED, SAFE_SYNC
 # from ldap3 import ALL
@@ -51,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.8.2'
+__version__ = '0.9.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -177,9 +179,65 @@ class BaseLdapApplication(BaseDPXApplication):
     pattern_re_uid = r'^[a-z](?:[a-z0-9\-_.]*[a-z0-9])?$'
     re_uid = re.compile(pattern_re_uid, re.IGNORECASE)
 
+    pattern_dn_separator = r'\s*,\s*'
+    re_dn_separator = re.compile(pattern_dn_separator)
+
     person_object_classes = FrozenCIStringSet([
         'account', 'inetOrgPerson', 'inetUser', 'posixAccount', 'sambaSamAccount'])
 
+    # -------------------------------------------------------------------------
+    @classmethod
+    def compare_ldap_dns(cls, x, y):
+
+        # Check for empty DNs
+        if x is None and y is None:
+            return 0
+        if x is None:
+            return -1
+        if y is None:
+            return 1
+
+        # Check for empty FQDNs
+        xs = str(x).strip()
+        ys = str(y).strip()
+
+        if xs == '' and ys == '':
+            return 0
+        if xs == '':
+            return -1
+        if ys == '':
+            return 1
+
+        tokens_x = cls.re_dn_separator.split(xs)
+        tokens_x_rev = list(reversed(tokens_x))
+        tokens_y = cls.re_dn_separator.split(ys)
+        tokens_y_rev = list(reversed(tokens_y))
+
+        if ','.join(tokens_x).lower() == ','.join(tokens_y).lower():
+            return 0
+
+        i = 0
+        while True:
+            j = i + 1
+            if j > len(tokens_x_rev) or j > len(tokens_y_rev):
+                if j <= len(tokens_x_rev):
+                    return 1
+                if j <= len(tokens_y_rev):
+                    return -1
+                return 0
+            token_x = tokens_x_rev[i]
+            token_y = tokens_y_rev[i]
+            i += 1
+            if token_x.lower() > token_y.lower():
+                return 1
+            if token_x.lower() < token_y.lower():
+                return -1
+
+        # Should never come to here ...
+        LOG.warn("Reached a point, where I never should come.")
+        LOG.debug("Compared x: {x!r} <=> y: {y!r}".format(x=x, y=y))
+        return 0
+
     # -------------------------------------------------------------------------
     def __init__(
         self, appname=None, verbose=0, version=GLOBAL_VERSION, base_dir=None,
@@ -702,6 +760,42 @@ class BaseLdapApplication(BaseDPXApplication):
                 LOG.debug(_("Disconnecting from LDAP server {!r} ...").format(connect_info.url))
             del self.ldap_server[inst]
 
+    # -------------------------------------------------------------------------
+    def get_all_entry_dns(self, inst):
+        """Get DNs of all entries in the given LDAP instance and sort them."""
+
+        connect_info = self.cfg.ldap_connection[inst]
+        base_dn = connect_info.base_dn
+        ldap = self.ldap_connection[inst]
+
+        result = []
+        attributes = ['dn']
+        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))
+                result.append(entry['dn'])
+
+        else:
+            LOG.warn("Got no entry DNs.")
+
+        if result:
+            result = sorted(result, key=cmp_to_key(self.compare_ldap_dns))
+
+        if self.verbose > 2 and result:
+            LOG.debug(_("Result:") + '\n' + pp(result))
+        return result
+
     # -------------------------------------------------------------------------
     def get_user_dn(self, user, inst):
 
index c682ff2c9d0722aaff63004b4e198a404b758bff..9f04ef9c9b8a1954510bd197a6344c41fb530307 100644 (file)
@@ -28,7 +28,7 @@ from .ldap import BaseLdapApplication
 from ..argparse_actions import NonNegativeItegerOptionAction
 from ..argparse_actions import LimitedFloatOptionAction
 
-__version__ = '0.2.2'
+__version__ = '0.3.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -60,9 +60,13 @@ class MirrorLdapApplication(BaseLdapApplication):
     def __init__(self, appname=None, base_dir=None):
 
         self.src_instance = None
-        self.src = None
         self.tgt_instance = None
-        self.tgt = None
+
+        self.src_connect_info = None
+        self.tgt_connect_info = None
+
+        self.src_dns = []
+        self.tgt_dns_current = []
 
         self.limit = 0
         self.wait_after_write = self.default_wait_after_write
@@ -130,7 +134,7 @@ class MirrorLdapApplication(BaseLdapApplication):
 
         limit = getattr(self.args, 'limit', 0)
         if limit:
-            print()
+            self.empty_line()
             if self.simulate:
                 self.limit = limit
                 LOG.warn(_(
@@ -140,7 +144,7 @@ class MirrorLdapApplication(BaseLdapApplication):
                 LOG.error(_(
                     "Limition the number of entries for mirroring may "
                     "only be done in simulation mode."))
-                print()
+                self.empty_line()
                 self.arg_parser.print_usage(sys.stdout)
                 self.exit(1)
 
@@ -152,6 +156,7 @@ class MirrorLdapApplication(BaseLdapApplication):
     def _check_source_instance(self):
 
         tgt_name = self.ldap_instances[0]
+        self.tgt_connect_info = self.cfg.ldap_connection[tgt_name]
         LOG.debug(_(
             "Checking mirror source instance for target instance {!r} ...").format(tgt_name))
 
@@ -165,6 +170,7 @@ class MirrorLdapApplication(BaseLdapApplication):
                 "does not exists.").format(src=src_name, tgt=tgt_name)
             LOG.error(msg)
             self.exit(3)
+        self.src_connect_info = self.cfg.ldap_connection[src_name]
 
         if tgt_name.lower() == src_name.lower():
             msg = _("Error in configuration:")
@@ -190,27 +196,11 @@ class MirrorLdapApplication(BaseLdapApplication):
         self.tgt_instance = tgt_name
         self.ldap_instances.append(src_name)
 
-    # -------------------------------------------------------------------------
-    def pre_run(self):
-
-        super(MirrorLdapApplication, self).pre_run()
-
-        self.src = self.ldap_connection[self.src_instance]
-        self.tgt = self.ldap_connection[self.tgt_instance]
-
-    # -------------------------------------------------------------------------
-    def disconnect_all(self):
-
-        self.src = None
-        self.tgt = None
-
-        super(MirrorLdapApplication, self).disconnect_all()
-
     # -------------------------------------------------------------------------
     def _run(self):
 
         if not self.quiet and not self.force:
-            print()
+            self.empty_line()
             src_url = self.cfg.ldap_connection[self.src_instance].url
             tgt_url = self.cfg.ldap_connection[self.tgt_instance].url
             msg = _(
@@ -219,10 +209,30 @@ class MirrorLdapApplication(BaseLdapApplication):
                 src=self.src_instance, src_url=src_url, tgt=self.tgt_instance, tgt_url=tgt_url)
             self.countdown(number=5, delay=1, prompt=msg)
 
-        if not self.quiet:
-            print()
+        self.empty_line()
         LOG.info("I'm walking, yes indeed I'm walking ...")
 
+        self.clean_target_instance()
+
+    # -------------------------------------------------------------------------
+    def clean_target_instance(self):
+        """Cleaning the target instance."""
+
+        self.empty_line()
+        LOG.info(_(
+            "Removing all existing entries in target LDAP instance "
+            "(except the base DN entry, of course)."))
+
+        self.get_current_tgt_entries()
+
+    # -------------------------------------------------------------------------
+    def get_current_tgt_entries(self):
+        """Get DNs of all entries in the target LDAP instance and sort them."""
+
+        LOG.debug(_("Trying to get DNs of all entries in the target LDAP instance."))
+
+        result = self.get_all_entry_dns(self.tgt_instance)
+
 
 # =============================================================================
 if __name__ == "__main__":