# Standard modules
import copy
import logging
+import re
import sys
import time
from functools import cmp_to_key
from ..config.mirror_ldap import MirrorLdapConfiguration
from ..xlate import XLATOR
-__version__ = '1.0.1'
+__version__ = '1.1.0'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
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 '
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):
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()
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):
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.',