]> Frank Brehm's Git Trees - pixelpark/ldap-migration.git/commitdiff
Adding migrate-ldap, first runnable version
authorFrank Brehm <frank.brehm@pixelpark.com>
Thu, 12 Nov 2020 15:08:26 +0000 (16:08 +0100)
committerFrank Brehm <frank.brehm@pixelpark.com>
Thu, 12 Nov 2020 15:08:26 +0000 (16:08 +0100)
lib/ldap_migration/__init__.py
lib/ldap_migration/config.py
migrate-ldap [new file with mode: 0755]

index 64533fa5d710a13d6e9becf815ecd4965443d450..d0bd187278d2547d7d733206952c517dd79cbea4 100644 (file)
@@ -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__":
 
index c90e7606d260d040f83f45772c1542489693c16a..0fad765a2f67120d7e08882ccab89968a2626efb 100644 (file)
@@ -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 (executable)
index 0000000..92684c9
--- /dev/null
@@ -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 <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