# 3rd party modules
-from ldap3 import LDAPException
+from ldap3.core.exceptions import LDAPException
# Own modules
from fb_tools.colored import ColoredFormatter
from fb_tools.config import CfgFileOptionAction
from fb_tools.errors import FbAppError
+from .config import LDAPMigrationConfiguration
-__version__ = '0.1.0'
+__version__ = '0.2.0'
LOG = logging.getLogger(__name__)
+CFG_BASENAME = 'ldap-migration.ini'
-
-#---------------------------------------------
+# --------------------------------------------
# Some module variables
-#==============================================================================
+# =============================================================================
class CommonLDAPMigrationError(FbAppError, LDAPException):
"""
Base error class for all exceptions belonging to the ldap_migration package
pass
-#==============================================================================
+# =============================================================================
+class LDAPMigrationApplication(BaseApplication):
+ """
+ Class for the application objects.
+ """
+
+ # -------------------------------------------------------------------------
+ def __init__(
+ self, appname=None, verbose=0, version=__version__, base_dir=None):
+
+ self._cfg_dir = None
+ self._cfg_file = None
+ self.config = None
+
+ description = "This application migrates a complete LDAP DIT to a new LDAP server. "
+ description += "During the migration all pointless ObljectClasses and "
+ description += "Atributes are removed from the entries."
+
+ self.src_server = None
+ self.source = None
+ self.tgt_server = None
+ self.target = None
+
+ self.object_classes = {}
+ self.attribute_types = {}
+
+ super(LDAPMigrationApplication, self).__init__(
+ appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+ description=description, initialized=False,
+ )
+
+ self.initialized = True
+
+ # -------------------------------------------------------------------------
+ @property
+ def cfg_dir(self):
+ """The directory containing the configuration file."""
+ return self._cfg_dir
+
+ # -------------------------------------------------------------------------
+ @property
+ def cfg_file(self):
+ """Configuration file."""
+ return self._cfg_file
+
+ # -------------------------------------------------------------------------
+ def as_dict(self, short=True):
+ """
+ Transforms the elements of the object into a dict
+
+ @param short: don't include local properties in resulting dict.
+ @type short: bool
+
+ @return: structure as dict
+ @rtype: dict
+ """
+
+ res = super(LDAPMigrationApplication, self).as_dict(short=short)
+ res['cfg_dir'] = self.cfg_dir
+ res['cfg_file'] = self.cfg_file
+
+ return res
+
+ # -------------------------------------------------------------------------
+ def init_arg_parser(self):
+ """
+ Public available method to initiate the argument parser.
+ """
+
+ self._cfg_dir = self.base_dir.joinpath('etc')
+ self._cfg_file = self.cfg_dir.joinpath(CFG_BASENAME)
+ default_cfg_file = copy.copy(self.cfg_file)
+
+ app_group = self.arg_parser.add_argument_group('Migration options')
+
+ app_group.add_argument(
+ '-T', '--timeout', dest='timeout', type=int, metavar='SECONDS',
+ help="The timeout in seconds for LDAP operations (default: {}).".format(
+ LDAPMigrationConfiguration.default_timeout),
+ )
+
+ app_group.add_argument(
+ '-c', '--config', '--config-file', dest='cfg_file', metavar='FILE',
+ action=CfgFileOptionAction,
+ help="Configuration file (default: {!r})".format(str(default_cfg_file))
+ )
+
+ # -------------------------------------------------------------------------
+ def _get_log_formatter(self, is_term=True):
+
+ # create formatter
+ if is_term:
+ format_str = ''
+ if self.verbose > 1:
+ format_str = '[%(asctime)s]: '
+ format_str += self.appname + ': '
+ else:
+ format_str = '[%(asctime)s]: ' + self.appname + ': '
+ if self.verbose:
+ if self.verbose > 1:
+ format_str += '%(name)s(%(lineno)d) %(funcName)s() '
+ else:
+ format_str += '%(name)s '
+ format_str += '%(levelname)s - %(message)s'
+ if is_term and self.terminal_has_colors:
+ formatter = ColoredFormatter(format_str)
+ else:
+ formatter = logging.Formatter(format_str)
+
+ return formatter
+
+ # -------------------------------------------------------------------------
+ def init_logging(self):
+ """
+ Initialize the logger object.
+ It creates a colored loghandler with all output to STDERR.
+ Maybe overridden in descendant classes.
+
+ @return: None
+ """
+
+ log_level = logging.INFO
+ if self.verbose:
+ log_level = logging.DEBUG
+ elif self.quiet:
+ log_level = logging.WARNING
+
+ root_logger = logging.getLogger()
+ root_logger.setLevel(log_level)
+
+ formatter = self._get_log_formatter()
+
+ # create log handler for console output
+ lh_console = logging.StreamHandler(sys.stderr)
+ lh_console.setLevel(log_level)
+ lh_console.setFormatter(formatter)
+
+ root_logger.addHandler(lh_console)
+
+ if self.verbose < 3:
+ paramiko_logger = logging.getLogger('paramiko.transport')
+ if self.verbose < 1:
+ paramiko_logger.setLevel(logging.WARNING)
+ else:
+ paramiko_logger.setLevel(logging.INFO)
+
+ return
+
+ # -------------------------------------------------------------------------
+ def post_init(self):
+ """
+ Method to execute before calling run(). Here could be done some
+ finishing actions after reading in commandline parameters,
+ configuration a.s.o.
+
+ This method could be overwritten by descendant classes, these
+ methhods should allways include a call to post_init() of the
+ parent class.
+
+ """
+
+ self.initialized = False
+
+ self.init_logging()
+
+ self.perform_arg_parser()
+
+ if self.args.cfg_file:
+ self._cfg_file = self.args.cfg_file
+ self._cfg_dir = self.cfg_file.parent
+
+ self.config = LDAPMigrationConfiguration(
+ appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+ config_file=self.cfg_file)
+
+ self.config.read()
+ if self.config.verbose > self.verbose:
+ self.verbose = self.config.verbose
+ self.config.initialized = True
+
+ if self.verbose > 3:
+ LOG.debug("Read configuration:\n{}".format(pp(self.config.as_dict())))
+
+ if self.args.timeout:
+ try:
+ self.config.timeout = self.args.timeout
+ except (ValueError, KeyError) as e:
+ msg = "Invalid value {!r} as timeout:".format(self.args.timeout) + ' ' + str(e)
+ LOG.error(msg)
+ print()
+ self.arg_parser.print_usage(sys.stdout)
+ self.exit(1)
+
+ self.initialized = True
+
+ # -------------------------------------------------------------------------
+ def _run(self):
+
+ LOG.info("Starting {a!r}, version {v!r} ...".format(
+ a=self.appname, v=self.version))
+
+ LOG.info("Ending {a!r}.".format(
+ a=self.appname, v=self.version))
+
+
+
+# =============================================================================
if __name__ == "__main__":
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+from __future__ import print_function
+
+import sys
+import locale
+
+if sys.version_info[0] != 3:
+ print("This script is intended to use with Python3.", file=sys.stderr)
+ print("You are using Python: {0}.{1}.{2}-{3}-{4}.\n".format(
+ *sys.version_info), file=sys.stderr)
+ sys.exit(1)
+
+if sys.version_info[1] < 4:
+ print("A minimal Python version of 3.4 is necessary to execute this script.", file=sys.stderr)
+ print("You are using Python: {0}.{1}.{2}-{3}-{4}.\n".format(
+ *sys.version_info), file=sys.stderr)
+ sys.exit(1)
+
+import os
+import logging
+
+# own modules:
+cur_dir = os.getcwd()
+base_dir = cur_dir
+
+if sys.argv[0] != '' and sys.argv[0] != '-c':
+ base_dir = os.path.dirname(os.path.realpath(sys.argv[0]))
+else:
+ base_dir = os.path.dirname(os.path.realpath(__file__))
+lib_dir = os.path.join(base_dir, 'lib')
+module_dir = os.path.join(lib_dir, 'ldap_migration')
+if os.path.exists(module_dir):
+ sys.path.insert(0, lib_dir)
+
+from ldap_migration import LDAPMigrationApplication
+
+log = logging.getLogger(__name__)
+
+__author__ = 'Frank Brehm <frank.brehm@pixelpark.com>'
+__copyright__ = '(C) 2020 by Frank Brehm, Digitas Pixelpark GmbH Berlin'
+
+appname = os.path.basename(sys.argv[0])
+
+locale.setlocale(locale.LC_ALL, '')
+
+app = LDAPMigrationApplication(appname=appname, base_dir=base_dir)
+app.initialized = True
+
+if app.verbose > 2:
+ print("{c}-Object:\n{a}".format(c=app.__class__.__name__, a=app))
+
+app()
+
+sys.exit(0)
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4