from .config import LDAPMigrationConfiguration
-__version__ = '0.10.8'
+__version__ = '0.11.0'
LOG = logging.getLogger(__name__)
CFG_BASENAME = 'ldap-migration.ini'
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)
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):
self.get_all_dns()
self.get_structural_dns()
self.migrate_entries()
- # self.migrate_group_entries()
+ self.migrate_group_entries()
self.detailled_summary()
finally: