From: Frank Brehm Date: Fri, 26 Jan 2024 13:09:05 +0000 (+0100) Subject: Refactored lib/pp_admintools/app/mirror_ldap.py X-Git-Tag: 1.0.0~1^2~33^2~4 X-Git-Url: https://git.uhu-banane.org/?a=commitdiff_plain;h=5c9a9e9186534a92d47af34f02901ca3f32f3c26;p=pixelpark%2Fpp-admin-tools.git Refactored lib/pp_admintools/app/mirror_ldap.py --- diff --git a/lib/pp_admintools/app/mirror_ldap.py b/lib/pp_admintools/app/mirror_ldap.py index 3671498..414ef27 100644 --- a/lib/pp_admintools/app/mirror_ldap.py +++ b/lib/pp_admintools/app/mirror_ldap.py @@ -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.',