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):
+++ /dev/null
-#!/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
--- /dev/null
+#!/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
--- /dev/null
+#!/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
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