ldaptools/src/ldaptools/ldapsync/cmd.py

160 lines
6.7 KiB
Python

from __future__ import print_function
import argparse
import sys
import ldap.sasl
from ldaptools import ldif_utils, paged, ldap_source
from ldaptools.synchronize import Synchronize
def source_uri(value):
for prefix in ['ldapi://', 'ldap://', 'ldaps://']:
if value.startswith(prefix):
return value
raise argparse.ArgumentTypeError('%r is not an LDAP url' % value)
def or_type(f1, f2):
def f(value):
try:
return f1(value)
except argparse.ArgumentTypeError as e1:
try:
return f2(value)
except argparse.ArgumentTypeError as e2:
raise argparse.ArgumentTypeError('%s and %s' % (e1.args[0], e2.args[0]))
return f
def object_class_pivot(value):
value = list(filter(None, map(str.strip, map(str.lower, value.split()))))
if len(value) != 2:
raise argparse.ArgumentTypeError('%r is not a pair of an objectClass and an attribute name')
return value
def main(args=None):
parser = argparse.ArgumentParser(description='''\
Synchronize an LDIF file or a source LDAP directory to another directory
Base DN of the source is remapped to another DN in the target directory''')
parser.add_argument('--object-class-pivot',
required=True,
type=object_class_pivot,
action='append',
help='an objectClass and an attribute name which is the unique identifier '
'for this class')
parser.add_argument('--attributes-file',
type=argparse.FileType('r'),
help='a file containing the list of attributes to synchronize')
parser.add_argument('--attributes',
help='a list of attribute names separated by spaces')
parser.add_argument('--source-uri',
required=True,
type=or_type(source_uri, argparse.FileType('r')),
help='URL of an LDAP directory (ldapi://, ldap:// or ldaps://) or path of '
'and LDIF file')
parser.add_argument('--case-insensitive-attribute',
action='append',
help='indicate that the attribute must be compared case insensitively')
parser.add_argument('--source-base-dn',
required=True,
help='base DN of the source')
parser.add_argument('--source-bind-dn',
help='bind DN for a source LDAP directory')
parser.add_argument('--source-bind-password',
help='bind password for a source LDAP directory')
parser.add_argument('--source-filter',
help='filter to apply to a source LDAP directory')
parser.add_argument('--source-objectclasses',
help='keep only thoses object classes')
parser.add_argument('--target-uri',
type=source_uri,
required=True,
help='URL of the target LDAP directory')
parser.add_argument('--target-base-dn',
required=True,
help='base DN of the target LDAP directory')
parser.add_argument('--target-bind-dn',
help='bind DN for a target LDAP directory')
parser.add_argument('--target-bind-password',
help='bind password for a target LDAP directory')
parser.add_argument('--fake',
action='store_true',
help='compute synchronization actions but do not apply')
parser.add_argument('--verbose',
action='store_true',
help='print all actions to stdout')
options = parser.parse_args(args=args)
attributes = set()
if options.attributes_file:
attributes.update([attribute.strip().lower() for attribute in options.attributes_file])
if options.attributes:
for attribute in options.attributes.split():
attribute = attribute.strip().lower()
if attribute:
attributes.add(attribute)
attributes = list(attributes)
if not attributes:
parser.print_help()
print('You must give at least one attribute to synchronize')
if options.verbose:
print('Synchronizing', end=' ')
if hasattr(options.source_uri, 'read'):
if options.verbose:
print(options.source_uri.name, end=' ')
source = ldif_utils.ListLDIFParser(options.source_uri)
source.parse()
else:
if options.verbose:
print(options.source_uri, end=' ')
conn = paged.PagedLDAPObject(options.source_uri)
if options.source_uri.startswith('ldapi://'):
conn.sasl_interactive_bind_s("", ldap.sasl.external())
elif options.source_bind_dn and options.source_bind_password:
conn.simple_bind_s(options.source_bind_dn, options.source_bind_password)
source = ldap_source.LDAPSource(conn, base_dn=options.source_base_dn, attributes=attributes,
filterstr=options.source_filter)
if options.verbose:
print('to', options.target_uri, end=' ')
target_conn = paged.PagedLDAPObject(options.target_uri)
if options.target_uri.startswith('ldapi://'):
target_conn.sasl_interactive_bind_s("", ldap.sasl.external())
elif options.target_bind_dn and options.target_bind_dn:
target_conn.simple_bind_s(options.target_bind_dn, options.target_bind_password)
if options.source_objectclasses:
source_objectclasses = options.source_objectclasses.split()
else:
source_objectclasses = [v[0] for v in options.object_class_pivot]
synchronize = Synchronize(source, options.source_base_dn,
target_conn, options.target_base_dn,
pivot_attributes=options.object_class_pivot,
objectclasses=source_objectclasses,
attributes=attributes,
case_insensitive_attribute=options.case_insensitive_attribute)
synchronize.build_actions()
if options.verbose:
for action in synchronize.actions:
print(' -', action)
if not synchronize.actions:
print('Nothing to do.')
if not options.fake:
synchronize.apply_actions()
failed_actions = [action for action in synchronize.actions if action.errors]
if failed_actions:
print('Some actions failed:', file=sys.stderr)
for action in failed_actions:
print(' -', action)
for error in action.errors:
print(' *', error)
raise SystemExit(1)