from fb_tools.obj import FbBaseObject
-from fb_tools.config import ConfigError, BaseConfiguration
+from fb_tools.config import BaseConfiguration
-from fb_tools.common import to_bool, RE_FQDN
+from fb_tools.common import to_bool, RE_FQDN, is_sequence
from fb_tools.pdns import DEFAULT_PORT as DEFAULT_PDNS_API_PORT
from fb_tools.pdns import DEFAULT_TIMEOUT as DEFAULT_PDNS_API_TIMEOUT # noqa
from fb_tools.pdns import DEFAULT_API_PREFIX as DEFAULT_PDNS_API_PREFIX # noqa
from fb_tools.pdns import DEFAULT_USE_HTTPS as DEFAULT_PDNS_API_USE_HTTPS
+from .errors import CrTfConfigError
+
from .xlate import XLATOR
-__version__ = '1.2.1'
+__version__ = '1.3.1'
LOG = logging.getLogger(__name__)
_ = XLATOR.gettext
ngettext = XLATOR.ngettext
-# =============================================================================
-class CrTfConfigError(ConfigError):
- """Base error class for all exceptions happened during
- execution this configured application"""
-
- pass
-
-
# =============================================================================
class VsphereConfig(FbBaseObject):
"""Class for encapsulation of config data of a connection to a VSPhere center."""
default_user = 'Administrator@vsphere.local'
default_dc = 'vmcc'
default_cluster = 'vmcc-l105-01'
+ default_min_root_size_gb = 32.0
+ default_guest_id = "oracleLinux7_64Guest"
default_template_name = 'oracle-linux-7-template'
def __init__(
self, appname=None, verbose=0, version=__version__, base_dir=None, name=None,
host=None, port=None, user=None, password=None, dc=None, cluster=None,
- template_name=None, initialized=False):
+ template_name=None, min_root_size_gb=None, excluded_ds=None, guest_id=None,
+ initialized=False):
self._name = None
self._host = None
self._dc = self.default_dc
self._cluster = self.default_cluster
self._template_name = self.default_template_name
+ self._min_root_size_gb = self.default_min_root_size_gb
+ self._guest_id = self.default_guest_id
+ self.excluded_ds = []
super(VsphereConfig, self).__init__(
appname=appname, verbose=verbose, version=version,
self.cluster = cluster
if template_name is not None:
self.template_name = template_name
+ if min_root_size_gb is not None:
+ self.min_root_size_gb = min_root_size_gb
+ if guest_id is not None:
+ self.guest_id = guest_id
+
+ if excluded_ds:
+ if is_sequence(excluded_ds):
+ for ds in excluded_ds:
+ self.excluded_ds.append(str(ds))
+ else:
+ self.excluded_ds.append(str(excluded_ds))
if initialized:
self.initialized = True
+ # -----------------------------------------------------------
+ @property
+ def name(self):
+ """The name of the VSphere."""
+ return self._name
+
+ @name.setter
+ def name(self, value):
+ if value is None:
+ self._name = None
+ return
+ val = str(value).strip().lower()
+ if val == '':
+ self._name = None
+ else:
+ self._name = val
+
+ # -----------------------------------------------------------
+ @property
+ def host(self):
+ """The host name or address of the VSphere server."""
+ return self._host
+
+ @host.setter
+ def host(self, value):
+ if value is None:
+ self._host = None
+ return
+ val = str(value).strip().lower()
+ if val == '':
+ self._host = None
+ else:
+ self._host = val
+
+ # -----------------------------------------------------------
+ @property
+ def port(self):
+ """The TCP port number, where the API is listening on the VSphere server."""
+ return self._port
+
+ @port.setter
+ def port(self, value):
+ if value is None:
+ self._port = self.default_port
+ return
+ val = self.default_port
+ try:
+ val = int(value)
+ if val < 1:
+ msg = _("a port may not be less than 1: {}.").format(val)
+ raise CrTfConfigError(msg)
+ max_val = (2**16) -1
+ if val > max_val:
+ msg = _("a port may not be greater than {m}: {v}.").format(
+ m=max_val, v=val)
+ raise CrTfConfigError(msg)
+ except ValueError as e:
+ msg = _("Wrong port number {v!r}: {e}").format(v=value, e=e)
+ LOG.error(msg)
+ else:
+ self._port = val
+
+ # -----------------------------------------------------------
+ @property
+ def user(self):
+ """The user name to connect to the VSphere server."""
+ return self._user
+
+ @user.setter
+ def user(self, value):
+ if value is None:
+ self._user = self.default_user
+ return
+ val = str(value).strip()
+ if val == '':
+ self._user = self.default_user
+ else:
+ self._user = val
+
+ # -----------------------------------------------------------
+ @property
+ def password(self):
+ """The password of the VSphere user."""
+ return self._password
+
+ @password.setter
+ def password(self, value):
+ if value is None:
+ self._password = None
+ return
+ val = str(value)
+ if val == '':
+ self._password = None
+ else:
+ self._password = val
+
+ # -----------------------------------------------------------
+ @property
+ def dc(self):
+ """The name of the datacenter in VSphere."""
+ return self._dc
+
+ @dc.setter
+ def dc(self, value):
+ if value is None:
+ self._dc = self.default_dc
+ return
+ val = str(value).strip()
+ if val == '':
+ self._dc = self.default_dc
+ else:
+ self._dc = val
+
+ # -----------------------------------------------------------
+ @property
+ def cluster(self):
+ """The name of the default cluster in VSphere."""
+ return self._cluster
+
+ @cluster.setter
+ def cluster(self, value):
+ if value is None:
+ self._cluster = self.default_cluster
+ return
+ val = str(value).strip()
+ if val == '':
+ self._cluster = self.default_cluster
+ else:
+ self._cluster = val
+
+ # -----------------------------------------------------------
+ @property
+ def template_name(self):
+ """The name of the default cluster in VSphere."""
+ return self._template_name
+
+ @template_name.setter
+ def template_name(self, value):
+ if value is None:
+ self._template_name = self.default_template_name
+ return
+ val = str(value).strip()
+ if val == '':
+ self._template_name = self.default_template_name
+ else:
+ self._template_name = val
+
+ # -----------------------------------------------------------
+ @property
+ def min_root_size_gb(self):
+ """The minimum size of a root disk in GiB."""
+ return self._min_root_size_gb
+
+ @min_root_size_gb.setter
+ def min_root_size_gb(self, value):
+ if value is None:
+ self._min_root_size_gb = self.default_min_root_size_gb
+ return
+ val = self.default_min_root_size_gb
+ try:
+ val = float(value)
+ if val < 10:
+ msg = _("may not be less than 10: {:0.1f}.").format(val)
+ raise CrTfConfigError(msg)
+ max_val = 4 * 1024
+ if val > max_val:
+ msg = _("may not be greater than {m}: {v:0.1f}.").format(
+ m=max_val, v=val)
+ raise CrTfConfigError(msg)
+ except ValueError as e:
+ msg = _("Wrong minimum root size in GiB {v!r}: {e}").format(v=value, e=e)
+ LOG.error(msg)
+ else:
+ self._min_root_size_gb = val
+
+ # -----------------------------------------------------------
+ @property
+ def guest_id(self):
+ """The user name to connect to the VSphere server."""
+ return self._guest_id
+
+ @guest_id.setter
+ def guest_id(self, value):
+ if value is None:
+ self._guest_id = self.default_guest_id
+ return
+ val = str(value).strip()
+ if val == '':
+ self._guest_id = self.default_guest_id
+ else:
+ self._guest_id = val
+
+ # -------------------------------------------------------------------------
+ def as_dict(self, short=True, show_secrets=False):
+ """
+ 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(VsphereConfig, self).as_dict(short=short)
+
+ res['name'] = self.name
+ res['host'] = self.host
+ res['port'] = self.port
+ res['user'] = self.user
+ res['dc'] = self.dc
+ res['cluster'] = self.cluster
+ res['template_name'] = self.template_name
+ res['min_root_size_gb'] = self.min_root_size_gb
+ res['guest_id'] = self.guest_id
+
+ if self.password:
+ if show_secrets or self.verbose > 4:
+ res['password'] = self.password
+ else:
+ res['password'] = '*******'
+
+ return res
+
+ # -------------------------------------------------------------------------
+ def __copy__(self):
+
+ vsphere = self.__class__(
+ appname=self.appname, verbose=self.verbose, base_dir=self.base_dir,
+ initialized=self.initialized, host=self.host, port=self.port, user=self.user,
+ password=self.password, dc=self.dc, cluster=self.cluster,
+ template_name=self.template_name, excluded_ds=self.excluded_ds,
+ min_root_size_gb=self.min_root_size_gb, guest_id=self.guest_id)
+ return vsphere
+
+ # -------------------------------------------------------------------------
+ def __eq__(self, other):
+
+ if self.verbose > 4:
+ LOG.debug(_("Comparing {} objects ...").format(self.__class__.__name__))
+
+ if not isinstance(other, VsphereConfig):
+ return False
+
+ if self.name != other.name:
+ return False
+ if self.host != other.host:
+ return False
+ if self.port != other.port:
+ return False
+ if self.user != other.user:
+ return False
+ if self.password != other.password:
+ return False
+ if self.dc != other.dc:
+ return False
+ if self.cluster != other.cluster:
+ return False
+ if self.template_name != other.template_name:
+ return False
+ if self.min_root_size_gb != other.min_root_size_gb:
+ return False
+ if self.guest_id != other.guest_id:
+ return False
+ if self.excluded_ds != other.excluded_ds:
+ return False
+
+ return True
+
+ # -------------------------------------------------------------------------
+ def is_valid(self, raise_on_error=False):
+
+ error_lst = []
+
+ attribs = ('name', 'host', 'user', 'password')
+ for attrib in attribs:
+ cur_val = getattr(self, attrib, None)
+ if not cur_val:
+ name = '<{}>'.format(_('unknown'))
+ if self.name:
+ name = self.name
+ msg = _("Attribute {a!r} of the {o}-object {n!r} is not set.").format(
+ a=attrib, o=self.__class__.__name__, n=name)
+ error_lst.append(msg)
+ if not raise_on_error:
+ LOG.error(msg)
+ if error_lst:
+ if raise_on_error:
+ nr = len(error_lst)
+ msg = ngettext(
+ 'Found an error in VSPhere configuration',
+ 'Found {} errors in VSPhere configuration', nr)
+ msg = msg.format(nr) + '\n * ' + '\n * '.join(error_lst)
+ raise CrTfConfigError(msg)
+ return False
+ return True
# =============================================================================
class CrTfConfiguration(BaseConfiguration):
and methods to read it from configuration files.
"""
- default_vsphere_port = 443
- default_vsphere_user = 'Administrator@vsphere.local'
- default_vsphere_dc = 'vmcc'
- default_vsphere_cluster = 'vmcc-l105-01'
-
- default_template_name = 'oracle-linux-7-template'
-
default_pdns_master_server = 'master.pp-dns.com'
default_pdns_api_port = DEFAULT_PDNS_API_PORT
default_pdns_api_use_https = bool(DEFAULT_PDNS_API_USE_HTTPS)
self, appname=None, verbose=0, version=__version__, base_dir=None, simulate=False,
encoding=None, config_dir=None, config_file=None, initialized=False):
- self.vsphere_host = self.default_vsphere_host
- self.vsphere_port = self.default_vsphere_port
- self.vsphere_user = self.default_vsphere_user
- self._vsphere_password = None
- self.vsphere_dc = self.default_vsphere_dc
- self.vsphere_cluster = self.default_vsphere_cluster
- self.template_name = self.default_template_name
self.pdns_master_server = self.default_pdns_master_server
self.pdns_api_port = self.default_pdns_api_port
self._pdns_api_key = None
self.puppetca = self.default_puppetca
self.pdns_comment_account = self.default_pdns_comment_account
+ self.vsphere = {}
+
self._disk_size = self.default_disk_size
self._root_min_size = self.default_root_min_size
def simulate(self, value):
self._simulate = to_bool(value)
- # -----------------------------------------------------------
- @property
- def vsphere_password(self):
- """The password of the VSphere user."""
- return self._vsphere_password
-
- @vsphere_password.setter
- def vsphere_password(self, value):
- if value is None:
- self._vsphere_password = None
- return
- val = str(value)
- if val == '':
- self._vsphere_password = None
- else:
- self._vsphere_password = val
-
# -----------------------------------------------------------
@property
def pdns_api_key(self):
res['simulate'] = self.simulate
res['pdns_api_use_https'] = self.pdns_api_use_https
res['pdns_api_timeout'] = self.pdns_api_timeout
- res['vsphere_password'] = None
res['vm_root_password'] = None
res['pdns_api_key'] = None
res['relative_tf_dir'] = self.relative_tf_dir
res['root_min_size'] = self.root_min_size
res['root_max_size'] = self.root_max_size
- if self.vsphere_password:
- if show_secrets or self.verbose > 4:
- res['vsphere_password'] = self.vsphere_password
- else:
- res['vsphere_password'] = '*******'
+ res['vsphere'] = {}
+ for vsphere_name in self.vsphere.keys():
+ res['vsphere'][vsphere_name] = self.vsphere[vsphere_name].as_dict(
+ short=short, show_secrets=show_secrets)
if self.pdns_api_key:
if show_secrets or self.verbose > 4:
super(CrTfConfiguration, self).eval_config_section(config, section_name)
- if section_name.lower() == 'vsphere':
- self.eval_config_vsphere(config, section_name)
- elif section_name.lower() == 'powerdns' or section_name.lower() == 'pdns':
+ sn = section_name.lower()
+
+ if sn == 'vsphere' or sn.startswith('vsphere:'):
+ if sn.startswith('vsphere:'):
+ vsphere_name = sn.replace('vsphere:', '').strip()
+ if vsphere_name == '':
+ LOG.error(_("Empty VSpehre name found."))
+ else:
+ self.eval_config_vsphere(config, section_name, vsphere_name)
+ else:
+ self.eval_config_vsphere(config, section_name, '_default')
+ elif sn == 'powerdns' or sn == 'pdns':
self.eval_config_pdns(config, section_name)
- elif section_name.lower() == 'terraform':
+ elif sn == 'terraform':
self.eval_config_terraform(config, section_name)
# -------------------------------------------------------------------------
try:
tz = pytz.timezone(val) # noqa
except pytz.exceptions.UnknownTimeZoneError as e:
- raise ConfigError(self.msg_invalid_type.format(
+ raise CrTfConfigError(self.msg_invalid_type.format(
f=self.config_file, s=section_name, v=value, n='time_zone', e=e))
self.tz_name = value.strip()
elif re_puppetmaster.search(key) and value.strip():
val = value.strip()
if not RE_FQDN.search(val):
- raise ConfigError(self.msg_invalid_type.format(
+ raise CrTfConfigError(self.msg_invalid_type.format(
f=self.config_file, s=section_name, v=value, n='puppet_master',
e='Invalid Host FQDN for puppetmaster'))
self.puppetmaster = val.lower()
elif re_puppetca.search(key) and value.strip():
val = value.strip()
if not RE_FQDN.search(val):
- raise ConfigError(self.msg_invalid_type.format(
+ raise CrTfConfigError(self.msg_invalid_type.format(
f=self.config_file, s=section_name, v=value, n='puppet_ca',
e='Invalid Host FQDN for puppetca'))
self.puppetca = val.lower()
# -------------------------------------------------------------------------
- def eval_config_vsphere(self, config, section):
+ def eval_config_vsphere(self, config, section_name, vsphere_name):
if self.verbose > 2:
- LOG.debug(_("Checking config section {!r} ...").format(section))
+ LOG.debug(_("Checking config section {s!r} ({n}) ...").format(
+ s=section_name, n=vsphere_name))
re_excl_ds = re.compile(r'^\s*excluded?[-_]datastores?\s*$', re.IGNORECASE)
re_split_ds = re.compile(r'[,;\s]+')
r'^\s*min[-_\.]?root[-_\.]?size(?:[-_\.]?gb)\s*$', re.IGNORECASE)
re_guest_id = re.compile(r'^\s*guest[-_]?id\s*$', re.IGNORECASE)
- for (key, value) in config.items(section):
+ params = {
+ 'appname': self.appname,
+ 'verbose': self.verbose,
+ 'base_dir': self.base_dir,
+ 'name': vsphere_name,
+ }
+
+ for (key, value) in config.items(section_name):
if key.lower() == 'host' and value.strip():
- self.vsphere_host = value.strip().lower()
+ params['host'] = value.strip()
elif key.lower() == 'port':
- val = 0
- try:
- val = int(value)
- except ValueError as e:
- raise ConfigError(self.msg_invalid_type.format(
- f=self.config_file, s=section, v=value, n='port', e=e))
- if val < 0:
- raise ConfigError(self.msg_val_negative.format(
- f=self.config_file, s=section, v=value, n='port'))
- self.vsphere_port = val
+ params['port'] = value
elif key.lower() == 'user' and value.strip():
- self.vsphere_user = value.strip()
+ params['port'] = value.strip()
elif key.lower() == 'password':
- self.vsphere_password = value
+ params['password'] = value
elif key.lower() == 'dc' and value.strip():
- self.vsphere_dc = value.strip()
+ params['dc'] = value.strip()
elif key.lower() == 'cluster' and value.strip():
- self.vsphere_cluster = value.strip()
+ params['cluster'] = value.strip()
elif re_template.search(key) and value.strip():
- self.template_name = value.strip()
+ params['template_name'] = value.strip()
elif re_excl_ds.search(key) and value.strip():
datastores = re_split_ds.split(value.strip())
- self.excluded_datastores = datastores
+ params['excluded_ds'] = datastores
elif re_min_root_size.search(key) and value.strip():
- val = 0.0
- try:
- val = float(value.strip())
- except ValueError as e:
- raise ConfigError(self.msg_invalid_type.format(
- f=self.config_file, s=section, v=value, n=key, e=e))
- if val < 0:
- raise ConfigError(self.msg_val_negative.format(
- f=self.config_file, s=section, v=value, n=key))
- self.min_root_size_gb = val
+ params['min_root_size_gb'] = value
elif re_guest_id.search(key) and value.strip():
- self.guest_id = value.strip()
+ params['guest_id'] = datastores
+ else:
+ msg = _(
+ "Unknown configuration parameter {k!r} with value {v!r} for VSPhere {n!r} "
+ "found.").format(k=key, v=value, n=vsphere_name)
+ LOG.warning(msg)
+
+ if self.verbose > 2:
+ msg = _("Creating a {}-object with parameters:").format('VsphereConfig')
+ msg += '\n' + pp(params)
+ LOG.debug(msg)
+ vsphere = VsphereConfig(**params)
+ if self.verbose > 2:
+ LOG.debug(_("Created object:") + '\n' + pp(vsphere.as_dict()))
+
+ vsphere.is_valid(raise_on_error=True)
+
+ self.vsphere[vsphere_name] = vsphere
return
try:
val = int(value.strip())
except ValueError as e:
- raise ConfigError(self.msg_invalid_type.format(
+ raise CrTfConfigError(self.msg_invalid_type.format(
f=self.config_file, s=section, v=value, n=key, e=e))
if val < 0:
- raise ConfigError(self.msg_val_negative.format(
+ raise CrTfConfigError(self.msg_val_negative.format(
f=self.config_file, s=section, v=value, n=key))
self.pdns_api_port = val
elif re_key.search(key) and value.strip():