]> Frank Brehm's Git Trees - pixelpark/ldap-migration.git/commitdiff
Implementing migration of group entries
authorFrank Brehm <frank.brehm@pixelpark.com>
Thu, 11 Mar 2021 15:11:38 +0000 (16:11 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Thu, 11 Mar 2021 15:11:38 +0000 (16:11 +0100)
lib/ldap_migration/__init__.py

index 5d898f3919b14808ca7dda5c99554198928831e6..8ff7bdc85c20f480d83075636de0cd34f17c93fe 100644 (file)
@@ -47,7 +47,7 @@ from fb_tools.collections import FrozenCIStringSet, CIStringSet, CIDict
 
 from .config import LDAPMigrationConfiguration
 
-__version__ = '0.10.8'
+__version__ = '0.11.0'
 
 LOG = logging.getLogger(__name__)
 CFG_BASENAME = 'ldap-migration.ini'
@@ -2025,7 +2025,7 @@ class LDAPMigrationApplication(BaseApplication):
         target_entry = {}
         used_classes = CIStringSet()
 
-        src_data = self.get_src_entry_data(src_entry, as_group=True)
+        src_data = self.normalize_entry_data(src_entry, as_group=True, is_src_entry=True)
         for src_oc_name in src_data['classes']:
             if src_oc_name.lower() not in ('groupofurls', 'groupofnames', 'groupofuniquenames'):
                 tgt_oc_name = self.object_classes.real_key(src_oc_name)
@@ -2140,83 +2140,268 @@ class LDAPMigrationApplication(BaseApplication):
         return members
 
     # -------------------------------------------------------------------------
-    def get_src_entry_data(self, src_entry, as_group=False):
+    def normalize_entry_data(self, entry, as_group=False, is_src_entry=False):
 
-        src_classes = CIStringSet()
-        src_attributes = CIDict()
-        src_members = CIStringSet()
-        src_member_url = CIStringSet()
+        classes = CIStringSet()
+        attributes = CIDict()
+        members = CIStringSet()
+        member_url = CIStringSet()
+        dn = entry['dn']
+        what = 'target entry'
+        if is_src_entry:
+            what = 'source entry'
 
-        for src_aname in src_entry['attributes']:
+        for aname in entry['attributes']:
 
-            src_attr = src_entry['attributes'][src_aname]
+            attribute = entry['attributes'][aname]
 
-            if src_aname.lower() == 'objectclass':
-                for src_oc_name in src_attr:
-                    if src_oc_name not in self.object_classes:
+            if aname.lower() == 'objectclass':
+                for oc_name in attribute:
+                    if oc_name not in self.object_classes:
                         if self.verbose > 3:
-                            msg = "ObjectClass {oc!r} of sorce entry {dn!r} not found "
+                            msg = "ObjectClass {oc!r} of {what} {dn!r} not found "
                             msg += "on target LDAP server."
-                            msg = msg.format(oc=src_oc_name, dn=src_dn)
+                            msg = msg.format(oc=oc_name, what=what, n=dn)
                             LOG.debug(msg)
-                        self.unknown_objectclasses.add(src_oc_name)
+                        self.unknown_objectclasses.add(oc_name)
                         continue
-                    src_classes.add(src_oc_name)
+                    norm_oc_name = self.object_classes.real_key(oc_name)
+                    classes.add(norm_oc_name)
+                continue
 
-            elif src_aname.lower() in ('member', 'uniquemember') and as_group:
+            elif aname.lower() in ('member', 'uniquemember') and as_group:
                 if self.verbose > 1:
-                    LOG.debug("Transforming members: {!r}".format(src_attr))
-                if is_sequence(src_attr):
-                    for attr in src_attr:
+                    LOG.debug("Transforming members: {!r}".format(attribute))
+                if is_sequence(attribute):
+                    for attr in attribute:
                         member = self.mangle_dn(attr)
-                        src_members.add(member)
+                        members.add(member)
                 else:
-                    member = self.mangle_dn(src_attr)
-                    src_members.add(member)
-            elif src_aname.lower() == 'memberurl' and as_group:
-                if is_sequence(src_attr):
-                    for attr in src_attr:
-                        src_member_url.add(attr)
+                    member = self.mangle_dn(attribute)
+                    members.add(member)
+                continue
+
+            elif aname.lower() == 'memberurl' and as_group:
+                if is_sequence(attribute):
+                    for attr in attribute:
+                        member_url.add(attr)
                 else:
-                    src_member_url.add(src_attr)
+                    member_url.add(attribute)
+                continue
+
             else:
-                if is_sequence(src_attr):
-                    for attr in src_attr:
+                if is_sequence(attribute):
+                    for attr in attribute:
                         if attr == '':
                             continue
-                        if src_aname not in src_attributes:
-                            src_attributes[src_aname] = []
-                        if src_aname in self.boolean_attr_types:
+                        if aname not in attributes:
+                            attributes[aname] = []
+                        if aname in self.boolean_attr_types:
                             if to_bool(attr):
                                 attr = 'TRUE'
                             else:
                                 attr = 'FALSE'
-                        src_attributes[src_aname].append(attr)
-                elif src_attr != '':
-                    if src_aname not in src_attributes:
-                        src_attributes[src_aname] = []
-                    if src_aname in self.boolean_attr_types:
-                        if to_bool(src_attr):
-                            src_attr = 'TRUE'
+                        attributes[aname].append(attr)
+                elif attribute != '':
+                    if aname not in attributes:
+                        attributes[aname] = []
+                    if aname in self.boolean_attr_types:
+                        if to_bool(attribute):
+                            attribute = 'TRUE'
                         else:
-                            src_attr = 'FALSE'
-                    src_attributes[src_aname].append(src_attr)
+                            attribute = 'FALSE'
+                    attributes[aname].append(attribute)
 
         ret = {
-            'classes': src_classes,
-            'attributes': src_attributes,
+            'attributes': attributes,
+            'classes': classes,
+            'dn': dn,
         }
         if as_group:
-            ret['members'] = src_members
-            ret['member_url'] = src_member_url
+            ret['members'] = members
+            ret['member_url'] = member_url
 
         return ret
 
     # -------------------------------------------------------------------------
     def migrate_existing_group_entry(self, src_dn, tgt_dn, src_entry, tgt_entry):
 
+        try:
+            changes = self.generate_modify_group_entry_data(src_entry, tgt_entry, src_dn, tgt_dn)
+        except UnicodeDecodeError as e:
+            msg = "Source attributes:\n{}".format(pp(src_entry['attributes']))
+            LOG.debug(msg)
+            msg = "Current target attributes:\n{}".format(pp(tgt_entry['attributes']))
+            LOG.debug(msg)
+            msg = "UnicodeDecodeError on generating changes for source DN {s!r}: {e}".format(
+                    s=src_dn, e=e)
+            raise FatalLDAPMigrationError(msg)
+        if changes:
+            if self.verbose:
+                LOG.info("Updating target entry {!r} ...".format(tgt_dn))
+            if self.verbose > 2:
+                msg = "Source attributes:\n{}".format(pp(src_entry['attributes']))
+                LOG.debug(msg)
+                msg = "Current target attributes:\n{}".format(pp(tgt_entry['attributes']))
+                LOG.debug(msg)
+            if self.verbose > 1:
+                msg = "Changes on target entry {tdn!r}:\n{ch}".format(
+                    tdn=tgt_dn, ch=pp(changes))
+                LOG.debug(msg)
+            self.count_modified += 1
+            if not self.simulate:
+                try:
+                    mod_status, mod_result, mod_response, _ = self.target.modify(tgt_dn, changes)
+                except LDAPException as e:
+                    msg = "Modifying NOT successfull - {c}: {e}\n"
+                    msg += "Source attributes:\n{sattr}\n"
+                    msg += "Changes:\n{ch}"
+                    msg = msg.format(
+                        c=e.__class__.__name__, e=e,
+                        sattr=pp(src_entry['attributes']), ch=pp(changes))
+                    raise WriteLDAPItemError(msg)
+                if mod_status:
+                    LOG.debug("Modifying successfull.")
+                    if self.verbose > 2:
+                        LOG.debug("Result of modifying:\n{}".format(pp(mod_result)))
+                else:
+                    msg = "Modifying NOT successfull:\n{res}\n"
+                    msg += "Source attributes:\n{sattr}\n"
+                    msg += "Changes:\n{ch}"
+                    msg = msg.format(
+                        res=pp(mod_result), sattr=pp(src_entry['attributes']),
+                        ch=pp(changes))
+                    raise WriteLDAPItemError(msg)
+            self.migrated_entries[rev_dn] = tgt_dn
+            self.write_result_file(fh, tgt_dn, '+')
+            return True
+        else:
+            if self.verbose:
+                msg = "No changes on target entry {tdn!r} necessary.".format(tdn=tgt_dn)
+                LOG.info(msg)
+            self.write_result_file(fh, tgt_dn, ' ')
+            return False
+
         return True
 
+    # -------------------------------------------------------------------------
+    def generate_modify_group_entry_data(self, src_entry, tgt_entry, src_dn, tgt_dn):
+
+        changes = {}
+
+        src_obj_classes = CIStringSet()
+        tgt_obj_classes = CIStringSet()
+        members_to_have = CIStringSet()
+        members_having = CIStringSet()
+
+        src_data = self.normalize_entry_data(src_entry, as_group=True, is_src_entry=True)
+        tgt_data = self.normalize_entry_data(tgt_entry, as_group=True, is_src_entry=False)
+
+        for src_oc_name in src_data['classes']:
+            if src_oc_name.lower() not in ('groupofurls', 'groupofnames', 'groupofuniquenames'):
+                oc_name = self.object_classes.real_key(src_oc_name)
+                src_obj_classes.add(oc_name)
+        src_obj_classes.add(self.object_classes.real_key('groupOfUniqueNames'))
+
+        for tgt_oc_name in tgt_data['classes']:
+            if tgt_oc_name.lower() not in ('groupofurls', 'groupofnames', 'groupofuniquenames'):
+                oc_name = self.object_classes.real_key(tgt_oc_name)
+                tgt_obj_classes.add(oc_name)
+        tgt_obj_classes.add(self.object_classes.real_key('groupOfUniqueNames'))
+
+        oc_diff = src_obj_classes - tgt_obj_classes
+        if oc_diff:
+            if 'objectClass' not in changes:
+                changes['objectClass'] = []
+            changes['objectClass'].append((MODIFY_ADD, oc_diff.as_list()))
+
+        oc_diff = tgt_obj_classes - src_obj_classes
+        if oc_diff:
+            if 'objectClass' not in changes:
+                changes['objectClass'] = []
+            changes['objectClass'].append((MODIFY_DELETE, oc_diff.as_list()))
+
+        for member in src_data['members']:
+            members_to_have.add(member)
+        members_to_have += self.get_dyn_members(src_data['member_url'])
+
+        for member in tgt_data['members']:
+            members_having.add(member)
+        members_having += self.get_dyn_members(tgt_data['member_url'], from_src=False)
+
+        for at_name in src_data['attributes']:
+
+            if ('obsolete' in self.attribute_types[at_name] and
+                    self.attribute_types[at_name]['obsolete']):
+                if self.verbose > 1:
+                    msg = "AttributeType {at!r} of sorce entry {dn!r} is obsolete.".format(
+                            at=at_name, dn=src_dn)
+                    LOG.debug(msg)
+                continue
+
+            if at_name not in self.attribute_types:
+                if self.verbose > 3:
+                    msg = "AttributeType {at!r} of sorce entry {dn!r} not found "
+                    msg += "on target LDAP server."
+                    msg = msg.format(at=at_name, dn=src_dn)
+                    LOG.debug(msg)
+                continue
+
+            src_value = src_data['attributes'][at_name]
+
+            if at_name in tgt_data['attributes']:
+                cur_tgt_value = tgt_data['attributes'][at_name]
+                both_equal = False
+                if at_name in self.pure_binary_attr_types:
+                    both_equal = self.compare_binary_values(
+                            src_value, tgt_data['attributes'][at_name])
+                else:
+                    both_equal = compare_ldap_values(src_value, tgt_data['attributes'][at_name])
+                if both_equal:
+                    if self.verbose > 3:
+                        msg = (
+                            "Attribute {atr!r} of source DN {sdn!r} is equal to "
+                            "target DN {tdn!r}. Won't change.")
+                        LOG.debug(msg.format(atr=at_name, sdn=src_dn, tdn=tgt_dn))
+                    continue
+                changes[at_name] = [(MODIFY_REPLACE, src_value)]
+            else:
+                changes[at_name] = [(MODIFY_ADD, src_value)]
+
+        at_name = 'uniqueMember'
+        if members_to_have:
+            if compare_ldap_values(members_to_have, members_having):
+                if self.verbose > 3:
+                    msg = (
+                        "Attribute {atr!r} of source DN {sdn!r} is equal to "
+                        "target DN {tdn!r}. Won't change.")
+                    LOG.debug(msg.format(atr=at_name, sdn=src_dn, tdn=tgt_dn))
+            else:
+                if at_name not in changes:
+                    changes[at_name] = []
+                member_diff = members_to_have - members_having
+                if member_diff:
+                    changes[at_name].append((MODIFY_ADD, member_diff.as_dict()))
+                member_diff = members_having - members_to_have
+                if member_diff:
+                    changes[at_name].append((MODIFY_DELETE, member_diff.as_dict()))
+
+        else:
+            if 'members' in tgt_data and tgt_data['members']:
+                if at_name not in changes:
+                    changes[at_name] = []
+                changes[at_name].append((MODIFY_DELETE))
+            if 'member_url' in tgt_data and tgt_data['member_url']:
+                if 'memberUrl' not in changes:
+                    changes['memberUrl'] = []
+                changes['memberUrl'].append((MODIFY_DELETE))
+
+        if changes.keys():
+            return changes
+
+        self.count_unchanged += 1
+        return None
+
     # -------------------------------------------------------------------------
     def detailled_summary(self):
 
@@ -2288,7 +2473,7 @@ class LDAPMigrationApplication(BaseApplication):
             self.get_all_dns()
             self.get_structural_dns()
             self.migrate_entries()
-            self.migrate_group_entries()
+            self.migrate_group_entries()
             self.detailled_summary()
 
         finally: