From: Frank Brehm Date: Thu, 12 Nov 2020 15:08:26 +0000 (+0100) Subject: Adding migrate-ldap, first runnable version X-Git-Url: https://git.uhu-banane.net/?a=commitdiff_plain;h=3d685c1754859063518f159b24792d4cb3fad995;p=pixelpark%2Fldap-migration.git Adding migrate-ldap, first runnable version --- diff --git a/lib/ldap_migration/__init__.py b/lib/ldap_migration/__init__.py index 64533fa..d0bd187 100644 --- a/lib/ldap_migration/__init__.py +++ b/lib/ldap_migration/__init__.py @@ -16,7 +16,7 @@ import os # 3rd party modules -from ldap3 import LDAPException +from ldap3.core.exceptions import LDAPException # Own modules from fb_tools.colored import ColoredFormatter @@ -26,17 +26,18 @@ from fb_tools.app import BaseApplication, DirectoryOptionAction 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 @@ -44,7 +45,213 @@ class CommonLDAPMigrationError(FbAppError, LDAPException): 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__": diff --git a/lib/ldap_migration/config.py b/lib/ldap_migration/config.py index c90e760..0fad765 100644 --- a/lib/ldap_migration/config.py +++ b/lib/ldap_migration/config.py @@ -178,7 +178,7 @@ class LDAPMigrationConfiguration(BaseConfiguration): # ------------------------------------------------------------------------- def eval_config_section(self, config, section_name): - super(LDAPMigrationConfiguration,, self).eval_config_section(config, section_name) + super(LDAPMigrationConfiguration, self).eval_config_section(config, section_name) if section_name.lower() == 'source' or section_name.lower() == 'src': self._eval_config_source(config, section_name) @@ -202,7 +202,7 @@ class LDAPMigrationConfiguration(BaseConfiguration): for (key, value) in config.items(section_name): - if (key.lower() == 'server' or key.lower() == 'host') and and value.strip(): + if (key.lower() == 'server' or key.lower() == 'host') and value.strip(): self.src_server = value.strip().lower() continue @@ -240,7 +240,7 @@ class LDAPMigrationConfiguration(BaseConfiguration): for (key, value) in config.items(section_name): - if (key.lower() == 'server' or key.lower() == 'host') and and value.strip(): + if (key.lower() == 'server' or key.lower() == 'host') and value.strip(): self.tgt_server = value.strip().lower() continue diff --git a/migrate-ldap b/migrate-ldap new file mode 100755 index 0000000..92684c9 --- /dev/null +++ b/migrate-ldap @@ -0,0 +1,58 @@ +#!/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 ' +__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