]> Frank Brehm's Git Trees - pixelpark/create-vmware-tpl.git/commitdiff
Separating definition of LdapConnectionInfo and LdapConnectionDict into a separate...
authorFrank Brehm <frank.brehm@pixelpark.com>
Fri, 22 Sep 2023 10:15:40 +0000 (12:15 +0200)
committerFrank Brehm <frank.brehm@pixelpark.com>
Fri, 22 Sep 2023 10:15:40 +0000 (12:15 +0200)
lib/cr_vmware_tpl/__init__.py
lib/cr_vmware_tpl/config.py [deleted file]
lib/cr_vmware_tpl/config/__init__.py [new file with mode: 0644]
lib/cr_vmware_tpl/config/ldap.py [new file with mode: 0644]
lib/cr_vmware_tpl/handler.py

index 7e2532028babfb1fd6846a42697601cb53a56375..735a8dc621196ab982b88deae542c5fce27c2c17 100644 (file)
@@ -3,11 +3,22 @@
 
 import time
 
-__version__ = '2.8.0'
+__version__ = '2.8.1'
 
 DEFAULT_CONFIG_DIR = 'pixelpark'
 DEFAULT_DISTRO_ARCH = 'x86_64'
 MAX_PORT_NUMBER = (2 ** 16) - 1
+DEFAULT_PORT_LDAP = 389
+DEFAULT_PORT_LDAPS = 636
+DEFAULT_TIMEOUT = 20
+MAX_TIMEOUT = 3600
+
+DEFAULT_LDAP_ADMIN_FILTER = (
+    '(&(inetuserstatus=active)(mailuserstatus=active)(objectclass=pppixelaccount)(mail=*)'
+    '(sshPublicKey=*)'
+    '(memberOf=cn=Administratoren Pixelpark Berlin,ou=Groups,o=Pixelpark,o=isp))'
+)
+
 
 # -------------------------------------------------------------------------
 def print_section_start(name, header=None, collapsed=False):
