]> Frank Brehm's Git Trees - pixelpark/pp-admin-tools.git/commitdiff
Refactored lib/pp_admintools/app/mirror_ldap.py
authorFrank Brehm <frank.brehm@pixelpark.com>
Fri, 26 Jan 2024 13:09:05 +0000 (14:09 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Fri, 26 Jan 2024 13:09:05 +0000 (14:09 +0100)
lib/pp_admintools/app/mirror_ldap.py

index 36714983a163dd8dc49400b576dc5cd72b3d5a01..414ef277d2440088ecf96bdf171ecfc73bcca8f3 100644 (file)
@@ -11,6 +11,7 @@ from __future__ import absolute_import
 # Standard modules
 import copy
 import logging
+import re
 import sys
 import time
 from functools import cmp_to_key
@@ -36,7 +37,7 @@ from ..argparse_actions import NonNegativeIntegerOptionAction
 from ..config.mirror_ldap import MirrorLdapConfiguration
 from ..xlate import XLATOR
 
-__version__ = '1.0.1'
+__version__ = '1.1.0'
 LOG = logging.getLogger(__name__)
 
 _ = XLATOR.gettext
@@ -94,6 +95,7 @@ class MirrorLdapApplication(BaseLdapApplication):
         self.non_structural_entr_dns = []
         self.keep_entry_dns = []
         self.sync_entry_dns = []
+        self.data2modify = None
 
         desc = _(
             'Mirror the content of a complete LDAP instance (server or cluster) to '
@@ -172,6 +174,14 @@ class MirrorLdapApplication(BaseLdapApplication):
         self._check_source_instance()
         self._eval_keep_dns()
 
+        if self.cfg.entries_modify and self.tgt_instance in self.cfg.entries_modify:
+            self.data2modify = self.cfg.entries_modify[self.tgt_instance]
+        if self.data2modify:
+            msg = _("Data to modify:") + '\n' + pp(self.data2modify)
+        else:
+            msg = _("No data to modify found.")
+        LOG.debug(msg)
+
     # -------------------------------------------------------------------------
     def _eval_keep_dns(self):
 
@@ -282,8 +292,6 @@ class MirrorLdapApplication(BaseLdapApplication):
         msg = _("The timeout on LDAP operations is {} seconds.").format(self.cfg.ldap_timeout)
         LOG.debug(msg)
 
-        self.exit(0)
-
         try:
             self.get_current_src_entries()
             self.get_current_tgt_entries()
@@ -634,102 +642,280 @@ class MirrorLdapApplication(BaseLdapApplication):
 
         count = 0
 
+        for dn in dns:
+
+            if self.mirror_entry(dn):
+                count += 1
+
+            if self.limit and self.mirrored_entries >= self.limit:
+                break
+
+        self.empty_line()
+        if count:
+            msg = ngettext(
+                'Mirrored one structural entry in target LDAP instance.',
+                'Mirrored {no} structural entries to target LDAP instance.',
+                count).format(no=count)
+        else:
+            msg = _('Mirrored no structural entries to target LDAP instance.')
+        LOG.info(msg)
+
+    # -------------------------------------------------------------------------
+    def mirror_entry(self, dn):
+
         attributes = [ALL_ATTRIBUTES, 'aci']
 
-        for dn in dns:
+        if self.verbose > 1:
+            self.empty_line()
 
-            if self.verbose > 1:
-                self.empty_line()
+        if dn in self.keep_entry_dns:
+            LOG.debug(_('Entry {!r} is set to be kept.').format(dn))
+            return False
 
-            if dn in self.keep_entry_dns:
-                if self.verbose > 1:
-                    LOG.debug(_('Entry {!r} is set to be kept.').format(dn))
-                continue
+        if self.verbose > 1:
+            LOG.debug(_('Mirroring entry {!r} ...').format(dn))
 
-            if self.verbose > 1:
-                LOG.debug(_('Mirroring entry {!r} ...').format(dn))
+        try:
+            src_entry = self.get_entry(dn, self.src_instance, attributes)
 
-            try:
-                src_entry = self.get_entry(dn, self.src_instance, attributes)
-            except LDAPSocketReceiveError as e:
-                msg = _("Error on reading entry {!r} from source:").format(dn) + ' ' + str(e)
-                raise LdapReadError(msg)
+        except LDAPSocketReceiveError as e:
+            msg = _("Error on reading entry {!r} from source:").format(dn) + ' ' + str(e)
+            raise LdapReadError(msg)
 
-            if not src_entry:
-                msg = _('Did not found {!r} in the source LDAP.').format(dn)
-                LOG.warn(msg)
-                continue
-            src_attribs = self.normalized_attributes(
-                src_entry, omit_members=True, omit_memberof=True)
+        if not src_entry:
+            msg = _('Did not found {!r} in the source LDAP.').format(dn)
+            LOG.warn(msg)
+            return False
+
+        src_attribs = self.normalized_attributes(src_entry, omit_members=True, omit_memberof=True)
+        src_oclasses = src_attribs['objectClass'].as_list()
+        src_attribs_dict = src_attribs.dict()
+        src_attribs_dict['objectClass'] = src_oclasses
+
+        if self.verbose > 2:
+            LOG.debug('Got source entry:\n' + pp(src_attribs_dict))
+
+        if self.modify_src_attribs(dn, src_attribs):
             src_oclasses = src_attribs['objectClass'].as_list()
             src_attribs_dict = src_attribs.dict()
             src_attribs_dict['objectClass'] = src_oclasses
+            if self.verbose > 1:
+                LOG.debug('Modified source entry:\n' + pp(src_attribs_dict))
 
-            if self.verbose > 2:
-                LOG.debug('Got source entry:\n' + pp(src_attribs_dict))
+        try:
+            tgt_entry = self.get_entry(dn, self.tgt_instance, attributes, tries=1)
+        except LDAPSocketReceiveError as e:
+            msg = _("Error on reading entry {!r} from target:").format(dn) + ' ' + str(e)
+            raise LdapReadError(msg)
+        if tgt_entry:
+            tgt_attribs = self.normalized_attributes(
+                tgt_entry, omit_members=True, omit_memberof=True)
+            tgt_oclasses = tgt_attribs['objectClass'].as_list()
+            tgt_attribs_dict = tgt_attribs.dict()
+            tgt_attribs_dict['objectClass'] = tgt_oclasses
 
-            try:
-                tgt_entry = self.get_entry(dn, self.tgt_instance, attributes, tries=1)
-            except LDAPSocketReceiveError as e:
-                msg = _("Error on reading entry {!r} from target:").format(dn) + ' ' + str(e)
-                raise LdapReadError(msg)
-            if tgt_entry:
-                tgt_attribs = self.normalized_attributes(
-                    tgt_entry, omit_members=True, omit_memberof=True)
-                tgt_oclasses = tgt_attribs['objectClass'].as_list()
-                tgt_attribs_dict = tgt_attribs.dict()
-                tgt_attribs_dict['objectClass'] = tgt_oclasses
-
-                if self.verbose > 2:
-                    LOG.debug('Got target entry:\n' + pp(tgt_attribs_dict))
-
-                changes = self.generate_modify_data(dn, src_attribs, tgt_attribs)
-                if changes:
-                    self.empty_line()
-                    LOG.info(_('Modifying entry {!r} ...').format(dn))
-                    msg = _('Got modify data for DN {!r}:').format(dn)
-                    LOG.debug(msg + '\n' + pp(changes))
-                    self.modify_entry(self.tgt_instance, dn, changes)
-                    self.mirrored_entries += 1
-                    count += 1
-                    self.total_updated += 1
-                    self.mirrored_dns.add(dn)
-                    if self.wait_after_write and not self.simulate:
-                        time.sleep(self.wait_after_write)
-                else:
-                    if self.verbose > 1:
-                        LOG.debug(_('No changes necessary on DN {!r}.').format(dn))
-                    continue
+            if self.verbose > 2:
+                LOG.debug('Got target entry:\n' + pp(tgt_attribs_dict))
 
-            else:
-                LOG.debug(_('Target entry {!r} not found.').format(dn))
-                (object_classes, target_entry) = self.generate_create_entry(src_attribs)
+            changes = self.generate_modify_data(dn, src_attribs, tgt_attribs)
+            if changes:
                 self.empty_line()
-                LOG.info(_('Creating entry {!r} ...').format(dn))
-                msg = _('Got create data for DN {!r}:').format(dn)
-                msg += '\nobjectClasses:\n' + pp(object_classes)
-                msg += '\nAttributes:\n' + pp(target_entry)
-                LOG.debug(msg)
-                self.add_entry(self.tgt_instance, dn, object_classes, target_entry)
+                LOG.info(_('Modifying entry {!r} ...').format(dn))
+                msg = _('Got modify data for DN {!r}:').format(dn)
+                LOG.debug(msg + '\n' + pp(changes))
+                self.modify_entry(self.tgt_instance, dn, changes)
                 self.mirrored_entries += 1
-                count += 1
-                self.total_created += 1
+                self.total_updated += 1
                 self.mirrored_dns.add(dn)
                 if self.wait_after_write and not self.simulate:
                     time.sleep(self.wait_after_write)
+            else:
+                if self.verbose > 1:
+                    LOG.debug(_('No changes necessary on DN {!r}.').format(dn))
+                return False
 
-            if self.limit and self.mirrored_entries >= self.limit:
+        else:
+            LOG.debug(_('Target entry {!r} not found.').format(dn))
+            (object_classes, target_entry) = self.generate_create_entry(src_attribs)
+            self.empty_line()
+            LOG.info(_('Creating entry {!r} ...').format(dn))
+            msg = _('Got create data for DN {!r}:').format(dn)
+            msg += '\nobjectClasses:\n' + pp(object_classes)
+            msg += '\nAttributes:\n' + pp(target_entry)
+            LOG.debug(msg)
+            self.add_entry(self.tgt_instance, dn, object_classes, target_entry)
+            self.mirrored_entries += 1
+            self.total_created += 1
+            self.mirrored_dns.add(dn)
+            if self.wait_after_write and not self.simulate:
+                time.sleep(self.wait_after_write)
+
+        return True
+
+    # -------------------------------------------------------------------------
+    def modify_src_attribs(self, dn, src_attribs):
+        """Modifying some attributes according to configuration."""
+
+        if not self.data2modify:
+            return False
+
+        ret = None
+        modified = False
+        modified_data = False
+        found_modify_data = False
+
+        unmodified_data = src_attribs.dict()
+
+        for dn_pattern in self.data2modify.keys():
+
+            if not re.search(dn_pattern, dn, re.IGNORECASE):
+                continue
+
+            for action in self.data2modify[dn_pattern].keys():
+                action_entries = self.data2modify[dn_pattern][action]
+                for attrib in action_entries.keys():
+                    attrib_data = action_entries[attrib]
+                    modified_data = False
+                    if action == 'delete':
+                        modified_data = self._delete_src_attrib(src_attribs, attrib, attrib_data, dn)
+                    elif action == 'add':
+                        modified_data = self._add_src_attrib(src_attribs, attrib, attrib_data, dn)
+                    elif action == 'replace':
+                        modified_data = self._replace_src_attrib(src_attribs, attrib, attrib_data, dn)
+                    if modified_data:
+                        modified = True
+
+        if modified and self.verbose:
+            if self.verbose == 1:
+                self.empty_line()
+            msg = _("Modifying source data for {!r} ...").format(dn)
+            LOG.info(msg)
+            LOG.debug("Unmodified data:\n" + pp(unmodified_data))
+            LOG.debug('Modified data:\n' + pp(src_attribs.dict()))
+
+        return modified
+
+    # -------------------------------------------------------------------------
+    def _replace_src_attrib(self, src_attribs, attrib, attrib_data, dn):
+
+        if self.verbose > 1:
+            LOG.debug(_("Replacing attribute {a!r} in entry {dn!r} ...").format(
+                a=attrib, dn=dn))
+
+        src_attrib = None
+        src_data = None
+        for src_attrib in src_attribs.keys():
+            if src_attrib.lower() == attrib.lower():
+                src_data = src_attribs[src_attrib]
                 break
+        if not src_attrib:
+            src_attrib = attrib
 
-        if count:
-            self.empty_line()
-            msg = ngettext(
-                'Mirrored one structural entry in target LDAP instance.',
-                'Mirrored {no} structural entries to target LDAP instance.',
-                count).format(no=count)
-        else:
-            msg = _('Mirrored no structural entries to target LDAP instance.')
-        LOG.info(msg)
+        if not src_data:
+            src_attribs[src_attrib] = attrib_data
+            return True
+
+        found = False
+        equal = True
+
+        for tgt_value in attrib_data:
+            found = False
+            for src_value in src_data:
+                if src_value.lower() == tgt_value.lower():
+                    found = True
+                    break
+            if not found:
+                equal = False
+
+        for src_value in src_data:
+            found = False
+            for tgt_value in attrib_data:
+                if src_value.lower() == tgt_value.lower():
+                    found = True
+                    break
+            if not found:
+                equal = False
+
+        if equal:
+            return False
+        src_attribs[src_attrib] = attrib_data
+        return True
+
+    # -------------------------------------------------------------------------
+    def _delete_src_attrib(self, src_attribs, attrib, attrib_data, dn):
+
+        if self.verbose > 1:
+            LOG.debug(_("Deleting attribute {a!r} from entry {dn!r} ...").format(
+                a=attrib, dn=dn))
+
+        found = False
+        src_attrib = None
+        for src_attrib in src_attribs.keys():
+            if src_attrib.lower() == attrib.lower():
+                found = True
+                break
+
+        if not found:
+            return False
+
+        # Remove complete attribute
+        if attrib_data is None:
+            del src_attribs[src_attrib]
+            return True
+
+        # Remove particular value
+        deleted = False
+        src_attrib_data = src_attribs[src_attrib]
+        for src_value in src_attrib_data:
+            for tgt_value in attrib_data:
+                if src_value.lower() == tgt_value.lower():
+                    src_attrib_data.remove(src_value)
+                    deleted = True
+                    break
+        if not len(src_attrib_data):
+            del src_attribs[src_attrib]
+            deleted = True
+
+        return deleted
+
+    # -------------------------------------------------------------------------
+    def _add_src_attrib(self, src_attribs, attrib, attrib_data, dn):
+
+        if self.verbose > 1:
+            LOG.debug(_("Adding attribute {a!r} to entry {dn!r} ...").format(
+                a=attrib, dn=dn))
+
+        src_attrib = None
+        src_data = None
+        for src_attrib in src_attribs.keys():
+            if src_attrib.lower() == attrib.lower():
+                src_data = src_attribs[src_attrib]
+                break
+        if not src_attrib:
+            src_attrib = attrib
+
+        if not src_data:
+            src_attribs[src_attrib] = attrib_data
+            return True
+
+        added = False
+        values_to_add = []
+        for tgt_value in attrib_data:
+            found = False
+            for src_value in src_data:
+                if src_value.lower() == tgt_value.lower():
+                    found = True
+                    break
+            if not found:
+                values_to_add.append(tgt_value)
+
+        if not values_to_add:
+            return False
+
+        for value in values_to_add:
+            src_attribs[src_attrib].append(value)
+
+        return True
 
     # -------------------------------------------------------------------------
     def mirror_non_struct_entries(self):
@@ -744,97 +930,19 @@ class MirrorLdapApplication(BaseLdapApplication):
 
         count = 0
 
-        attributes = [ALL_ATTRIBUTES, 'aci']
-
         for dn in dns:
 
             if dn in self.src_struct_dns:
                 continue
 
-            if self.verbose > 1:
-                self.empty_line()
-
-            if dn in self.keep_entry_dns:
-                if self.verbose > 1:
-                    LOG.debug(_('Entry {!r} is set to be kept.').format(dn))
-                continue
-
-            if self.verbose > 1:
-                LOG.debug(_('Mirroring entry {!r} ...').format(dn))
-
-            try:
-                src_entry = self.get_entry(dn, self.src_instance, attributes)
-            except LDAPSocketReceiveError as e:
-                msg = _("Error on reading entry {!r} from source:").format(dn) + ' ' + str(e)
-                raise LdapReadError(msg)
-            if not src_entry:
-                msg = _('Did not found {!r} in the source LDAP.').format(dn)
-                LOG.warn(msg)
-                continue
-            src_attribs = self.normalized_attributes(
-                src_entry, omit_members=True, omit_memberof=True)
-            src_oclasses = src_attribs['objectClass'].as_list()
-            src_attribs_dict = src_attribs.dict()
-            src_attribs_dict['objectClass'] = src_oclasses
-
-            if self.verbose > 2:
-                LOG.debug('Got source entry:\n' + pp(src_attribs_dict))
-
-            try:
-                tgt_entry = self.get_entry(dn, self.tgt_instance, attributes, tries=1)
-            except LDAPSocketReceiveError as e:
-                msg = _("Error on reading entry {!r} from target:").format(dn) + ' ' + str(e)
-                raise LdapReadError(msg)
-            if tgt_entry:
-                tgt_attribs = self.normalized_attributes(
-                    tgt_entry, omit_members=True, omit_memberof=True)
-                tgt_oclasses = tgt_attribs['objectClass'].as_list()
-                tgt_attribs_dict = tgt_attribs.dict()
-                tgt_attribs_dict['objectClass'] = tgt_oclasses
-
-                if self.verbose > 2:
-                    LOG.debug('Got target entry:\n' + pp(tgt_attribs_dict))
-
-                changes = self.generate_modify_data(dn, src_attribs, tgt_attribs)
-                if changes:
-                    self.empty_line()
-                    LOG.info(_('Modifying entry {!r} ...').format(dn))
-                    msg = _('Got modify data for DN {!r}:').format(dn)
-                    LOG.debug(msg + '\n' + pp(changes))
-                    self.modify_entry(self.tgt_instance, dn, changes)
-                    self.mirrored_entries += 1
-                    count += 1
-                    self.total_updated += 1
-                    self.mirrored_dns.add(dn)
-                    if self.wait_after_write and not self.simulate:
-                        time.sleep(self.wait_after_write)
-                else:
-                    if self.verbose > 1:
-                        LOG.debug(_('No changes necessary on DN {!r}.').format(dn))
-                    continue
-
-            else:
-                LOG.debug(_('Target entry {!r} not found.').format(dn))
-                (object_classes, target_entry) = self.generate_create_entry(src_attribs)
-                self.empty_line()
-                LOG.info(_('Creating entry {!r} ...').format(dn))
-                msg = _('Got create data for DN {!r}:').format(dn)
-                msg += '\nobjectClasses:\n' + pp(object_classes)
-                msg += '\nAttributes:\n' + pp(target_entry)
-                LOG.debug(msg)
-                self.add_entry(self.tgt_instance, dn, object_classes, target_entry)
-                self.mirrored_entries += 1
+            if self.mirror_entry(dn):
                 count += 1
-                self.total_created += 1
-                self.mirrored_dns.add(dn)
-                if self.wait_after_write and not self.simulate:
-                    time.sleep(self.wait_after_write)
 
             if self.limit and self.mirrored_entries >= self.limit:
                 break
 
+        self.empty_line()
         if count:
-            self.empty_line()
             msg = ngettext(
                 'Mirrored one non-structural entry in target LDAP instance.',
                 'Mirrored {no} non-structural entries to target LDAP instance.',