diff --git a/lib/cr_vmware_tpl/config.py b/lib/cr_vmware_tpl/config.py
deleted file mode 100644 (file)
index 53d27d3..0000000
+++ /dev/null
@@ -1,1704 +0,0 @@
-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-"""
-@author: Frank Brehm
-@contact: frank.brehm@pixelpark.com
-@copyright: © 2018 by Frank Brehm, Berlin
-@summary: A module for providing a configuration
-"""
-from __future__ import absolute_import
-
-# Standard module
-import logging
-import re
-import copy
-import crypt
-import os
-import ipaddress
-
-# Third party modules
-from pathlib import Path
-
-# Own modules
-from fb_tools.common import is_sequence, pp, to_bool
-from fb_tools.obj import FbGenericBaseObject, FbBaseObject
-from fb_tools.collections import CIStringSet
-from fb_tools.multi_config import BaseMultiConfig
-from fb_tools.multi_config import DEFAULT_ENCODING
-from fb_tools.xlate import format_list
-
-from fb_vmware.config import VSPhereConfigInfo
-
-from . import DEFAULT_CONFIG_DIR, DEFAULT_DISTRO_ARCH, MAX_PORT_NUMBER
-
-from .errors import CrTplConfigError
-
-from .xlate import XLATOR
-
-__version__ = '2.3.3'
-LOG = logging.getLogger(__name__)
-
-_ = XLATOR.gettext
-ngettext = XLATOR.ngettext
-
-DEFAULT_PORT_LDAP = 389
-DEFAULT_PORT_LDAPS = 636
-DEFAULT_TIMEOUT = 20
-MAX_TIMEOUT = 3600
-DEFAULT_ADMIN_FILTER = (
-    '(&(inetuserstatus=active)(mailuserstatus=active)(objectclass=pppixelaccount)(mail=*)'
-    '(sshPublicKey=*)'
-    '(memberOf=cn=Administratoren Pixelpark Berlin,ou=Groups,o=Pixelpark,o=isp))'
-)
-
-
-# =============================================================================
-class LdapConnectionInfo(FbBaseObject):
-    """Encapsulating all necessary data to connect to a LDAP server."""
-
-    re_host_key = re.compile(r'^\s*(?:host|server)\s*$', re.IGNORECASE)
-    re_ldaps_key = re.compile(r'^\s*(?:use[_-]?)?(?:ldaps|ssl)\s*$', re.IGNORECASE)
-    re_port_key = re.compile(r'^\s*port\s*$', re.IGNORECASE)
-    re_base_dn_key = re.compile(r'^\s*base[_-]*dn\s*$', re.IGNORECASE)
-    re_bind_dn_key = re.compile(r'^\s*bind[_-]*dn\s*$', re.IGNORECASE)
-    re_bind_pw_key = re.compile(r'^\s*bind[_-]*pw\s*$', re.IGNORECASE)
-    re_admin_filter_key = re.compile(r'^\s*(?:admin[_-]?)?filter\s*$', re.IGNORECASE)
-
-    # -------------------------------------------------------------------------
-    def __init__(
-        self, appname=None, verbose=0, version=__version__, base_dir=None,
-            host=None, use_ldaps=False, port=DEFAULT_PORT_LDAP, base_dn=None,
-            bind_dn=None, bind_pw=None, admin_filter=None, initialized=False):
-
-        self._host = None
-        self._use_ldaps = False
-        self._port = DEFAULT_PORT_LDAP
-        self._base_dn = None
-        self._bind_dn = None
-        self._bind_pw = None
-        self._admin_filter = DEFAULT_ADMIN_FILTER
-
-        super(LdapConnectionInfo, self).__init__(
-            appname=appname, verbose=verbose, version=version, base_dir=base_dir,
-            initialized=False)
-
-        self.host = host
-        self.use_ldaps = use_ldaps
-        self.port = port
-        if base_dn:
-            self.base_dn = base_dn
-        self.bind_dn = bind_dn
-        self.bind_pw = bind_pw
-        if admin_filter:
-            self.admin_filter = admin_filter
-
-        if initialized:
-            self.initialized = True
-
-    # -------------------------------------------------------------------------
-    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(LdapConnectionInfo, self).as_dict(short=short)
-
-        res['host'] = self.host
-        res['use_ldaps'] = self.use_ldaps
-        res['port'] = self.port
-        res['base_dn'] = self.base_dn
-        res['bind_dn'] = self.bind_dn
-        res['bind_pw'] = None
-        res['schema'] = self.schema
-        res['url'] = self.url
-        res['admin_filter'] = self.admin_filter
-
-        if self.bind_pw:
-            if self.verbose > 4:
-                res['bind_pw'] = self.bind_pw
-            else:
-                res['bind_pw'] = '******'
-
-        return res
-
-    # -----------------------------------------------------------
-    @property
-    def host(self):
-        """The host name (or IP address) of the LDAP server."""
-        return self._host
-
-    @host.setter
-    def host(self, value):
-        if value is None or str(value).strip() == '':
-            self._host = None
-            return
-        self._host = str(value).strip().lower()
-
-    # -----------------------------------------------------------
-    @property
-    def use_ldaps(self):
-        """Should there be used LDAPS for communicating with the LDAP server?"""
-        return self._use_ldaps
-
-    @use_ldaps.setter
-    def use_ldaps(self, value):
-        self._use_ldaps = to_bool(value)
-
-    # -----------------------------------------------------------
-    @property
-    def port(self):
-        "The TCP port number of the LDAP server."
-        return self._port
-
-    @port.setter
-    def port(self, value):
-        v = int(value)
-        if v < 1 or v > MAX_PORT_NUMBER:
-            raise CrTplConfigError(_("Invalid port {!r} for LDAP server given.").format(value))
-        self._port = v
-
-    # -----------------------------------------------------------
-    @property
-    def base_dn(self):
-        """The DN used to connect to the LDAP server, anonymous bind is used, if
-            this DN is empty or None."""
-        return self._base_dn
-
-    @base_dn.setter
-    def base_dn(self, value):
-        if value is None or str(value).strip() == '':
-            msg = _("An empty Base DN for LDAP searches is not allowed.")
-            raise CrTplConfigError(msg)
-        self._base_dn = str(value).strip()
-
-    # -----------------------------------------------------------
-    @property
-    def bind_dn(self):
-        """The DN used to connect to the LDAP server, anonymous bind is used, if
-            this DN is empty or None."""
-        return self._bind_dn
-
-    @bind_dn.setter
-    def bind_dn(self, value):
-        if value is None or str(value).strip() == '':
-            self._bind_dn = None
-            return
-        self._bind_dn = str(value).strip()
-
-    # -----------------------------------------------------------
-    @property
-    def bind_pw(self):
-        """The password of the DN used to connect to the LDAP server."""
-        return self._bind_pw
-
-    @bind_pw.setter
-    def bind_pw(self, value):
-        if value is None or str(value).strip() == '':
-            self._bind_pw = None
-            return
-        self._bind_pw = str(value).strip()
-
-    # -----------------------------------------------------------
-    @property
-    def admin_filter(self):
-        """The LDAP filter to get  the list of administrators from LDAP."""
-        return self._admin_filter
-
-    @admin_filter.setter
-    def admin_filter(self, value):
-        if value is None or str(value).strip() == '':
-            self._admin_filter = None
-            return
-        self._admin_filter = str(value).strip()
-
-    # -----------------------------------------------------------
-    @property
-    def schema(self):
-        """The schema as part of the URL to connect to the LDAP server."""
-        if self.use_ldaps:
-            return 'ldaps'
-        return 'ldap'
-
-    # -----------------------------------------------------------
-    @property
-    def url(self):
-        """The URL, which ca be used to connect to the LDAP server."""
-        if not self.host:
-            return None
-
-        port = ''
-        if self.use_ldaps:
-            if self.port != DEFAULT_PORT_LDAPS:
-                port = ':{}'.format(self.port)
-        else:
-            if self.port != DEFAULT_PORT_LDAP:
-                port = ':{}'.format(self.port)
-
-        return '{s}://{h}{p}'.format(s=self.schema, h=self.host, p=port)
-
-    # -------------------------------------------------------------------------
-    def __repr__(self):
-        """Typecasting into a string for reproduction."""
-
-        out = "<%s(" % (self.__class__.__name__)
-
-        fields = []
-        fields.append("appname={!r}".format(self.appname))
-        fields.append("host={!r}".format(self.host))
-        fields.append("use_ldaps={!r}".format(self.use_ldaps))
-        fields.append("port={!r}".format(self.port))
-        fields.append("base_dn={!r}".format(self.base_dn))
-        fields.append("bind_dn={!r}".format(self.bind_dn))
-        fields.append("bind_pw={!r}".format(self.bind_pw))
-        fields.append("admin_filter={!r}".format(self.admin_filter))
-        fields.append("initialized={!r}".format(self.initialized))
-
-        out += ", ".join(fields) + ")>"
-        return out
-
-    # -------------------------------------------------------------------------
-    def __copy__(self):
-
-        new = self.__class__(
-            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir, host=self.host,
-            use_ldaps=self.use_ldaps, port=self.port, base_dn=self.base_dn, bind_dn=self.bind_dn,
-            bind_pw=self.bind_pw, admin_filter=self.admin_filter, initialized=self.initialized)
-
-        return new
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def init_from_config(cls, name, data, appname=None, verbose=0, base_dir=None):
-
-        new = cls(appname=appname, verbose=verbose, base_dir=base_dir)
-
-        s_name = "ldap:" + name
-        msg_invalid = _("Invalid value {val!r} in section {sec!r} for a LDAP {what}.")
-
-        for key in data.keys():
-            value = data[key]
-
-            if cls.re_host_key.match(key):
-                if value.strip():
-                    new.host = value
-                else:
-                    msg = msg_invalid.format(val=value, sec=s_name, what='host')
-                    LOG.error(msg)
-                continue
-
-            if cls.re_ldaps_key.match(key):
-                new.use_ldaps = value
-                continue
-
-            if cls.re_port_key.match(key):
-                port = DEFAULT_PORT_LDAP
-                try:
-                    port = int(value)
-                except (ValueError, TypeError) as e:
-                    msg = msg_invalid.format(val=value, sec=s_name, what='port')
-                    msg += ' ' + str(e)
-                    LOG.error(msg)
-                    continue
-                if port <= 0 or port > MAX_PORT_NUMBER:
-                    msg = msg_invalid.format(val=value, sec=s_name, what='port')
-                    LOG.error(msg)
-                    continue
-                new.port = port
-                continue
-
-            if cls.re_base_dn_key.match(key):
-                if value.strip():
-                    new.base_dn = value
-                else:
-                    msg = msg_invalid.format(val=value, sec=s_name, what='base_dn')
-                    LOG.error(msg)
-                continue
-
-            if cls.re_bind_dn_key.match(key):
-                new.bind_dn = value
-                continue
-
-            if cls.re_bind_pw_key.match(key):
-                new.bind_pw = value
-                continue
-
-            if cls.re_admin_filter_key.match(key):
-                new.admin_filter = value
-                continue
-
-            if key.lower() in ['is_admin', 'readonly', 'tier', 'sync-source']:
-                continue
-
-            msg = _("Unknown LDAP configuration key {key} found in section {sec!r}.").format(
-                key=key, sec=s_name)
-            LOG.error(msg)
-
-        new.initialized = True
-
-        return new
-
-
-# =============================================================================
-class CobblerDistroInfo(FbGenericBaseObject):
-    """Class for encapsulation all necessary data of a Repo definition in Cobbler."""
-
-    re_dashes = re.compile(r'[_-]')
-    valid_arch = (
-        'i386', 'x86_64', 'ia64', 'ppc', 'ppc64', 'ppc64el', 'ppc64le',
-        's390', 's390x', 'arm', 'aarch64')
-
-    # -------------------------------------------------------------------------
-    def __init__(
-            self, name, shortname=None, distro=None, description=None, arch=DEFAULT_DISTRO_ARCH,
-            ks_repo_url=None, packages=None, repos=None, snippets=None):
-
-        self._name = None
-        self._shortname = None
-        self._distro = None
-        self._description = None
-        self._arch = DEFAULT_DISTRO_ARCH
-        self._ks_repo_url = None
-        self._is_rhel = False
-        self._ks_template = 'el8-standard.ks'
-        self.packages = []
-        self.repos = CIStringSet()
-        self.snippets = CIStringSet()
-
-        self.name = name
-        self.shortname = shortname
-        self.distro = distro
-        self.description = description
-        self.arch = arch
-
-        if packages:
-            if is_sequence(packages):
-                for pkg in packages:
-                    if pkg not in packages:
-                        self.packages.append(pkg)
-            else:
-                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
-                raise TypeError(msg.format(p='packages', v=repos))
-
-        if repos:
-            if is_sequence(repos):
-                for repo in repos:
-                    self.repos.add(repo)
-            else:
-                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
-                raise TypeError(msg.format(p='repos', v=repos))
-
-        if snippets:
-            if is_sequence(repos):
-                self.snippets = copy.copy(snippets)
-                for snippet in snippets:
-                    self.snippets.add(snippet)
-            else:
-                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
-                raise TypeError(msg.format(p='snippets', v=snippets))
-
-    # -------------------------------------------------------------------------
-    @property
-    def name(self):
-        """The canonical name of the distribution."""
-        return getattr(self, '_name', None)
-
-    @name.setter
-    def name(self, value):
-
-        name = value.strip()
-        if name == '':
-            msg = _("The name of a Cobbler distro may not be empty.")
-            raise ValueError(msg)
-
-        self._name = name
-
-    # -------------------------------------------------------------------------
-    @property
-    def shortname(self):
-        """The short name of the distro, how to be used e.g. as part of the template name."""
-
-        shortname = getattr(self, '_shortname', None)
-        if shortname is None:
-            name = self.name
-            if name is None:
-                return None
-            shortname = self.re_dashes.sub('', name)
-        return shortname
-
-    @shortname.setter
-    def shortname(self, value):
-        if value is None:
-            self._shortname = None
-            return
-        shortname = value.strip()
-        if shortname == '':
-            self._shortname = None
-            return
-        self._shortname = shortname
-
-    # -------------------------------------------------------------------------
-    @property
-    def distro(self):
-        """The name of the underlaying (real) cobbler distro."""
-        return getattr(self, '_distro', None)
-
-    @distro.setter
-    def distro(self, value):
-        if value is None:
-            self._distro = None
-            return
-
-        dis = value.strip()
-        if dis == '':
-            self._distro = None
-            return
-
-        self._distro = dis
-
-    # -------------------------------------------------------------------------
-    @property
-    def arch(self):
-        """The name of the underlaying (real) cobbler distro."""
-        return getattr(self, '_arch', DEFAULT_DISTRO_ARCH)
-
-    @arch.setter
-    def arch(self, value):
-
-        arch = value.strip().lower()
-        if arch not in self.valid_arch:
-            msg = _(
-                "Invalid architecture {a!r} for distro {n!r} given. Valid architectures are {v}.")
-            msg = msg.format(a=value, n=self.name, v=format_list(self.valid_arch, do_repr=True))
-            raise ValueError(msg)
-
-        self._arch = arch
-
-    # -------------------------------------------------------------------------
-    @property
-    def description(self):
-        """The name of the underlaying (real) cobbler distro."""
-        return getattr(self, '_description', None)
-
-    @description.setter
-    def description(self, value):
-        if value is None:
-            self._description = None
-            return
-
-        desc = value.strip()
-        if desc == '':
-            self._description = None
-            return
-
-        self._description = desc
-
-    # -------------------------------------------------------------------------
-    @property
-    def ks_repo_url(self):
-        """The URL for the base installation repository."""
-        return getattr(self, '_ks_repo_url', None)
-
-    @ks_repo_url.setter
-    def ks_repo_url(self, value):
-        if value is None:
-            self._ks_repo_url = None
-            return
-
-        ks_repo_url = value.strip()
-        if ks_repo_url == '':
-            self._ks_repo_url = None
-            return
-
-        self._ks_repo_url = ks_repo_url
-
-    # -------------------------------------------------------------------------
-    @property
-    def is_rhel(self):
-        """Is the currwnt distro a RHEL distro?"""
-        return self._is_rhel
-
-    @is_rhel.setter
-    def is_rhel(self, value):
-        self._is_rhel = to_bool(value)
-
-    # -------------------------------------------------------------------------
-    @property
-    def ks_template(self):
-        """The filename below templates for generating the final kickstart file."""
-
-        return getattr(self, '_ks_template', 'el8-standard.ks')
-
-    @ks_template.setter
-    def ks_template(self, value):
-        if value is None:
-            return
-        template = value.strip()
-        if template == '':
-            return
-        self._ks_template = template
-
-    # -------------------------------------------------------------------------
-    @property
-    def repos_string(self):
-        """Returns all repos as a string of their space concatinated names."""
-        if self.repos:
-            return ' '.join(self.repos.as_list())
-        return ''
-
-    # -------------------------------------------------------------------------
-    def __repr__(self):
-        """Typecasting into a string for reproduction."""
-
-        out = "<%s()" % (self.__class__.__name__)
-
-        fields = []
-        fields.append("name={!r}".format(self.name))
-        fields.append("shortname={!r}".format(self._shortname))
-        fields.append("distro={!r}".format(self.distro))
-        fields.append("arch={!r}".format(self.arch))
-        fields.append("is_rhel={!r}".format(self.is_rhel))
-        fields.append("ks_template={!r}".format(self.ks_template))
-        fields.append("description={!r}".format(self.description))
-        fields.append("ks_repo_url={!r}".format(self.ks_repo_url))
-
-        out += ", ".join(fields) + ")>"
-
-        return out
-
-    # -------------------------------------------------------------------------
-    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(CobblerDistroInfo, self).as_dict(short=short)
-        res['arch'] = self.arch
-        res['description'] = self.description
-        res['distro'] = self.distro
-        res['is_rhel'] = self.is_rhel
-        res['ks_repo_url'] = self.ks_repo_url
-        res['ks_template'] = self.ks_template
-        res['name'] = self.name
-        res['repos_string'] = self.repos_string
-        res['shortname'] = self.shortname
-
-        return res
-
-    # -------------------------------------------------------------------------
-    def __eq__(self, other):
-
-        if not isinstance(other, CobblerDistroInfo):
-            return False
-
-        return self.name == other.name
-
-    # -------------------------------------------------------------------------
-    def __copy__(self):
-
-        new = self.__class__(
-            self.name, shortname=self.shortname, distro=self.distro, arch=self.arch,
-            ks_repo_url=self.ks_repo_url, description=self.description)
-
-        for package in self.packages:
-            new.packages.append(package)
-
-        for repo in self.repos:
-            new.repos.add(repo)
-
-        for snippet in self.snippets:
-            new.snippets.add(snippet)
-
-        return new
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def init_from_config(cls, name, data, verbose=0):
-
-        new = cls(name)
-
-        for key in data.keys():
-            value = data[key]
-
-            if key.lower() == 'shortname' and value.strip() != '':
-                new.shortname = value.strip()
-                continue
-
-            if key.lower() == 'distro' and value.strip() != '':
-                new.distro = value.strip()
-                continue
-
-            if key.lower() == 'description' and value.strip() != '':
-                new.description = value.strip()
-                continue
-
-            if key.lower() == 'arch' and value.strip() != '':
-                new.arch = value.strip()
-                continue
-
-            if key.lower() == 'is_rhel':
-                new.is_rhel = value
-                continue
-
-            if key.lower() == 'ks_repo_url' and value.strip() != '':
-                new.ks_repo_url = value.strip()
-                continue
-
-            if key.lower() == 'ks_template' and value.strip() != '':
-                new._ks_template = value.strip()
-                continue
-
-            if key.lower() == 'repos':
-                cls._update_repos(new, value)
-                continue
-
-            if key.lower() == 'packages':
-                cls._update_packages(new, value)
-                continue
-
-            if key.lower() == 'snippets':
-                cls._update_snippets(new, value)
-                continue
-
-            if verbose:
-                LOG.warn(_(
-                    "Found unknown config parameter {p!r} with value {v!r} in configuration "
-                    "of the Cobbler repository {r!r}.").format(p=key, v=value, r=name))
-
-        if verbose > 2:
-            msg = _("Found Cobbler repository configuration:") + '\n' + pp(new.as_dict())
-            LOG.debug(msg)
-
-        return new
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def _update_repos(cls, new, value):
-
-        if is_sequence(value):
-            for repo in value:
-                repo = repo.strip()
-                if repo != '':
-                    new.repos.add(repo)
-        elif value.strip() != '':
-            new.repos.add(value.strip())
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def _update_packages(cls, new, value):
-
-        if is_sequence(value):
-            for pkg in value:
-                pkg = pkg.strip()
-                if pkg != '' and pkg not in new.packages:
-                    new.packages.append(pkg)
-        elif value.strip() != '':
-            new.packages.add(value.strip())
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def _update_snippets(cls, new, value):
-
-        if is_sequence(value):
-            for snippet in value:
-                snippet = snippet.strip()
-                if snippet != '':
-                    new.snippets.add(snippet)
-        elif value.strip() != '':
-            new.snippets.add(value.strip())
-
-
-# =============================================================================
-class LdapConnectionDict(dict, FbGenericBaseObject):
-    """A dictionary containing LdapConnectionInfo as values and their names as keys."""
-
-    # -------------------------------------------------------------------------
-    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(LdapConnectionDict, self).as_dict(short=short)
-
-        for key in self.keys():
-            res[key] = self[key].as_dict(short=short)
-
-        return res
-
-    # -------------------------------------------------------------------------
-    def __copy__(self):
-
-        new = self.__class__()
-
-        for key in self.keys():
-            new[key] = copy.copy(self[key])
-
-        return new
-
-
-# =============================================================================
-class CrTplConfiguration(BaseMultiConfig):
-    """
-    A class for providing a configuration for the CrTplApplication class
-    and methods to read it from configuration files.
-    """
-
-    default_os_id = 'centos-stream-8'
-
-    default_vsphere_host = 'vcs01.ppbrln.internal'
-    default_vsphere_port = 443
-    default_vsphere_user = 'root'
-    default_vsphere_cluster = 'vmcc-l105-01'
-    default_dc = 'vmcc'
-    default_folder = 'templates'
-    default_template_name = default_os_id + '-template'
-    default_data_size_gb = 32.0
-    default_storage_cluster = 'ds-cluster-hdd-vmcc-l105-01'
-    default_num_cpus = 2
-    default_ram_mb = 4 * 1024
-    default_network = '192.168.88.0_23'
-    default_max_wait_for_general = 15
-    default_max_wait_for_shutdown = 600
-    default_max_wait_for_finish_install = 60 * 60
-    default_max_nr_templates_stay = 4
-    default_vmware_cfg_version = 'vmx-15'
-    default_os_version = 'centos8_64Guest'
-    min_max_wait_for_finish_general = 2
-    min_max_wait_for_finish_install = 3 * 60
-    max_max_wait_for_finish_general = 60 * 60
-    max_max_wait_for_finish_install = 24 * 60 * 60
-    limit_max_nr_templates_stay = 100
-    default_root_password = 'testtest'
-
-    default_tpl_vm_domain = 'pixelpark.com'
-
-    default_cobbler_bin = '/bin/cobbler'
-    default_cobbler_host = 'cobbler.pixelpark.com'
-    default_cobbler_ssh_port = 22
-    default_cobbler_ssh_user = 'root'
-    default_cobbler_ssh_timeout = 30
-    default_cobbler_distro = 'CentOS-8.2-x86_64'
-    default_cobbler_rootdir = Path('/var/lib/cobbler')
-    default_cobbler_profile_repos = ['pp-centos8-baseos']
-    default_cobbler_nameservers = [
-        '93.188.104.82',
-        '93.188.109.12',
-        '217.66.52.10',
-    ]
-    default_cobbler_dns_search = [
-        'pixelpark.net',
-        'pixelpark.com',
-        'pixelpark.de',
-    ]
-    resolv_conf = Path('/etc/resolv.conf')
-    evaluated_resolv_conf = False
-
-    default_cobbler_ws_base_url = 'http://cobbler.pixelpark.com'
-    default_cobbler_ws_docroot = Path('/var/www/html')
-    default_cobbler_ws_rel_filesdir = Path('custom/vmware-template-files')
-
-    default_cobbler2_templates_dir_rel = 'kickstarts'
-    default_cobbler2_collections_dir_rel = 'config'
-    default_cobbler2_profiles_dir_rel = 'profiles.d'
-
-    default_cobbler3_templates_dir_rel = 'templates'
-    default_cobbler3_collections_dir_rel = 'collections'
-    default_cobbler3_profiles_dir_rel = 'profiles'
-
-    ssh_privkey = 'id_rsa_cr_vmw_tpl'
-
-    mac_address_template = "00:16:3e:53:{:02x}:{:02x}"
-
-    method_list = {}
-    for method in crypt.methods:
-        mname = method.name.lower()
-        method_list[mname] = method
-
-    valid_system_status = ('development', 'testing', 'acceptance', 'production')
-    default_system_status = 'development'
-
-    default_cobbler_profile = 'vmware-template.' + default_os_id + '.' + default_system_status
-
-    default_swap_size_mb = 512
-
-    default_ldap_server = 'prd-ds.pixelpark.com'
-    use_ssl_on_default = True
-    default_ldap_port = DEFAULT_PORT_LDAPS
-    default_base_dn = 'o=isp'
-    default_bind_dn = 'uid=readonly,ou=People,o=isp'
-
-    re_ldap_section_w_name = re.compile(r'^\s*ldap\s*:\s*(\S+)')
-    re_resolv_ns_entry = re.compile(r'^\s*nameserver\s+(\S+)')
-
-    # -------------------------------------------------------------------------
-    def __init__(
-        self, appname=None, verbose=0, version=__version__, base_dir=None,
-            append_appname_to_stems=True, additional_stems=None, config_dir=DEFAULT_CONFIG_DIR,
-            additional_config_file=None, additional_cfgdirs=None, encoding=DEFAULT_ENCODING,
-            ensure_privacy=False, use_chardet=True, initialized=False):
-
-        self.eval_resolv_conf()
-
-        add_stems = []
-        if additional_stems:
-            if is_sequence(additional_stems):
-                for stem in additional_stems:
-                    add_stems.append(stem)
-            else:
-                add_stems.append(additional_stems)
-
-        if 'mail' not in add_stems:
-            add_stems.append('mail')
-        if 'cobbler-repos' not in add_stems:
-            add_stems.append('cobbler-distros')
-        if 'ldap' not in add_stems:
-            add_stems.append('ldap')
-
-        self.os_id = self.default_os_id
-
-        self.vsphere_cluster = self.default_vsphere_cluster
-        self.folder = self.default_folder
-        self.template_name = self.default_template_name
-        self.data_size_gb = self.default_data_size_gb
-        self.num_cpus = self.default_num_cpus
-        self.ram_mb = self.default_ram_mb
-        self.swap_size_mb = self.default_swap_size_mb
-        self.network = self.default_network
-        self.max_wait_for_general = self.default_max_wait_for_general
-        self.max_wait_for_create_vm = None
-        self.max_wait_for_poweron_vm = None
-        self.max_wait_for_shutdown_vm = self.default_max_wait_for_shutdown
-        self.max_wait_for_purge_vm = None
-        self.max_wait_for_finish_install = self.default_max_wait_for_finish_install
-        self.max_nr_templates_stay = self.default_max_nr_templates_stay
-        self.vmware_cfg_version = self.default_vmware_cfg_version
-        self.os_version = self.default_os_version
-
-        self.storage_cluster = self.default_storage_cluster
-
-        self.tpl_vm_domain = self.default_tpl_vm_domain
-
-        self.cobbler_profile_given = False
-        self.template_name_given = False
-
-        self._root_password = self.default_root_password
-
-        self.private_ssh_key = None
-
-        self._cobbler_major_version = 3
-
-        self.cobbler_bin = self.default_cobbler_bin
-        self.cobbler_distro = self.default_cobbler_distro
-        self.cobbler_host = self.default_cobbler_host
-        self.cobbler_ssh_port = self.default_cobbler_ssh_port
-        self.cobbler_ssh_user = self.default_cobbler_ssh_user
-        self.cobbler_ssh_timeout = self.default_cobbler_ssh_timeout
-        self.cobbler_rootdir = self.default_cobbler_rootdir
-        self.cobbler_profile = self.default_cobbler_profile
-        self.cobbler_profile_repos = copy.copy(self.default_cobbler_profile_repos)
-        self.cobbler_nameservers = copy.copy(self.default_cobbler_nameservers)
-        self.cobbler_dns_search = copy.copy(self.default_cobbler_dns_search)
-        self.cobbler_ws_base_url = self.default_cobbler_ws_base_url
-        self.cobbler_ws_docroot = self.default_cobbler_ws_docroot
-        self.cobbler_ws_rel_filesdir = self.default_cobbler_ws_rel_filesdir
-        self.cobbler_distros = {}
-        self.cobbler_repos = {}
-
-        self.current_distro = None
-
-        self.system_status = self.default_system_status
-
-        self.excluded_datastores = []
-
-        if config_dir is None:
-            config_dir = DEFAULT_CONFIG_DIR
-        LOG.debug("Config dir: {!r}.".format(config_dir))
-
-        self.ldap_timeout = DEFAULT_TIMEOUT
-
-        super(CrTplConfiguration, self).__init__(
-            appname=appname, verbose=verbose, version=version, base_dir=base_dir,
-            append_appname_to_stems=append_appname_to_stems, config_dir=config_dir,
-            additional_stems=add_stems, additional_config_file=additional_config_file,
-            additional_cfgdirs=additional_cfgdirs, encoding=encoding, use_chardet=use_chardet,
-            ensure_privacy=ensure_privacy, initialized=False,
-        )
-
-        self.vsphere_info = VSPhereConfigInfo(
-            host=self.default_vsphere_host, port=self.default_vsphere_port, dc=self.default_dc,
-            use_https=True, user=self.default_vsphere_user,
-            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
-            initialized=True)
-
-        self.private_ssh_key = str(self.base_dir.joinpath('keys', self.ssh_privkey))
-
-        self.ldap_connection = LdapConnectionDict()
-
-        default_connection = LdapConnectionInfo(
-            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
-            host=self.default_ldap_server, use_ldaps=self.use_ssl_on_default,
-            port=self.default_ldap_port, base_dn=self.default_base_dn,
-            bind_dn=self.default_bind_dn, initialized=False)
-
-        self.ldap_connection['default'] = default_connection
-
-        if initialized:
-            self.initialized = True
-
-    # -------------------------------------------------------------------------
-    @property
-    def cobbler_major_version(self):
-        """The major version of the used cobbler installation. Valid values are 2 and 3."""
-        return self._cobbler_major_version
-
-    @cobbler_major_version.setter
-    def cobbler_major_version(self, value):
-        if value not in (2, 3):
-            msg = _("Unsupported version {ver!r} of {co}, valid versions of {co} are {valid}.")
-            msg = msg.format(ver=value, co='Cobbler', valid=format_list(['2', '3']))
-            raise CrTplConfigError(msg)
-
-        self._cobbler_major_version = value
-
-    # -------------------------------------------------------------------------
-    @property
-    def cobbler_collections_dir(self):
-        """The absolute pathname of all Cobbler collections."""
-        if self.cobbler_major_version == 2:
-            return self.cobbler_rootdir / self.default_cobbler2_collections_dir_rel
-        return self.cobbler_rootdir / self.default_cobbler3_collections_dir_rel
-
-    # -------------------------------------------------------------------------
-    @property
-    def cobbler_ks_dir(self):
-        """The absolute pathname of the directory of the kickstart/template/autoinstall files."""
-        if self.cobbler_major_version == 2:
-            return self.cobbler_rootdir / self.default_cobbler2_templates_dir_rel
-        return self.cobbler_rootdir / self.default_cobbler3_templates_dir_rel
-
-    # -------------------------------------------------------------------------
-    @property
-    def cobbler_profile_dir(self):
-        """The absolute pathname of the directory of Cobbler profile configuration files."""
-        if self.cobbler_major_version == 2:
-            return self.cobbler_collections_dir / self.default_cobbler2_profiles_dir_rel
-        return self.cobbler_collections_dir / self.default_cobbler3_profiles_dir_rel
-
-    # -------------------------------------------------------------------------
-    @property
-    def cobbler_profile_ks(self):
-        """The absolute pathname of the profile kickstart file."""
-        return self.cobbler_ks_dir / ('profile.' + self.cobbler_profile + '.ks')
-
-    # -------------------------------------------------------------------------
-    @property
-    def data_size_mb(self):
-        """Size of template volume in MiB."""
-        return int(self.data_size_gb * 1024.0)
-
-    # -------------------------------------------------------------------------
-    @property
-    def data_size_kb(self):
-        """Size of template volume in KiB."""
-        return self.data_size_mb * 1024
-
-    # -------------------------------------------------------------------------
-    @property
-    def data_size(self):
-        """Size of template volume in Bytes."""
-        return self.data_size_mb * 1024 * 1024
-
-    # -------------------------------------------------------------------------
-    @property
-    def ram_gb(self):
-        """Size of RAM in GiB."""
-        return float(self.ram_mb) / 1024
-
-    # -------------------------------------------------------------------------
-    @property
-    def root_password(self):
-        """The root password of the VM to create."""
-        return self._root_password
-
-    # -------------------------------------------------------------------------
-    @property
-    def system_ks(self):
-        """The path to the system kickstart file."""
-        ks_base = 'template-' + self.os_id + '-' + self.system_status + '.ks'
-        return self.cobbler_ks_dir / ks_base
-
-    # -------------------------------------------------------------------------
-    @property
-    def snippets_dir(self):
-        """The path to the snippets dirctory, depending of the system status."""
-        return self.cobbler_rootdir / 'snippets' / 'per_status' / self.system_status
-
-    # -------------------------------------------------------------------------
-    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(CrTplConfiguration, self).as_dict(short=short)
-        res['cobbler_major_version'] = self.cobbler_major_version
-        res['cobbler_collections_dir'] = self.cobbler_collections_dir
-        res['cobbler_ks_dir'] = self.cobbler_ks_dir
-        res['cobbler_profile_dir'] = self.cobbler_profile_dir
-        res['cobbler_profile_ks'] = self.cobbler_profile_ks
-        res['data_size_mb'] = self.data_size_mb
-        res['data_size_kb'] = self.data_size_kb
-        res['data_size'] = self.data_size
-        res['default_cobbler_nameservers'] = self.default_cobbler_nameservers
-        res['ram_gb'] = self.ram_gb
-        res['system_ks'] = self.system_ks
-        res['snippets_dir'] = self.snippets_dir
-
-        res['cobbler_distros'] = {}
-        for distro in self.cobbler_distros.keys():
-            res['cobbler_distros'][distro] = self.cobbler_distros[distro].as_dict(short=short)
-
-        res['root_password'] = None
-        if self.root_password:
-            if self.verbose > 4:
-                res['root_password'] = self.root_password
-            else:
-                res['root_password'] = '********'
-
-        return res
-
-    # -------------------------------------------------------------------------
-    def eval(self):
-        """Evaluating read configuration and storing them in object properties."""
-
-        super(CrTplConfiguration, self).eval()
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking for unconfigured options ..."))
-
-        if self.max_wait_for_create_vm is None:
-            self.max_wait_for_create_vm = self.max_wait_for_general
-        if self.max_wait_for_poweron_vm is None:
-            self.max_wait_for_poweron_vm = self.max_wait_for_general
-        if self.max_wait_for_purge_vm is None:
-            self.max_wait_for_purge_vm = self.max_wait_for_general
-        if self.max_wait_for_shutdown_vm is None:
-            self.max_wait_for_shutdown_vm = self.max_wait_for_general
-
-        if not self.template_name_given:
-            self.template_name = self.os_id + '-template'
-
-        if not self.cobbler_profile_given:
-            self.cobbler_profile = 'vmware-template.' + self.os_id + '.' + self.system_status
-
-        self.verify_cobbler_distros()
-
-        if not self.template_name_given:
-            self.template_name = self.current_distro.shortname + '-template'
-
-    # -------------------------------------------------------------------------
-    @classmethod
-    def eval_resolv_conf(cls):
-
-        if cls.evaluated_resolv_conf:
-            return True
-
-        if not cls.resolv_conf.exists():
-            LOG.error(_("File {!r} not found on current host.").format(str(cls.resolv_conf)))
-            cls.evaluated_resolv_conf = True
-            return False
-
-        if not cls.resolv_conf.is_file():
-            LOG.error(_("Path {!r} is not a regular file.").format(str(cls.resolv_conf)))
-            cls.evaluated_resolv_conf = True
-            return False
-
-        if not os.access(cls.resolv_conf, os.R_OK):
-            LOG.error(_("File {!r} is not readable.").format(str(cls.resolv_conf)))
-            cls.evaluated_resolv_conf = True
-            return False
-
-        LOG.info(_("Evaluating {!r} for nameservers.").format(str(cls.resolv_conf)))
-
-        nameservers = []
-        file_content = cls.resolv_conf.read_text()
-        lines = file_content.splitlines()
-
-        for line in lines:
-            match = cls.re_resolv_ns_entry.match(line)
-            if match:
-                try:
-                    ns_address = ipaddress.ip_address(match.group(1))
-                    nameservers.append(str(ns_address))
-                except ValueError as e:
-                    msg = _("Found invalid IP address {addr!r} as a nameserver in {file!r}:")
-                    msg = msg.format(addr=match.group(1), file=str(cls.resolv_conf))
-                    msg += ' ' + str(e)
-                    LOG.warn(msg)
-
-        cls.evaluated_resolv_conf = True
-        msg = _("Found nameservers in {!r}:").format(str(cls.resolv_conf))
-        msg += ' {}'.format(pp(nameservers))
-        LOG.debug(msg)
-
-        if len(nameservers):
-            cls.default_cobbler_nameservers = nameservers
-            return True
-
-        return False
-
-    # -------------------------------------------------------------------------
-    def verify_cobbler_distros(self):
-
-        LOG.debug(_("Verifying cobbler distros ..."))
-
-        if not len(self.cobbler_distros):
-            msg = _("Did not found configured Cobbler distros.")
-            raise CrTplConfigError(msg)
-
-        for distro_id in self.cobbler_distros.keys():
-            distro = self.cobbler_distros[distro_id]
-
-            if not distro.distro:
-                msg = _("Did not found distro of configured Cobbler distro {!r}.").format(
-                    distro_id)
-                raise CrTplConfigError(msg)
-
-            if not distro.ks_repo_url:
-                msg = _(
-                    "Did not found the base install repo URL of configured Cobbler "
-                    "distro {!r}.").format(distro_id)
-                raise CrTplConfigError(msg)
-
-            if not len(distro.repos):
-                msg = _(
-                    "Did not found repo definitions for configured Cobbler "
-                    "distro {!r}.").format(distro_id)
-                LOG.warn(msg)
-
-            if not distro.description:
-                distro.description = distro_id
-
-        LOG.debug(_("Searching for distro with ID {!r} ...").format(self.os_id))
-
-        if self.os_id not in self.cobbler_distros:
-            msg = _("Did not found distro {!r} in configured Cobbler distros.").format(self.os_id)
-            raise CrTplConfigError(msg)
-
-        self.current_distro = self.cobbler_distros[self.os_id]
-        self.cobbler_distro = self.current_distro.distro
-
-        LOG.info(_("Using OS {os!r} with cobbler distro {di!r}.").format(
-            os=self.os_id, di=self.cobbler_distro))
-
-    # -------------------------------------------------------------------------
-    def eval_section(self, section_name):
-
-        re_cobbler_distros = re.compile(r'^\s*cobbler[_-]?distros\s*$', re.IGNORECASE)
-        re_cobbler_repos = re.compile(r'^\s*cobbler[_-]?repos\s*$', re.IGNORECASE)
-
-        sn = section_name.lower()
-        section = self.cfg[section_name]
-
-        LOG.debug(_("Evaluating section {!r} ...").format(section_name))
-        if self.verbose > 2:
-            LOG.debug(_("Content of section:") + '\n' + pp(section))
-
-        super(CrTplConfiguration, self).eval_section(section_name)
-
-        if sn == 'vsphere':
-            self._eval_config_vsphere(section_name, section)
-            return
-
-        if sn == 'template':
-            self._eval_config_template(section_name, section)
-            return
-
-        if sn == 'timeouts':
-            self._eval_config_timeouts(section_name, section)
-            return
-
-        if sn == 'cobbler':
-            self._eval_config_cobbler(section_name, section)
-            return
-
-        if re_cobbler_distros.match(section_name):
-            self._eval_cobbler_distros(section_name, section)
-            return
-
-        if re_cobbler_repos.match(section_name):
-            self._eval_cobbler_repos(section_name, section)
-            return
-
-        if sn == 'ldap':
-            for key in section.keys():
-                sub = section[key]
-                if key.lower().strip() == 'timeout':
-                    self._eval_ldap_timeout(sub)
-                    continue
-                self._eval_ldap_connection(key, sub)
-            return
-
-        match = self.re_ldap_section_w_name.match(sn)
-        if match:
-            connection_name = match.group(1)
-            self._eval_ldap_connection(connection_name, section)
-            return
-
-        if self.verbose > 1:
-            LOG.debug(_("Unhandled configuration section {!r}.").format(section_name))
-
-    # -------------------------------------------------------------------------
-    def _eval_ldap_timeout(self, value):
-
-        timeout = DEFAULT_TIMEOUT
-        msg_invalid = _("Value {!r} for a timeout is invalid.")
-
-        try:
-            timeout = int(value)
-        except (ValueError, TypeError) as e:
-            msg = msg_invalid.format(value)
-            msg += ': ' + str(e)
-            LOG.error(msg)
-            return
-        if timeout <= 0 or timeout > MAX_TIMEOUT:
-            msg = msg_invalid.format(value)
-            LOG.error(msg)
-            return
-
-        self.ldap_timeout = timeout
-
-    # -------------------------------------------------------------------------
-    def _eval_ldap_connection(self, connection_name, section):
-
-        connection = LdapConnectionInfo.init_from_config(
-            connection_name, section,
-            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
-        )
-
-        self.ldap_connection[connection_name] = connection
-
-    # -------------------------------------------------------------------------
-    def _eval_config_vsphere(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        re_excl_ds = re.compile(r'^\s*excluded?[-_]datastores?\s*$', re.IGNORECASE)
-        re_split_ds = re.compile(r'[,;\s]+')
-        re_storage_cluster = re.compile(r'^\s*storage[-_]?cluster\s*$', re.IGNORECASE)
-
-        for key in section.keys():
-            value = section[key]
-
-            if key.lower() == 'host':
-                self.vsphere_info.host = value
-                continue
-            elif key.lower() == 'port':
-                self.vsphere_info.port = value
-                continue
-            elif key.lower() == 'user':
-                self.vsphere_info.user = value
-                continue
-            elif key.lower() == 'password':
-                self.vsphere_info.password = value
-                continue
-            elif key.lower() == 'cluster':
-                self.vsphere_cluster = value
-                continue
-            elif key.lower() == 'folder':
-                self.folder = value
-            elif key.lower() == 'dc':
-                self.vsphere_info.dc = value
-
-            elif key.lower() == 'max_nr_templates_stay':
-                v = int(value)
-                if v < 1:
-                    LOG.error(_(
-                        "Value {val} for {p} is less than {minval}, using {default}.").format(
-                        val=v, minval=1, p='max_nr_templates_stay',
-                        default=self.default_max_nr_templates_stay))
-                elif v >= 100:
-                    LOG.error(_(
-                        "Value {val} for {p} is greater than {maxval}, using {default}.").format(
-                        val=v, maxval=100, p='max_nr_templates_stay',
-                        default=self.default_max_nr_templates_stay))
-                else:
-                    self.max_nr_templates_stay = v
-
-            elif re_excl_ds.search(key):
-                datastores = re_split_ds.split(value.strip())
-                self.excluded_datastores = datastores
-
-            elif re_storage_cluster.search(key):
-                cl_name = value.strip()
-                if cl_name:
-                    self.storage_cluster = cl_name
-                else:
-                    self.storage_cluster = None
-
-        return
-
-    # -------------------------------------------------------------------------
-    def _eval_config_template(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        re_os_id = re.compile(r'^\s*os[-_]?id\s*$', re.IGNORECASE)
-        re_os_id_subst = re.compile(r'[^a-z0-9_-]+', re.IGNORECASE)
-        re_vm_domain = re.compile(r'^\s*(?:vm[-_]?)?domain\s*$', re.IGNORECASE)
-        re_root_pwd = re.compile(r'^\s*root[-_]?password\s*$', re.IGNORECASE)
-        re_swap_space = re.compile(r'^\s*swap[-_]?space(?:[-_]?mb)?\s*$', re.IGNORECASE)
-
-        for key in section.keys():
-            value = section[key]
-
-            if re_os_id.match(key):
-                v = value.strip().lower()
-                v = re_os_id_subst.sub('', v)
-                if v:
-                    self.os_id = v
-                continue
-            if key.lower() == 'name':
-                self.template_name = value
-                self.template_name_given = True
-                continue
-            if re_vm_domain.match(key) and value.strip():
-                self.tpl_vm_domain = value.strip().lower()
-                continue
-            if key.lower() == 'data_size_gb':
-                self.data_size_gb = float(value)
-                continue
-            if key.lower() == 'data_size_mb':
-                self.data_size_gb = float(value) / 1024.0
-                continue
-            if key.lower() == 'data_size_kb':
-                self.data_size_gb = float(value) / 1024.0 / 1024.0
-                continue
-            if key.lower() == 'data_size':
-                self.data_size_gb = float(value) / 1024.0 / 1024.0 / 1024.0
-                continue
-            if key.lower() == 'num_cpus':
-                self.num_cpus = int(value)
-                continue
-            if key.lower() == 'ram_gb':
-                self.ram_mb = int(float(value) * 1024.0)
-                continue
-            if key.lower() == 'ram_mb':
-                self.ram_mb = int(value)
-                continue
-            if key.lower() == 'network':
-                self.network = value.strip()
-                continue
-            if key.lower() == 'vmware_cfg_version':
-                self.vmware_cfg_version = value.strip()
-                continue
-            if key.lower() == 'os_version':
-                self.os_version = value.strip()
-                continue
-            if re_root_pwd.match(key) and value.strip():
-                self._root_password = value.strip()
-                continue
-            if re_swap_space.match(key) and value.strip():
-                self.swap_size_mb = int(value)
-                continue
-
-        return
-
-    # -------------------------------------------------------------------------
-    def _eval_timeout_value(self, prop_name, value, min_val, max_val, default_val):
-
-        if self.verbose > 2:
-            LOG.debug(_("Checking value {v!r} for {p} ...").format(v=value, p=prop_name))
-        if self.verbose > 3:
-            LOG.debug(_(
-                "Minimal value: {min_val}, maximum value: {max_val}, "
-                "default value: {def_val}.").format(
-                min_val=min_val, max_val=max_val, def_val=default_val))
-
-        v = float(value)
-
-        if v < min_val:
-            msg = _(
-                "Value {val} for {prop} is less than {min_val}, "
-                "using {def_val} seconds.").format(val=v, min_val=min_val, def_val=default_val)
-            LOG.error(msg)
-            return
-
-        if v < min_val:
-            msg = _(
-                "Value {val} for {prop} is greater than {max_val}, "
-                "using {def_val} seconds.").format(val=v, max_val=max_val, def_val=default_val)
-            LOG.error(msg)
-            return
-
-        if self.verbose > 2:
-            msg = _("Setting timeout {p!r} to {v:0.1f} seconds.").format(p=prop_name, v=v)
-            LOG.debug(msg)
-        setattr(self, prop_name, v)
-
-    # -------------------------------------------------------------------------
-    def _eval_config_timeouts(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        for key in section.keys():
-            value = section[key]
-
-            if key.lower() == 'max_wait_for_general':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_general', value=value,
-                    min_val=self.min_max_wait_for_finish_general,
-                    max_val=self.max_max_wait_for_finish_general,
-                    default_val=self.default_max_wait_for_general)
-                continue
-
-            if key.lower() == 'max_wait_for_create_vm':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_create_vm', value=value,
-                    min_val=self.min_max_wait_for_finish_general,
-                    max_val=self.max_max_wait_for_finish_general,
-                    default_val=self.max_wait_for_general)
-                continue
-            elif key.lower() == 'max_wait_for_poweron_vm':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_poweron_vm', value=value,
-                    min_val=self.min_max_wait_for_finish_general,
-                    max_val=self.max_max_wait_for_finish_general,
-                    default_val=self.max_wait_for_general)
-                continue
-            elif key.lower() == 'max_wait_for_shutdown_vm':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_shutdown_vm', value=value,
-                    min_val=self.min_max_wait_for_finish_general,
-                    max_val=self.max_max_wait_for_finish_general,
-                    default_val=self.default_max_wait_for_shutdown)
-                continue
-            elif key.lower() == 'max_wait_for_purge_vm':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_purge_vm', value=value,
-                    min_val=self.min_max_wait_for_finish_general,
-                    max_val=self.max_max_wait_for_finish_general,
-                    default_val=self.max_wait_for_general)
-                continue
-            elif key.lower() == 'max_wait_for_finish_install':
-                self._eval_timeout_value(
-                    prop_name='max_wait_for_finish_install', value=value,
-                    min_val=self.min_max_wait_for_finish_install,
-                    max_val=self.max_max_wait_for_finish_install,
-                    default_val=self.default_max_wait_for_finish_install)
-                continue
-
-    # -------------------------------------------------------------------------
-    def _eval_config_cobbler(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        re_port_key = re.compile(r'^\s*ssh[-_]?port\s*$', re.IGNORECASE)
-        re_user_key = re.compile(r'^\s*ssh[-_]?user\s*$', re.IGNORECASE)
-        re_timeout_key = re.compile(r'^\s*ssh[-_]?timeout\s*$', re.IGNORECASE)
-        re_rootdir_key = re.compile(r'^\s*root[-_]?dir(?:ectory)?\s*$', re.IGNORECASE)
-        re_split_values = re.compile(r'[,;\s]+')
-        re_pr_repos_key = re.compile(r'^\s*profile[-_]?repos?\s*$', re.IGNORECASE)
-        re_nameserver_key = re.compile(r'^\s*nameservers?\s*$', re.IGNORECASE)
-        re_dns_search_key = re.compile(r'^\s*dns[-_]?search\s*$', re.IGNORECASE)
-        re_ws_base_url_key = re.compile(
-            r'^\s*(?:ws|webserver)[-_]?base[-_]?url\s*$', re.IGNORECASE)
-        re_ws_docroot_key = re.compile(
-            r'^\s*(?:ws|webserver)[-_]?docroot\s*$', re.IGNORECASE)
-        re_ws_rel_filesdir_key = re.compile(
-            r'^\s*(?:ws|webserver)[-_]?rel(?:ative)?[-_]?filesdir\s*$', re.IGNORECASE)
-        re_system_status = re.compile(r'^\s*system[-_]?status\s*$', re.IGNORECASE)
-        re_cobbler_bin_key = re.compile(r'^\s*(?:cobbler[_-]?)?bin\s*$', re.IGNORECASE)
-
-        for key in section.keys():
-            value = section[key]
-
-            if key.lower() == 'distro' and value.strip() != '':
-                self.cobbler_distro = value.strip()
-                continue
-            if key.lower() == 'host' and value.strip() != '':
-                self.cobbler_host = value.strip().lower()
-                continue
-            if re_port_key.match(key) and value.strip() != '':
-                self.cobbler_ssh_port = int(value)
-                continue
-            if re_user_key.match(key):
-                self.cobbler_ssh_user = value.strip().lower()
-                continue
-            if re_timeout_key.match(key):
-                self.cobbler_ssh_timeout = int(value)
-                continue
-            if re_cobbler_bin_key.match(key) and value.strip() != '':
-                self.cobbler_bin = value.strip()
-                continue
-            if re_rootdir_key.match(key):
-                dpath = Path(value)
-                if dpath.is_absolute():
-                    self.cobbler_rootdir = Path(value)
-                else:
-                    msg = _("Path for {what} {path!r} is not absolute.").format(
-                        what=_("Cobbler root directory"), path=str(dpath))
-                    LOG.error(msg)
-                continue
-            if key.lower() == 'profile' and value.strip() != '':
-                self.cobbler_profile = value.strip().lower()
-                self.cobbler_profile_given = True
-                continue
-            if re_pr_repos_key.match(key) and value.strip() != '':
-                self.cobbler_profile_repos = re_split_values.split(value.strip())
-                continue
-            if re_nameserver_key.match(key) and value.strip() != '':
-                self.cobbler_nameservers = re_split_values.split(value.strip().lower())
-                continue
-            if re_dns_search_key.match(key) and value.strip() != '':
-                self.cobbler_dns_search = re_split_values.split(value.strip().lower())
-                continue
-            if re_ws_base_url_key.match(key) and value.strip() != '':
-                self.cobbler_ws_base_url = value.strip().lower()
-                continue
-            if re_ws_docroot_key.match(key) and value.strip() != '':
-                dpath = Path(value.strip())
-                if dpath.is_absolute():
-                    self.cobbler_ws_docroot = dpath
-                else:
-                    msg = _("Path for {what} {path!r} is not absolute.").format(
-                        what=_("Webserver document root"), path=str(dpath))
-                    LOG.error(msg)
-                continue
-            if re_ws_rel_filesdir_key.match(key) and value.strip() != '':
-                self.cobbler_ws_rel_filesdir = Path(value.strip())
-                continue
-            if re_system_status.match(key) and value.strip() != '':
-                val = value.strip().lower()
-                if val in self.valid_system_status:
-                    self.system_status = val
-                else:
-                    msg = _(
-                        "The value of {what!r} must be one of {valid!r}, "
-                        "but found {val!r}.").format(
-                            what='system_status',
-                            valid=self.valid_system_status, val=value)
-                    LOG.error(msg)
-                continue
-
-    # -------------------------------------------------------------------------
-    def _eval_cobbler_distros(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        for distro_id in section.keys():
-            distro_info = section[distro_id]
-            distro_id = distro_id.lower()
-            distro = CobblerDistroInfo.init_from_config(
-                distro_id, distro_info, verbose=self.verbose)
-            self.cobbler_distros[distro_id] = distro
-
-    # -------------------------------------------------------------------------
-    def get_root_pwd_hash(self, method='sha256'):
-        """Generates a password hash based on the root password."""
-
-        m = method.lower()
-        if m not in self.method_list:
-            msg = _("Given method {!r} is not a valid crypt method.").format(method)
-            raise ValueError(msg)
-
-        salt = crypt.mksalt(self.method_list[m])
-        if self.verbose > 1:
-            pw_show = self.root_password
-            if self.verbose < 4:
-                pw_show = '********'
-            LOG.debug("Hashing password {pw!r} with salt {sa!r} (method {me!r})..".format(
-                pw=pw_show, sa=salt, me=m))
-        pwd_hash = crypt.crypt(self.root_password, salt)
-        if self.verbose > 2:
-            LOG.debug(_("Hashed root password: {!r}").format(pwd_hash))
-
-        return pwd_hash
-
-    # -------------------------------------------------------------------------
-    def _eval_cobbler_repos(self, section_name, section):
-
-        if self.verbose > 1:
-            LOG.debug(_("Checking config section {!r} ...").format(section_name))
-
-        for full_repo_name in section.keys():
-            repo_data = section[full_repo_name]
-            if isinstance(repo_data, dict):
-                repo_info = {}
-                if self.verbose > 2:
-                    LOG.debug(_("Found Cobbler repository {!r}.").format(full_repo_name))
-                for key in repo_data.keys():
-                    value = repo_data[key]
-                    if key.lower() == 'reponame' and value.strip() != '':
-                        repo_info['reponame'] = value.strip()
-                        continue
-                    if key.lower() == 'filename' and value.strip() != '':
-                        repo_info['filename'] = value.strip()
-                        continue
-                self.cobbler_repos[full_repo_name] = repo_info
-        if self.verbose > 3:
-            LOG.debug(_("Evaluated Cobbler repositories:") + '\n' + pp(self.cobbler_repos))
-
-    # -------------------------------------------------------------------------
-    def strip_unnessecary(self):
-        """Stripping no more necessary stuff."""
-        LOG.debug(_("Stripping no more necessary stuff from configuration ..."))
-
-        if self.verbose > 1:
-            LOG.debug(_("Stripping {!r} ...").format('cfg.cobbler-distros'))
-        if 'cobbler-distros' in self.cfg:
-            del self.cfg['cobbler-distros']
-
-        if self.verbose > 1:
-            LOG.debug(_("Stripping {!r} ...").format('cfg.cobbler-repos'))
-        if 'cobbler-repos' in self.cfg:
-            del self.cfg['cobbler-repos']
-
-        if self.verbose > 1:
-            LOG.debug(_("Stripping {!r} ...").format('cobbler_distros'))
-        self.cobbler_distros = {}
-
-        if self.verbose > 1:
-            LOG.debug(_("Stripping {!r} ...").format('cobbler_repos'))
-        self.cobbler_repos = {}
-
-        if self.verbose > 1:
-            LOG.debug(_("Stripping {!r} ...").format('configs_raw'))
-        self.configs_raw = {}
-
-
-# =============================================================================
-if __name__ == "__main__":
-
-    pass
-
-# =============================================================================
-
-# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
diff --git a/lib/cr_vmware_tpl/config/__init__.py b/lib/cr_vmware_tpl/config/__init__.py
new file mode 100644 (file)
index 0000000..c195dee
--- /dev/null
@@ -0,0 +1,1371 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2018 by Frank Brehm, Berlin
+@summary: A module for providing a configuration
+"""
+from __future__ import absolute_import
+
+# Standard module
+import logging
+import re
+import copy
+import crypt
+import os
+import ipaddress
+
+# Third party modules
+from pathlib import Path
+
+from fb_tools.common import is_sequence, pp, to_bool
+from fb_tools.obj import FbGenericBaseObject
+from fb_tools.collections import CIStringSet
+from fb_tools.multi_config import BaseMultiConfig
+from fb_tools.multi_config import DEFAULT_ENCODING
+from fb_tools.xlate import format_list
+
+from fb_vmware.config import VSPhereConfigInfo
+
+# Own modules
+from .. import DEFAULT_CONFIG_DIR, DEFAULT_DISTRO_ARCH
+from .. import DEFAULT_PORT_LDAPS, DEFAULT_TIMEOUT, MAX_TIMEOUT
+
+from ..errors import CrTplConfigError
+
+from ..xlate import XLATOR
+
+from .ldap import LdapConnectionInfo, LdapConnectionDict
+
+__version__ = '3.0.1'
+LOG = logging.getLogger(__name__)
+
+_ = XLATOR.gettext
+ngettext = XLATOR.ngettext
+
+
+# =============================================================================
+class CobblerDistroInfo(FbGenericBaseObject):
+    """Class for encapsulation all necessary data of a Repo definition in Cobbler."""
+
+    re_dashes = re.compile(r'[_-]')
+    valid_arch = (
+        'i386', 'x86_64', 'ia64', 'ppc', 'ppc64', 'ppc64el', 'ppc64le',
+        's390', 's390x', 'arm', 'aarch64')
+
+    # -------------------------------------------------------------------------
+    def __init__(
+            self, name, shortname=None, distro=None, description=None, arch=DEFAULT_DISTRO_ARCH,
+            ks_repo_url=None, packages=None, repos=None, snippets=None):
+
+        self._name = None
+        self._shortname = None
+        self._distro = None
+        self._description = None
+        self._arch = DEFAULT_DISTRO_ARCH
+        self._ks_repo_url = None
+        self._is_rhel = False
+        self._ks_template = 'el8-standard.ks'
+        self.packages = []
+        self.repos = CIStringSet()
+        self.snippets = CIStringSet()
+
+        self.name = name
+        self.shortname = shortname
+        self.distro = distro
+        self.description = description
+        self.arch = arch
+
+        if packages:
+            if is_sequence(packages):
+                for pkg in packages:
+                    if pkg not in packages:
+                        self.packages.append(pkg)
+            else:
+                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
+                raise TypeError(msg.format(p='packages', v=repos))
+
+        if repos:
+            if is_sequence(repos):
+                for repo in repos:
+                    self.repos.add(repo)
+            else:
+                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
+                raise TypeError(msg.format(p='repos', v=repos))
+
+        if snippets:
+            if is_sequence(repos):
+                self.snippets = copy.copy(snippets)
+                for snippet in snippets:
+                    self.snippets.add(snippet)
+            else:
+                msg = _("The given parameter {p!r} must be sequential type (given: {v!r}).")
+                raise TypeError(msg.format(p='snippets', v=snippets))
+
+    # -------------------------------------------------------------------------
+    @property
+    def name(self):
+        """The canonical name of the distribution."""
+        return getattr(self, '_name', None)
+
+    @name.setter
+    def name(self, value):
+
+        name = value.strip()
+        if name == '':
+            msg = _("The name of a Cobbler distro may not be empty.")
+            raise ValueError(msg)
+
+        self._name = name
+
+    # -------------------------------------------------------------------------
+    @property
+    def shortname(self):
+        """The short name of the distro, how to be used e.g. as part of the template name."""
+
+        shortname = getattr(self, '_shortname', None)
+        if shortname is None:
+            name = self.name
+            if name is None:
+                return None
+            shortname = self.re_dashes.sub('', name)
+        return shortname
+
+    @shortname.setter
+    def shortname(self, value):
+        if value is None:
+            self._shortname = None
+            return
+        shortname = value.strip()
+        if shortname == '':
+            self._shortname = None
+            return
+        self._shortname = shortname
+
+    # -------------------------------------------------------------------------
+    @property
+    def distro(self):
+        """The name of the underlaying (real) cobbler distro."""
+        return getattr(self, '_distro', None)
+
+    @distro.setter
+    def distro(self, value):
+        if value is None:
+            self._distro = None
+            return
+
+        dis = value.strip()
+        if dis == '':
+            self._distro = None
+            return
+
+        self._distro = dis
+
+    # -------------------------------------------------------------------------
+    @property
+    def arch(self):
+        """The name of the underlaying (real) cobbler distro."""
+        return getattr(self, '_arch', DEFAULT_DISTRO_ARCH)
+
+    @arch.setter
+    def arch(self, value):
+
+        arch = value.strip().lower()
+        if arch not in self.valid_arch:
+            msg = _(
+                "Invalid architecture {a!r} for distro {n!r} given. Valid architectures are {v}.")
+            msg = msg.format(a=value, n=self.name, v=format_list(self.valid_arch, do_repr=True))
+            raise ValueError(msg)
+
+        self._arch = arch
+
+    # -------------------------------------------------------------------------
+    @property
+    def description(self):
+        """The name of the underlaying (real) cobbler distro."""
+        return getattr(self, '_description', None)
+
+    @description.setter
+    def description(self, value):
+        if value is None:
+            self._description = None
+            return
+
+        desc = value.strip()
+        if desc == '':
+            self._description = None
+            return
+
+        self._description = desc
+
+    # -------------------------------------------------------------------------
+    @property
+    def ks_repo_url(self):
+        """The URL for the base installation repository."""
+        return getattr(self, '_ks_repo_url', None)
+
+    @ks_repo_url.setter
+    def ks_repo_url(self, value):
+        if value is None:
+            self._ks_repo_url = None
+            return
+
+        ks_repo_url = value.strip()
+        if ks_repo_url == '':
+            self._ks_repo_url = None
+            return
+
+        self._ks_repo_url = ks_repo_url
+
+    # -------------------------------------------------------------------------
+    @property
+    def is_rhel(self):
+        """Is the currwnt distro a RHEL distro?"""
+        return self._is_rhel
+
+    @is_rhel.setter
+    def is_rhel(self, value):
+        self._is_rhel = to_bool(value)
+
+    # -------------------------------------------------------------------------
+    @property
+    def ks_template(self):
+        """The filename below templates for generating the final kickstart file."""
+
+        return getattr(self, '_ks_template', 'el8-standard.ks')
+
+    @ks_template.setter
+    def ks_template(self, value):
+        if value is None:
+            return
+        template = value.strip()
+        if template == '':
+            return
+        self._ks_template = template
+
+    # -------------------------------------------------------------------------
+    @property
+    def repos_string(self):
+        """Returns all repos as a string of their space concatinated names."""
+        if self.repos:
+            return ' '.join(self.repos.as_list())
+        return ''
+
+    # -------------------------------------------------------------------------
+    def __repr__(self):
+        """Typecasting into a string for reproduction."""
+
+        out = "<%s()" % (self.__class__.__name__)
+
+        fields = []
+        fields.append("name={!r}".format(self.name))
+        fields.append("shortname={!r}".format(self._shortname))
+        fields.append("distro={!r}".format(self.distro))
+        fields.append("arch={!r}".format(self.arch))
+        fields.append("is_rhel={!r}".format(self.is_rhel))
+        fields.append("ks_template={!r}".format(self.ks_template))
+        fields.append("description={!r}".format(self.description))
+        fields.append("ks_repo_url={!r}".format(self.ks_repo_url))
+
+        out += ", ".join(fields) + ")>"
+
+        return out
+
+    # -------------------------------------------------------------------------
+    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(CobblerDistroInfo, self).as_dict(short=short)
+        res['arch'] = self.arch
+        res['description'] = self.description
+        res['distro'] = self.distro
+        res['is_rhel'] = self.is_rhel
+        res['ks_repo_url'] = self.ks_repo_url
+        res['ks_template'] = self.ks_template
+        res['name'] = self.name
+        res['repos_string'] = self.repos_string
+        res['shortname'] = self.shortname
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def __eq__(self, other):
+
+        if not isinstance(other, CobblerDistroInfo):
+            return False
+
+        return self.name == other.name
+
+    # -------------------------------------------------------------------------
+    def __copy__(self):
+
+        new = self.__class__(
+            self.name, shortname=self.shortname, distro=self.distro, arch=self.arch,
+            ks_repo_url=self.ks_repo_url, description=self.description)
+
+        for package in self.packages:
+            new.packages.append(package)
+
+        for repo in self.repos:
+            new.repos.add(repo)
+
+        for snippet in self.snippets:
+            new.snippets.add(snippet)
+
+        return new
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def init_from_config(cls, name, data, verbose=0):
+
+        new = cls(name)
+
+        for key in data.keys():
+            value = data[key]
+
+            if key.lower() == 'shortname' and value.strip() != '':
+                new.shortname = value.strip()
+                continue
+
+            if key.lower() == 'distro' and value.strip() != '':
+                new.distro = value.strip()
+                continue
+
+            if key.lower() == 'description' and value.strip() != '':
+                new.description = value.strip()
+                continue
+
+            if key.lower() == 'arch' and value.strip() != '':
+                new.arch = value.strip()
+                continue
+
+            if key.lower() == 'is_rhel':
+                new.is_rhel = value
+                continue
+
+            if key.lower() == 'ks_repo_url' and value.strip() != '':
+                new.ks_repo_url = value.strip()
+                continue
+
+            if key.lower() == 'ks_template' and value.strip() != '':
+                new._ks_template = value.strip()
+                continue
+
+            if key.lower() == 'repos':
+                cls._update_repos(new, value)
+                continue
+
+            if key.lower() == 'packages':
+                cls._update_packages(new, value)
+                continue
+
+            if key.lower() == 'snippets':
+                cls._update_snippets(new, value)
+                continue
+
+            if verbose:
+                LOG.warn(_(
+                    "Found unknown config parameter {p!r} with value {v!r} in configuration "
+                    "of the Cobbler repository {r!r}.").format(p=key, v=value, r=name))
+
+        if verbose > 2:
+            msg = _("Found Cobbler repository configuration:") + '\n' + pp(new.as_dict())
+            LOG.debug(msg)
+
+        return new
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def _update_repos(cls, new, value):
+
+        if is_sequence(value):
+            for repo in value:
+                repo = repo.strip()
+                if repo != '':
+                    new.repos.add(repo)
+        elif value.strip() != '':
+            new.repos.add(value.strip())
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def _update_packages(cls, new, value):
+
+        if is_sequence(value):
+            for pkg in value:
+                pkg = pkg.strip()
+                if pkg != '' and pkg not in new.packages:
+                    new.packages.append(pkg)
+        elif value.strip() != '':
+            new.packages.add(value.strip())
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def _update_snippets(cls, new, value):
+
+        if is_sequence(value):
+            for snippet in value:
+                snippet = snippet.strip()
+                if snippet != '':
+                    new.snippets.add(snippet)
+        elif value.strip() != '':
+            new.snippets.add(value.strip())
+
+
+# =============================================================================
+class CrTplConfiguration(BaseMultiConfig):
+    """
+    A class for providing a configuration for the CrTplApplication class
+    and methods to read it from configuration files.
+    """
+
+    default_os_id = 'centos-stream-8'
+
+    default_vsphere_host = 'vcs01.ppbrln.internal'
+    default_vsphere_port = 443
+    default_vsphere_user = 'root'
+    default_vsphere_cluster = 'vmcc-l105-01'
+    default_dc = 'vmcc'
+    default_folder = 'templates'
+    default_template_name = default_os_id + '-template'
+    default_data_size_gb = 32.0
+    default_storage_cluster = 'ds-cluster-hdd-vmcc-l105-01'
+    default_num_cpus = 2
+    default_ram_mb = 4 * 1024
+    default_network = '192.168.88.0_23'
+    default_max_wait_for_general = 15
+    default_max_wait_for_shutdown = 600
+    default_max_wait_for_finish_install = 60 * 60
+    default_max_nr_templates_stay = 4
+    default_vmware_cfg_version = 'vmx-15'
+    default_os_version = 'centos8_64Guest'
+    min_max_wait_for_finish_general = 2
+    min_max_wait_for_finish_install = 3 * 60
+    max_max_wait_for_finish_general = 60 * 60
+    max_max_wait_for_finish_install = 24 * 60 * 60
+    limit_max_nr_templates_stay = 100
+    default_root_password = 'testtest'
+
+    default_tpl_vm_domain = 'pixelpark.com'
+
+    default_cobbler_bin = '/bin/cobbler'
+    default_cobbler_host = 'cobbler.pixelpark.com'
+    default_cobbler_ssh_port = 22
+    default_cobbler_ssh_user = 'root'
+    default_cobbler_ssh_timeout = 30
+    default_cobbler_distro = 'CentOS-8.2-x86_64'
+    default_cobbler_rootdir = Path('/var/lib/cobbler')
+    default_cobbler_profile_repos = ['pp-centos8-baseos']
+    default_cobbler_nameservers = [
+        '93.188.104.82',
+        '93.188.109.12',
+        '217.66.52.10',
+    ]
+    default_cobbler_dns_search = [
+        'pixelpark.net',
+        'pixelpark.com',
+        'pixelpark.de',
+    ]
+    resolv_conf = Path('/etc/resolv.conf')
+    evaluated_resolv_conf = False
+
+    default_cobbler_ws_base_url = 'http://cobbler.pixelpark.com'
+    default_cobbler_ws_docroot = Path('/var/www/html')
+    default_cobbler_ws_rel_filesdir = Path('custom/vmware-template-files')
+
+    default_cobbler2_templates_dir_rel = 'kickstarts'
+    default_cobbler2_collections_dir_rel = 'config'
+    default_cobbler2_profiles_dir_rel = 'profiles.d'
+
+    default_cobbler3_templates_dir_rel = 'templates'
+    default_cobbler3_collections_dir_rel = 'collections'
+    default_cobbler3_profiles_dir_rel = 'profiles'
+
+    ssh_privkey = 'id_rsa_cr_vmw_tpl'
+
+    mac_address_template = "00:16:3e:53:{:02x}:{:02x}"
+
+    method_list = {}
+    for method in crypt.methods:
+        mname = method.name.lower()
+        method_list[mname] = method
+
+    valid_system_status = ('development', 'testing', 'acceptance', 'production')
+    default_system_status = 'development'
+
+    default_cobbler_profile = 'vmware-template.' + default_os_id + '.' + default_system_status
+
+    default_swap_size_mb = 512
+
+    default_ldap_server = 'prd-ds.pixelpark.com'
+    use_ssl_on_default = True
+    default_ldap_port = DEFAULT_PORT_LDAPS
+    default_base_dn = 'o=isp'
+    default_bind_dn = 'uid=readonly,ou=People,o=isp'
+
+    re_ldap_section_w_name = re.compile(r'^\s*ldap\s*:\s*(\S+)')
+    re_resolv_ns_entry = re.compile(r'^\s*nameserver\s+(\S+)')
+
+    # -------------------------------------------------------------------------
+    def __init__(
+        self, appname=None, verbose=0, version=__version__, base_dir=None,
+            append_appname_to_stems=True, additional_stems=None, config_dir=DEFAULT_CONFIG_DIR,
+            additional_config_file=None, additional_cfgdirs=None, encoding=DEFAULT_ENCODING,
+            ensure_privacy=False, use_chardet=True, initialized=False):
+
+        self.eval_resolv_conf()
+
+        add_stems = []
+        if additional_stems:
+            if is_sequence(additional_stems):
+                for stem in additional_stems:
+                    add_stems.append(stem)
+            else:
+                add_stems.append(additional_stems)
+
+        if 'mail' not in add_stems:
+            add_stems.append('mail')
+        if 'cobbler-repos' not in add_stems:
+            add_stems.append('cobbler-distros')
+        if 'ldap' not in add_stems:
+            add_stems.append('ldap')
+
+        self.os_id = self.default_os_id
+
+        self.vsphere_cluster = self.default_vsphere_cluster
+        self.folder = self.default_folder
+        self.template_name = self.default_template_name
+        self.data_size_gb = self.default_data_size_gb
+        self.num_cpus = self.default_num_cpus
+        self.ram_mb = self.default_ram_mb
+        self.swap_size_mb = self.default_swap_size_mb
+        self.network = self.default_network
+        self.max_wait_for_general = self.default_max_wait_for_general
+        self.max_wait_for_create_vm = None
+        self.max_wait_for_poweron_vm = None
+        self.max_wait_for_shutdown_vm = self.default_max_wait_for_shutdown
+        self.max_wait_for_purge_vm = None
+        self.max_wait_for_finish_install = self.default_max_wait_for_finish_install
+        self.max_nr_templates_stay = self.default_max_nr_templates_stay
+        self.vmware_cfg_version = self.default_vmware_cfg_version
+        self.os_version = self.default_os_version
+
+        self.storage_cluster = self.default_storage_cluster
+
+        self.tpl_vm_domain = self.default_tpl_vm_domain
+
+        self.cobbler_profile_given = False
+        self.template_name_given = False
+
+        self._root_password = self.default_root_password
+
+        self.private_ssh_key = None
+
+        self._cobbler_major_version = 3
+
+        self.cobbler_bin = self.default_cobbler_bin
+        self.cobbler_distro = self.default_cobbler_distro
+        self.cobbler_host = self.default_cobbler_host
+        self.cobbler_ssh_port = self.default_cobbler_ssh_port
+        self.cobbler_ssh_user = self.default_cobbler_ssh_user
+        self.cobbler_ssh_timeout = self.default_cobbler_ssh_timeout
+        self.cobbler_rootdir = self.default_cobbler_rootdir
+        self.cobbler_profile = self.default_cobbler_profile
+        self.cobbler_profile_repos = copy.copy(self.default_cobbler_profile_repos)
+        self.cobbler_nameservers = copy.copy(self.default_cobbler_nameservers)
+        self.cobbler_dns_search = copy.copy(self.default_cobbler_dns_search)
+        self.cobbler_ws_base_url = self.default_cobbler_ws_base_url
+        self.cobbler_ws_docroot = self.default_cobbler_ws_docroot
+        self.cobbler_ws_rel_filesdir = self.default_cobbler_ws_rel_filesdir
+        self.cobbler_distros = {}
+        self.cobbler_repos = {}
+
+        self.current_distro = None
+
+        self.system_status = self.default_system_status
+
+        self.excluded_datastores = []
+
+        if config_dir is None:
+            config_dir = DEFAULT_CONFIG_DIR
+        LOG.debug("Config dir: {!r}.".format(config_dir))
+
+        self.ldap_timeout = DEFAULT_TIMEOUT
+
+        super(CrTplConfiguration, self).__init__(
+            appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+            append_appname_to_stems=append_appname_to_stems, config_dir=config_dir,
+            additional_stems=add_stems, additional_config_file=additional_config_file,
+            additional_cfgdirs=additional_cfgdirs, encoding=encoding, use_chardet=use_chardet,
+            ensure_privacy=ensure_privacy, initialized=False,
+        )
+
+        self.vsphere_info = VSPhereConfigInfo(
+            host=self.default_vsphere_host, port=self.default_vsphere_port, dc=self.default_dc,
+            use_https=True, user=self.default_vsphere_user,
+            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+            initialized=True)
+
+        self.private_ssh_key = str(self.base_dir.joinpath('keys', self.ssh_privkey))
+
+        self.ldap_connection = LdapConnectionDict()
+
+        default_connection = LdapConnectionInfo(
+            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+            host=self.default_ldap_server, use_ldaps=self.use_ssl_on_default,
+            port=self.default_ldap_port, base_dn=self.default_base_dn,
+            bind_dn=self.default_bind_dn, initialized=False)
+
+        self.ldap_connection['default'] = default_connection
+
+        if initialized:
+            self.initialized = True
+
+    # -------------------------------------------------------------------------
+    @property
+    def cobbler_major_version(self):
+        """The major version of the used cobbler installation. Valid values are 2 and 3."""
+        return self._cobbler_major_version
+
+    @cobbler_major_version.setter
+    def cobbler_major_version(self, value):
+        if value not in (2, 3):
+            msg = _("Unsupported version {ver!r} of {co}, valid versions of {co} are {valid}.")
+            msg = msg.format(ver=value, co='Cobbler', valid=format_list(['2', '3']))
+            raise CrTplConfigError(msg)
+
+        self._cobbler_major_version = value
+
+    # -------------------------------------------------------------------------
+    @property
+    def cobbler_collections_dir(self):
+        """The absolute pathname of all Cobbler collections."""
+        if self.cobbler_major_version == 2:
+            return self.cobbler_rootdir / self.default_cobbler2_collections_dir_rel
+        return self.cobbler_rootdir / self.default_cobbler3_collections_dir_rel
+
+    # -------------------------------------------------------------------------
+    @property
+    def cobbler_ks_dir(self):
+        """The absolute pathname of the directory of the kickstart/template/autoinstall files."""
+        if self.cobbler_major_version == 2:
+            return self.cobbler_rootdir / self.default_cobbler2_templates_dir_rel
+        return self.cobbler_rootdir / self.default_cobbler3_templates_dir_rel
+
+    # -------------------------------------------------------------------------
+    @property
+    def cobbler_profile_dir(self):
+        """The absolute pathname of the directory of Cobbler profile configuration files."""
+        if self.cobbler_major_version == 2:
+            return self.cobbler_collections_dir / self.default_cobbler2_profiles_dir_rel
+        return self.cobbler_collections_dir / self.default_cobbler3_profiles_dir_rel
+
+    # -------------------------------------------------------------------------
+    @property
+    def cobbler_profile_ks(self):
+        """The absolute pathname of the profile kickstart file."""
+        return self.cobbler_ks_dir / ('profile.' + self.cobbler_profile + '.ks')
+
+    # -------------------------------------------------------------------------
+    @property
+    def data_size_mb(self):
+        """Size of template volume in MiB."""
+        return int(self.data_size_gb * 1024.0)
+
+    # -------------------------------------------------------------------------
+    @property
+    def data_size_kb(self):
+        """Size of template volume in KiB."""
+        return self.data_size_mb * 1024
+
+    # -------------------------------------------------------------------------
+    @property
+    def data_size(self):
+        """Size of template volume in Bytes."""
+        return self.data_size_mb * 1024 * 1024
+
+    # -------------------------------------------------------------------------
+    @property
+    def ram_gb(self):
+        """Size of RAM in GiB."""
+        return float(self.ram_mb) / 1024
+
+    # -------------------------------------------------------------------------
+    @property
+    def root_password(self):
+        """The root password of the VM to create."""
+        return self._root_password
+
+    # -------------------------------------------------------------------------
+    @property
+    def system_ks(self):
+        """The path to the system kickstart file."""
+        ks_base = 'template-' + self.os_id + '-' + self.system_status + '.ks'
+        return self.cobbler_ks_dir / ks_base
+
+    # -------------------------------------------------------------------------
+    @property
+    def snippets_dir(self):
+        """The path to the snippets dirctory, depending of the system status."""
+        return self.cobbler_rootdir / 'snippets' / 'per_status' / self.system_status
+
+    # -------------------------------------------------------------------------
+    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(CrTplConfiguration, self).as_dict(short=short)
+        res['cobbler_major_version'] = self.cobbler_major_version
+        res['cobbler_collections_dir'] = self.cobbler_collections_dir
+        res['cobbler_ks_dir'] = self.cobbler_ks_dir
+        res['cobbler_profile_dir'] = self.cobbler_profile_dir
+        res['cobbler_profile_ks'] = self.cobbler_profile_ks
+        res['data_size_mb'] = self.data_size_mb
+        res['data_size_kb'] = self.data_size_kb
+        res['data_size'] = self.data_size
+        res['default_cobbler_nameservers'] = self.default_cobbler_nameservers
+        res['ram_gb'] = self.ram_gb
+        res['system_ks'] = self.system_ks
+        res['snippets_dir'] = self.snippets_dir
+
+        res['cobbler_distros'] = {}
+        for distro in self.cobbler_distros.keys():
+            res['cobbler_distros'][distro] = self.cobbler_distros[distro].as_dict(short=short)
+
+        res['root_password'] = None
+        if self.root_password:
+            if self.verbose > 4:
+                res['root_password'] = self.root_password
+            else:
+                res['root_password'] = '********'
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def eval(self):
+        """Evaluating read configuration and storing them in object properties."""
+
+        super(CrTplConfiguration, self).eval()
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking for unconfigured options ..."))
+
+        if self.max_wait_for_create_vm is None:
+            self.max_wait_for_create_vm = self.max_wait_for_general
+        if self.max_wait_for_poweron_vm is None:
+            self.max_wait_for_poweron_vm = self.max_wait_for_general
+        if self.max_wait_for_purge_vm is None:
+            self.max_wait_for_purge_vm = self.max_wait_for_general
+        if self.max_wait_for_shutdown_vm is None:
+            self.max_wait_for_shutdown_vm = self.max_wait_for_general
+
+        if not self.template_name_given:
+            self.template_name = self.os_id + '-template'
+
+        if not self.cobbler_profile_given:
+            self.cobbler_profile = 'vmware-template.' + self.os_id + '.' + self.system_status
+
+        self.verify_cobbler_distros()
+
+        if not self.template_name_given:
+            self.template_name = self.current_distro.shortname + '-template'
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def eval_resolv_conf(cls):
+
+        if cls.evaluated_resolv_conf:
+            return True
+
+        if not cls.resolv_conf.exists():
+            LOG.error(_("File {!r} not found on current host.").format(str(cls.resolv_conf)))
+            cls.evaluated_resolv_conf = True
+            return False
+
+        if not cls.resolv_conf.is_file():
+            LOG.error(_("Path {!r} is not a regular file.").format(str(cls.resolv_conf)))
+            cls.evaluated_resolv_conf = True
+            return False
+
+        if not os.access(cls.resolv_conf, os.R_OK):
+            LOG.error(_("File {!r} is not readable.").format(str(cls.resolv_conf)))
+            cls.evaluated_resolv_conf = True
+            return False
+
+        LOG.info(_("Evaluating {!r} for nameservers.").format(str(cls.resolv_conf)))
+
+        nameservers = []
+        file_content = cls.resolv_conf.read_text()
+        lines = file_content.splitlines()
+
+        for line in lines:
+            match = cls.re_resolv_ns_entry.match(line)
+            if match:
+                try:
+                    ns_address = ipaddress.ip_address(match.group(1))
+                    nameservers.append(str(ns_address))
+                except ValueError as e:
+                    msg = _("Found invalid IP address {addr!r} as a nameserver in {file!r}:")
+                    msg = msg.format(addr=match.group(1), file=str(cls.resolv_conf))
+                    msg += ' ' + str(e)
+                    LOG.warn(msg)
+
+        cls.evaluated_resolv_conf = True
+        msg = _("Found nameservers in {!r}:").format(str(cls.resolv_conf))
+        msg += ' {}'.format(pp(nameservers))
+        LOG.debug(msg)
+
+        if len(nameservers):
+            cls.default_cobbler_nameservers = nameservers
+            return True
+
+        return False
+
+    # -------------------------------------------------------------------------
+    def verify_cobbler_distros(self):
+
+        LOG.debug(_("Verifying cobbler distros ..."))
+
+        if not len(self.cobbler_distros):
+            msg = _("Did not found configured Cobbler distros.")
+            raise CrTplConfigError(msg)
+
+        for distro_id in self.cobbler_distros.keys():
+            distro = self.cobbler_distros[distro_id]
+
+            if not distro.distro:
+                msg = _("Did not found distro of configured Cobbler distro {!r}.").format(
+                    distro_id)
+                raise CrTplConfigError(msg)
+
+            if not distro.ks_repo_url:
+                msg = _(
+                    "Did not found the base install repo URL of configured Cobbler "
+                    "distro {!r}.").format(distro_id)
+                raise CrTplConfigError(msg)
+
+            if not len(distro.repos):
+                msg = _(
+                    "Did not found repo definitions for configured Cobbler "
+                    "distro {!r}.").format(distro_id)
+                LOG.warn(msg)
+
+            if not distro.description:
+                distro.description = distro_id
+
+        LOG.debug(_("Searching for distro with ID {!r} ...").format(self.os_id))
+
+        if self.os_id not in self.cobbler_distros:
+            msg = _("Did not found distro {!r} in configured Cobbler distros.").format(self.os_id)
+            raise CrTplConfigError(msg)
+
+        self.current_distro = self.cobbler_distros[self.os_id]
+        self.cobbler_distro = self.current_distro.distro
+
+        LOG.info(_("Using OS {os!r} with cobbler distro {di!r}.").format(
+            os=self.os_id, di=self.cobbler_distro))
+
+    # -------------------------------------------------------------------------
+    def eval_section(self, section_name):
+
+        re_cobbler_distros = re.compile(r'^\s*cobbler[_-]?distros\s*$', re.IGNORECASE)
+        re_cobbler_repos = re.compile(r'^\s*cobbler[_-]?repos\s*$', re.IGNORECASE)
+
+        sn = section_name.lower()
+        section = self.cfg[section_name]
+
+        LOG.debug(_("Evaluating section {!r} ...").format(section_name))
+        if self.verbose > 2:
+            LOG.debug(_("Content of section:") + '\n' + pp(section))
+
+        super(CrTplConfiguration, self).eval_section(section_name)
+
+        if sn == 'vsphere':
+            self._eval_config_vsphere(section_name, section)
+            return
+
+        if sn == 'template':
+            self._eval_config_template(section_name, section)
+            return
+
+        if sn == 'timeouts':
+            self._eval_config_timeouts(section_name, section)
+            return
+
+        if sn == 'cobbler':
+            self._eval_config_cobbler(section_name, section)
+            return
+
+        if re_cobbler_distros.match(section_name):
+            self._eval_cobbler_distros(section_name, section)
+            return
+
+        if re_cobbler_repos.match(section_name):
+            self._eval_cobbler_repos(section_name, section)
+            return
+
+        if sn == 'ldap':
+            for key in section.keys():
+                sub = section[key]
+                if key.lower().strip() == 'timeout':
+                    self._eval_ldap_timeout(sub)
+                    continue
+                self._eval_ldap_connection(key, sub)
+            return
+
+        match = self.re_ldap_section_w_name.match(sn)
+        if match:
+            connection_name = match.group(1)
+            self._eval_ldap_connection(connection_name, section)
+            return
+
+        if self.verbose > 1:
+            LOG.debug(_("Unhandled configuration section {!r}.").format(section_name))
+
+    # -------------------------------------------------------------------------
+    def _eval_ldap_timeout(self, value):
+
+        timeout = DEFAULT_TIMEOUT
+        msg_invalid = _("Value {!r} for a timeout is invalid.")
+
+        try:
+            timeout = int(value)
+        except (ValueError, TypeError) as e:
+            msg = msg_invalid.format(value)
+            msg += ': ' + str(e)
+            LOG.error(msg)
+            return
+        if timeout <= 0 or timeout > MAX_TIMEOUT:
+            msg = msg_invalid.format(value)
+            LOG.error(msg)
+            return
+
+        self.ldap_timeout = timeout
+
+    # -------------------------------------------------------------------------
+    def _eval_ldap_connection(self, connection_name, section):
+
+        connection = LdapConnectionInfo.init_from_config(
+            connection_name, section,
+            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+        )
+
+        self.ldap_connection[connection_name] = connection
+
+    # -------------------------------------------------------------------------
+    def _eval_config_vsphere(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        re_excl_ds = re.compile(r'^\s*excluded?[-_]datastores?\s*$', re.IGNORECASE)
+        re_split_ds = re.compile(r'[,;\s]+')
+        re_storage_cluster = re.compile(r'^\s*storage[-_]?cluster\s*$', re.IGNORECASE)
+
+        for key in section.keys():
+            value = section[key]
+
+            if key.lower() == 'host':
+                self.vsphere_info.host = value
+                continue
+            elif key.lower() == 'port':
+                self.vsphere_info.port = value
+                continue
+            elif key.lower() == 'user':
+                self.vsphere_info.user = value
+                continue
+            elif key.lower() == 'password':
+                self.vsphere_info.password = value
+                continue
+            elif key.lower() == 'cluster':
+                self.vsphere_cluster = value
+                continue
+            elif key.lower() == 'folder':
+                self.folder = value
+            elif key.lower() == 'dc':
+                self.vsphere_info.dc = value
+
+            elif key.lower() == 'max_nr_templates_stay':
+                v = int(value)
+                if v < 1:
+                    LOG.error(_(
+                        "Value {val} for {p} is less than {minval}, using {default}.").format(
+                        val=v, minval=1, p='max_nr_templates_stay',
+                        default=self.default_max_nr_templates_stay))
+                elif v >= 100:
+                    LOG.error(_(
+                        "Value {val} for {p} is greater than {maxval}, using {default}.").format(
+                        val=v, maxval=100, p='max_nr_templates_stay',
+                        default=self.default_max_nr_templates_stay))
+                else:
+                    self.max_nr_templates_stay = v
+
+            elif re_excl_ds.search(key):
+                datastores = re_split_ds.split(value.strip())
+                self.excluded_datastores = datastores
+
+            elif re_storage_cluster.search(key):
+                cl_name = value.strip()
+                if cl_name:
+                    self.storage_cluster = cl_name
+                else:
+                    self.storage_cluster = None
+
+        return
+
+    # -------------------------------------------------------------------------
+    def _eval_config_template(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        re_os_id = re.compile(r'^\s*os[-_]?id\s*$', re.IGNORECASE)
+        re_os_id_subst = re.compile(r'[^a-z0-9_-]+', re.IGNORECASE)
+        re_vm_domain = re.compile(r'^\s*(?:vm[-_]?)?domain\s*$', re.IGNORECASE)
+        re_root_pwd = re.compile(r'^\s*root[-_]?password\s*$', re.IGNORECASE)
+        re_swap_space = re.compile(r'^\s*swap[-_]?space(?:[-_]?mb)?\s*$', re.IGNORECASE)
+
+        for key in section.keys():
+            value = section[key]
+
+            if re_os_id.match(key):
+                v = value.strip().lower()
+                v = re_os_id_subst.sub('', v)
+                if v:
+                    self.os_id = v
+                continue
+            if key.lower() == 'name':
+                self.template_name = value
+                self.template_name_given = True
+                continue
+            if re_vm_domain.match(key) and value.strip():
+                self.tpl_vm_domain = value.strip().lower()
+                continue
+            if key.lower() == 'data_size_gb':
+                self.data_size_gb = float(value)
+                continue
+            if key.lower() == 'data_size_mb':
+                self.data_size_gb = float(value) / 1024.0
+                continue
+            if key.lower() == 'data_size_kb':
+                self.data_size_gb = float(value) / 1024.0 / 1024.0
+                continue
+            if key.lower() == 'data_size':
+                self.data_size_gb = float(value) / 1024.0 / 1024.0 / 1024.0
+                continue
+            if key.lower() == 'num_cpus':
+                self.num_cpus = int(value)
+                continue
+            if key.lower() == 'ram_gb':
+                self.ram_mb = int(float(value) * 1024.0)
+                continue
+            if key.lower() == 'ram_mb':
+                self.ram_mb = int(value)
+                continue
+            if key.lower() == 'network':
+                self.network = value.strip()
+                continue
+            if key.lower() == 'vmware_cfg_version':
+                self.vmware_cfg_version = value.strip()
+                continue
+            if key.lower() == 'os_version':
+                self.os_version = value.strip()
+                continue
+            if re_root_pwd.match(key) and value.strip():
+                self._root_password = value.strip()
+                continue
+            if re_swap_space.match(key) and value.strip():
+                self.swap_size_mb = int(value)
+                continue
+
+        return
+
+    # -------------------------------------------------------------------------
+    def _eval_timeout_value(self, prop_name, value, min_val, max_val, default_val):
+
+        if self.verbose > 2:
+            LOG.debug(_("Checking value {v!r} for {p} ...").format(v=value, p=prop_name))
+        if self.verbose > 3:
+            LOG.debug(_(
+                "Minimal value: {min_val}, maximum value: {max_val}, "
+                "default value: {def_val}.").format(
+                min_val=min_val, max_val=max_val, def_val=default_val))
+
+        v = float(value)
+
+        if v < min_val:
+            msg = _(
+                "Value {val} for {prop} is less than {min_val}, "
+                "using {def_val} seconds.").format(val=v, min_val=min_val, def_val=default_val)
+            LOG.error(msg)
+            return
+
+        if v < min_val:
+            msg = _(
+                "Value {val} for {prop} is greater than {max_val}, "
+                "using {def_val} seconds.").format(val=v, max_val=max_val, def_val=default_val)
+            LOG.error(msg)
+            return
+
+        if self.verbose > 2:
+            msg = _("Setting timeout {p!r} to {v:0.1f} seconds.").format(p=prop_name, v=v)
+            LOG.debug(msg)
+        setattr(self, prop_name, v)
+
+    # -------------------------------------------------------------------------
+    def _eval_config_timeouts(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        for key in section.keys():
+            value = section[key]
+
+            if key.lower() == 'max_wait_for_general':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_general', value=value,
+                    min_val=self.min_max_wait_for_finish_general,
+                    max_val=self.max_max_wait_for_finish_general,
+                    default_val=self.default_max_wait_for_general)
+                continue
+
+            if key.lower() == 'max_wait_for_create_vm':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_create_vm', value=value,
+                    min_val=self.min_max_wait_for_finish_general,
+                    max_val=self.max_max_wait_for_finish_general,
+                    default_val=self.max_wait_for_general)
+                continue
+            elif key.lower() == 'max_wait_for_poweron_vm':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_poweron_vm', value=value,
+                    min_val=self.min_max_wait_for_finish_general,
+                    max_val=self.max_max_wait_for_finish_general,
+                    default_val=self.max_wait_for_general)
+                continue
+            elif key.lower() == 'max_wait_for_shutdown_vm':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_shutdown_vm', value=value,
+                    min_val=self.min_max_wait_for_finish_general,
+                    max_val=self.max_max_wait_for_finish_general,
+                    default_val=self.default_max_wait_for_shutdown)
+                continue
+            elif key.lower() == 'max_wait_for_purge_vm':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_purge_vm', value=value,
+                    min_val=self.min_max_wait_for_finish_general,
+                    max_val=self.max_max_wait_for_finish_general,
+                    default_val=self.max_wait_for_general)
+                continue
+            elif key.lower() == 'max_wait_for_finish_install':
+                self._eval_timeout_value(
+                    prop_name='max_wait_for_finish_install', value=value,
+                    min_val=self.min_max_wait_for_finish_install,
+                    max_val=self.max_max_wait_for_finish_install,
+                    default_val=self.default_max_wait_for_finish_install)
+                continue
+
+    # -------------------------------------------------------------------------
+    def _eval_config_cobbler(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        re_port_key = re.compile(r'^\s*ssh[-_]?port\s*$', re.IGNORECASE)
+        re_user_key = re.compile(r'^\s*ssh[-_]?user\s*$', re.IGNORECASE)
+        re_timeout_key = re.compile(r'^\s*ssh[-_]?timeout\s*$', re.IGNORECASE)
+        re_rootdir_key = re.compile(r'^\s*root[-_]?dir(?:ectory)?\s*$', re.IGNORECASE)
+        re_split_values = re.compile(r'[,;\s]+')
+        re_pr_repos_key = re.compile(r'^\s*profile[-_]?repos?\s*$', re.IGNORECASE)
+        re_nameserver_key = re.compile(r'^\s*nameservers?\s*$', re.IGNORECASE)
+        re_dns_search_key = re.compile(r'^\s*dns[-_]?search\s*$', re.IGNORECASE)
+        re_ws_base_url_key = re.compile(
+            r'^\s*(?:ws|webserver)[-_]?base[-_]?url\s*$', re.IGNORECASE)
+        re_ws_docroot_key = re.compile(
+            r'^\s*(?:ws|webserver)[-_]?docroot\s*$', re.IGNORECASE)
+        re_ws_rel_filesdir_key = re.compile(
+            r'^\s*(?:ws|webserver)[-_]?rel(?:ative)?[-_]?filesdir\s*$', re.IGNORECASE)
+        re_system_status = re.compile(r'^\s*system[-_]?status\s*$', re.IGNORECASE)
+        re_cobbler_bin_key = re.compile(r'^\s*(?:cobbler[_-]?)?bin\s*$', re.IGNORECASE)
+
+        for key in section.keys():
+            value = section[key]
+
+            if key.lower() == 'distro' and value.strip() != '':
+                self.cobbler_distro = value.strip()
+                continue
+            if key.lower() == 'host' and value.strip() != '':
+                self.cobbler_host = value.strip().lower()
+                continue
+            if re_port_key.match(key) and value.strip() != '':
+                self.cobbler_ssh_port = int(value)
+                continue
+            if re_user_key.match(key):
+                self.cobbler_ssh_user = value.strip().lower()
+                continue
+            if re_timeout_key.match(key):
+                self.cobbler_ssh_timeout = int(value)
+                continue
+            if re_cobbler_bin_key.match(key) and value.strip() != '':
+                self.cobbler_bin = value.strip()
+                continue
+            if re_rootdir_key.match(key):
+                dpath = Path(value)
+                if dpath.is_absolute():
+                    self.cobbler_rootdir = Path(value)
+                else:
+                    msg = _("Path for {what} {path!r} is not absolute.").format(
+                        what=_("Cobbler root directory"), path=str(dpath))
+                    LOG.error(msg)
+                continue
+            if key.lower() == 'profile' and value.strip() != '':
+                self.cobbler_profile = value.strip().lower()
+                self.cobbler_profile_given = True
+                continue
+            if re_pr_repos_key.match(key) and value.strip() != '':
+                self.cobbler_profile_repos = re_split_values.split(value.strip())
+                continue
+            if re_nameserver_key.match(key) and value.strip() != '':
+                self.cobbler_nameservers = re_split_values.split(value.strip().lower())
+                continue
+            if re_dns_search_key.match(key) and value.strip() != '':
+                self.cobbler_dns_search = re_split_values.split(value.strip().lower())
+                continue
+            if re_ws_base_url_key.match(key) and value.strip() != '':
+                self.cobbler_ws_base_url = value.strip().lower()
+                continue
+            if re_ws_docroot_key.match(key) and value.strip() != '':
+                dpath = Path(value.strip())
+                if dpath.is_absolute():
+                    self.cobbler_ws_docroot = dpath
+                else:
+                    msg = _("Path for {what} {path!r} is not absolute.").format(
+                        what=_("Webserver document root"), path=str(dpath))
+                    LOG.error(msg)
+                continue
+            if re_ws_rel_filesdir_key.match(key) and value.strip() != '':
+                self.cobbler_ws_rel_filesdir = Path(value.strip())
+                continue
+            if re_system_status.match(key) and value.strip() != '':
+                val = value.strip().lower()
+                if val in self.valid_system_status:
+                    self.system_status = val
+                else:
+                    msg = _(
+                        "The value of {what!r} must be one of {valid!r}, "
+                        "but found {val!r}.").format(
+                            what='system_status',
+                            valid=self.valid_system_status, val=value)
+                    LOG.error(msg)
+                continue
+
+    # -------------------------------------------------------------------------
+    def _eval_cobbler_distros(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        for distro_id in section.keys():
+            distro_info = section[distro_id]
+            distro_id = distro_id.lower()
+            distro = CobblerDistroInfo.init_from_config(
+                distro_id, distro_info, verbose=self.verbose)
+            self.cobbler_distros[distro_id] = distro
+
+    # -------------------------------------------------------------------------
+    def get_root_pwd_hash(self, method='sha256'):
+        """Generates a password hash based on the root password."""
+
+        m = method.lower()
+        if m not in self.method_list:
+            msg = _("Given method {!r} is not a valid crypt method.").format(method)
+            raise ValueError(msg)
+
+        salt = crypt.mksalt(self.method_list[m])
+        if self.verbose > 1:
+            pw_show = self.root_password
+            if self.verbose < 4:
+                pw_show = '********'
+            LOG.debug("Hashing password {pw!r} with salt {sa!r} (method {me!r})..".format(
+                pw=pw_show, sa=salt, me=m))
+        pwd_hash = crypt.crypt(self.root_password, salt)
+        if self.verbose > 2:
+            LOG.debug(_("Hashed root password: {!r}").format(pwd_hash))
+
+        return pwd_hash
+
+    # -------------------------------------------------------------------------
+    def _eval_cobbler_repos(self, section_name, section):
+
+        if self.verbose > 1:
+            LOG.debug(_("Checking config section {!r} ...").format(section_name))
+
+        for full_repo_name in section.keys():
+            repo_data = section[full_repo_name]
+            if isinstance(repo_data, dict):
+                repo_info = {}
+                if self.verbose > 2:
+                    LOG.debug(_("Found Cobbler repository {!r}.").format(full_repo_name))
+                for key in repo_data.keys():
+                    value = repo_data[key]
+                    if key.lower() == 'reponame' and value.strip() != '':
+                        repo_info['reponame'] = value.strip()
+                        continue
+                    if key.lower() == 'filename' and value.strip() != '':
+                        repo_info['filename'] = value.strip()
+                        continue
+                self.cobbler_repos[full_repo_name] = repo_info
+        if self.verbose > 3:
+            LOG.debug(_("Evaluated Cobbler repositories:") + '\n' + pp(self.cobbler_repos))
+
+    # -------------------------------------------------------------------------
+    def strip_unnessecary(self):
+        """Stripping no more necessary stuff."""
+        LOG.debug(_("Stripping no more necessary stuff from configuration ..."))
+
+        if self.verbose > 1:
+            LOG.debug(_("Stripping {!r} ...").format('cfg.cobbler-distros'))
+        if 'cobbler-distros' in self.cfg:
+            del self.cfg['cobbler-distros']
+
+        if self.verbose > 1:
+            LOG.debug(_("Stripping {!r} ...").format('cfg.cobbler-repos'))
+        if 'cobbler-repos' in self.cfg:
+            del self.cfg['cobbler-repos']
+
+        if self.verbose > 1:
+            LOG.debug(_("Stripping {!r} ...").format('cobbler_distros'))
+        self.cobbler_distros = {}
+
+        if self.verbose > 1:
+            LOG.debug(_("Stripping {!r} ...").format('cobbler_repos'))
+        self.cobbler_repos = {}
+
+        if self.verbose > 1:
+            LOG.debug(_("Stripping {!r} ...").format('configs_raw'))
+        self.configs_raw = {}
+
+
+# =============================================================================
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
diff --git a/lib/cr_vmware_tpl/config/ldap.py b/lib/cr_vmware_tpl/config/ldap.py
new file mode 100644 (file)
index 0000000..4b734b7
--- /dev/null
@@ -0,0 +1,369 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+"""
+@author: Frank Brehm
+@contact: frank.brehm@pixelpark.com
+@copyright: © 2018 by Frank Brehm, Berlin
+@summary: A module for providing LDAP connection infos.
+"""
+from __future__ import absolute_import
+
+# Standard module
+import logging
+import re
+import copy
+
+# Third party modules
+from fb_tools.common import to_bool
+from fb_tools.obj import FbGenericBaseObject, FbBaseObject
+
+# Own modules
+from .. import MAX_PORT_NUMBER
+from .. import DEFAULT_PORT_LDAP, DEFAULT_PORT_LDAPS
+from .. import DEFAULT_LDAP_ADMIN_FILTER
+
+from ..errors import CrTplConfigError
+
+from ..xlate import XLATOR
+
+__version__ = '3.0.0'
+LOG = logging.getLogger(__name__)
+
+_ = XLATOR.gettext
+ngettext = XLATOR.ngettext
+
+
+# =============================================================================
+class LdapConnectionInfo(FbBaseObject):
+    """Encapsulating all necessary data to connect to a LDAP server."""
+
+    re_host_key = re.compile(r'^\s*(?:host|server)\s*$', re.IGNORECASE)
+    re_ldaps_key = re.compile(r'^\s*(?:use[_-]?)?(?:ldaps|ssl)\s*$', re.IGNORECASE)
+    re_port_key = re.compile(r'^\s*port\s*$', re.IGNORECASE)
+    re_base_dn_key = re.compile(r'^\s*base[_-]*dn\s*$', re.IGNORECASE)
+    re_bind_dn_key = re.compile(r'^\s*bind[_-]*dn\s*$', re.IGNORECASE)
+    re_bind_pw_key = re.compile(r'^\s*bind[_-]*pw\s*$', re.IGNORECASE)
+    re_admin_filter_key = re.compile(r'^\s*(?:admin[_-]?)?filter\s*$', re.IGNORECASE)
+
+    # -------------------------------------------------------------------------
+    def __init__(
+        self, appname=None, verbose=0, version=__version__, base_dir=None,
+            host=None, use_ldaps=False, port=DEFAULT_PORT_LDAP, base_dn=None,
+            bind_dn=None, bind_pw=None, admin_filter=None, initialized=False):
+
+        self._host = None
+        self._use_ldaps = False
+        self._port = DEFAULT_PORT_LDAP
+        self._base_dn = None
+        self._bind_dn = None
+        self._bind_pw = None
+        self._admin_filter = DEFAULT_LDAP_ADMIN_FILTER
+
+        super(LdapConnectionInfo, self).__init__(
+            appname=appname, verbose=verbose, version=version, base_dir=base_dir,
+            initialized=False)
+
+        self.host = host
+        self.use_ldaps = use_ldaps
+        self.port = port
+        if base_dn:
+            self.base_dn = base_dn
+        self.bind_dn = bind_dn
+        self.bind_pw = bind_pw
+        if admin_filter:
+            self.admin_filter = admin_filter
+
+        if initialized:
+            self.initialized = True
+
+    # -------------------------------------------------------------------------
+    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(LdapConnectionInfo, self).as_dict(short=short)
+
+        res['host'] = self.host
+        res['use_ldaps'] = self.use_ldaps
+        res['port'] = self.port
+        res['base_dn'] = self.base_dn
+        res['bind_dn'] = self.bind_dn
+        res['bind_pw'] = None
+        res['schema'] = self.schema
+        res['url'] = self.url
+        res['admin_filter'] = self.admin_filter
+
+        if self.bind_pw:
+            if self.verbose > 4:
+                res['bind_pw'] = self.bind_pw
+            else:
+                res['bind_pw'] = '******'
+
+        return res
+
+    # -----------------------------------------------------------
+    @property
+    def host(self):
+        """The host name (or IP address) of the LDAP server."""
+        return self._host
+
+    @host.setter
+    def host(self, value):
+        if value is None or str(value).strip() == '':
+            self._host = None
+            return
+        self._host = str(value).strip().lower()
+
+    # -----------------------------------------------------------
+    @property
+    def use_ldaps(self):
+        """Should there be used LDAPS for communicating with the LDAP server?"""
+        return self._use_ldaps
+
+    @use_ldaps.setter
+    def use_ldaps(self, value):
+        self._use_ldaps = to_bool(value)
+
+    # -----------------------------------------------------------
+    @property
+    def port(self):
+        "The TCP port number of the LDAP server."
+        return self._port
+
+    @port.setter
+    def port(self, value):
+        v = int(value)
+        if v < 1 or v > MAX_PORT_NUMBER:
+            raise CrTplConfigError(_("Invalid port {!r} for LDAP server given.").format(value))
+        self._port = v
+
+    # -----------------------------------------------------------
+    @property
+    def base_dn(self):
+        """The DN used to connect to the LDAP server, anonymous bind is used, if
+            this DN is empty or None."""
+        return self._base_dn
+
+    @base_dn.setter
+    def base_dn(self, value):
+        if value is None or str(value).strip() == '':
+            msg = _("An empty Base DN for LDAP searches is not allowed.")
+            raise CrTplConfigError(msg)
+        self._base_dn = str(value).strip()
+
+    # -----------------------------------------------------------
+    @property
+    def bind_dn(self):
+        """The DN used to connect to the LDAP server, anonymous bind is used, if
+            this DN is empty or None."""
+        return self._bind_dn
+
+    @bind_dn.setter
+    def bind_dn(self, value):
+        if value is None or str(value).strip() == '':
+            self._bind_dn = None
+            return
+        self._bind_dn = str(value).strip()
+
+    # -----------------------------------------------------------
+    @property
+    def bind_pw(self):
+        """The password of the DN used to connect to the LDAP server."""
+        return self._bind_pw
+
+    @bind_pw.setter
+    def bind_pw(self, value):
+        if value is None or str(value).strip() == '':
+            self._bind_pw = None
+            return
+        self._bind_pw = str(value).strip()
+
+    # -----------------------------------------------------------
+    @property
+    def admin_filter(self):
+        """The LDAP filter to get  the list of administrators from LDAP."""
+        return self._admin_filter
+
+    @admin_filter.setter
+    def admin_filter(self, value):
+        if value is None or str(value).strip() == '':
+            self._admin_filter = None
+            return
+        self._admin_filter = str(value).strip()
+
+    # -----------------------------------------------------------
+    @property
+    def schema(self):
+        """The schema as part of the URL to connect to the LDAP server."""
+        if self.use_ldaps:
+            return 'ldaps'
+        return 'ldap'
+
+    # -----------------------------------------------------------
+    @property
+    def url(self):
+        """The URL, which ca be used to connect to the LDAP server."""
+        if not self.host:
+            return None
+
+        port = ''
+        if self.use_ldaps:
+            if self.port != DEFAULT_PORT_LDAPS:
+                port = ':{}'.format(self.port)
+        else:
+            if self.port != DEFAULT_PORT_LDAP:
+                port = ':{}'.format(self.port)
+
+        return '{s}://{h}{p}'.format(s=self.schema, h=self.host, p=port)
+
+    # -------------------------------------------------------------------------
+    def __repr__(self):
+        """Typecasting into a string for reproduction."""
+
+        out = "<%s(" % (self.__class__.__name__)
+
+        fields = []
+        fields.append("appname={!r}".format(self.appname))
+        fields.append("host={!r}".format(self.host))
+        fields.append("use_ldaps={!r}".format(self.use_ldaps))
+        fields.append("port={!r}".format(self.port))
+        fields.append("base_dn={!r}".format(self.base_dn))
+        fields.append("bind_dn={!r}".format(self.bind_dn))
+        fields.append("bind_pw={!r}".format(self.bind_pw))
+        fields.append("admin_filter={!r}".format(self.admin_filter))
+        fields.append("initialized={!r}".format(self.initialized))
+
+        out += ", ".join(fields) + ")>"
+        return out
+
+    # -------------------------------------------------------------------------
+    def __copy__(self):
+
+        new = self.__class__(
+            appname=self.appname, verbose=self.verbose, base_dir=self.base_dir, host=self.host,
+            use_ldaps=self.use_ldaps, port=self.port, base_dn=self.base_dn, bind_dn=self.bind_dn,
+            bind_pw=self.bind_pw, admin_filter=self.admin_filter, initialized=self.initialized)
+
+        return new
+
+    # -------------------------------------------------------------------------
+    @classmethod
+    def init_from_config(cls, name, data, appname=None, verbose=0, base_dir=None):
+
+        new = cls(appname=appname, verbose=verbose, base_dir=base_dir)
+
+        s_name = "ldap:" + name
+        msg_invalid = _("Invalid value {val!r} in section {sec!r} for a LDAP {what}.")
+
+        for key in data.keys():
+            value = data[key]
+
+            if cls.re_host_key.match(key):
+                if value.strip():
+                    new.host = value
+                else:
+                    msg = msg_invalid.format(val=value, sec=s_name, what='host')
+                    LOG.error(msg)
+                continue
+
+            if cls.re_ldaps_key.match(key):
+                new.use_ldaps = value
+                continue
+
+            if cls.re_port_key.match(key):
+                port = DEFAULT_PORT_LDAP
+                try:
+                    port = int(value)
+                except (ValueError, TypeError) as e:
+                    msg = msg_invalid.format(val=value, sec=s_name, what='port')
+                    msg += ' ' + str(e)
+                    LOG.error(msg)
+                    continue
+                if port <= 0 or port > MAX_PORT_NUMBER:
+                    msg = msg_invalid.format(val=value, sec=s_name, what='port')
+                    LOG.error(msg)
+                    continue
+                new.port = port
+                continue
+
+            if cls.re_base_dn_key.match(key):
+                if value.strip():
+                    new.base_dn = value
+                else:
+                    msg = msg_invalid.format(val=value, sec=s_name, what='base_dn')
+                    LOG.error(msg)
+                continue
+
+            if cls.re_bind_dn_key.match(key):
+                new.bind_dn = value
+                continue
+
+            if cls.re_bind_pw_key.match(key):
+                new.bind_pw = value
+                continue
+
+            if cls.re_admin_filter_key.match(key):
+                new.admin_filter = value
+                continue
+
+            if key.lower() in ['is_admin', 'readonly', 'tier', 'sync-source']:
+                continue
+
+            msg = _("Unknown LDAP configuration key {key} found in section {sec!r}.").format(
+                key=key, sec=s_name)
+            LOG.error(msg)
+
+        new.initialized = True
+
+        return new
+
+
+# =============================================================================
+class LdapConnectionDict(dict, FbGenericBaseObject):
+    """A dictionary containing LdapConnectionInfo as values and their names as keys."""
+
+    # -------------------------------------------------------------------------
+    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(LdapConnectionDict, self).as_dict(short=short)
+
+        for key in self.keys():
+            res[key] = self[key].as_dict(short=short)
+
+        return res
+
+    # -------------------------------------------------------------------------
+    def __copy__(self):
+
+        new = self.__class__()
+
+        for key in self.keys():
+            new[key] = copy.copy(self[key])
+
+        return new
+
+
+# =============================================================================
+if __name__ == "__main__":
+
+    pass
+
+# =============================================================================
+
+# vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4 list
index daf01571a319e6545ab8ea4df506bbb9b2b9d3a4..748374dd139065656a96ee9133ccacadf05448f0 100644 (file)
@@ -47,8 +47,9 @@ from fb_vmware.iface import VsphereVmInterface
 from fb_vmware.datastore import VsphereDatastore
 
 from . import print_section_start, print_section_end
+from . import DEFAULT_PORT_LDAP, DEFAULT_PORT_LDAPS
 
-from .config import CrTplConfiguration, DEFAULT_PORT_LDAP, DEFAULT_PORT_LDAPS
+from .config import CrTplConfiguration
 
 from .cobbler import Cobbler