Source code for spicerack.confctl

"""Confctl module to abstract Conftool."""
import logging
import re

from conftool import configuration, kvobject, loader
from conftool.drivers import BackendError

from spicerack.exceptions import SpicerackError


logger = logging.getLogger(__name__)  # pylint: disable=invalid-name


[docs]class ConfctlError(SpicerackError): """Custom exception class for errors of this module."""
[docs]class Confctl: """Confctl class to abstract conftool operations.""" def __init__(self, config='/etc/conftool/config.yaml', schema='/etc/conftool/schema.yaml', dry_run=True): """Initialize the instance. Arguments: config (str, optional): the path to the configuration file to load. schema (str, optional): the path to the Conftool schema to load. dry_run (bool, optional): whether this is a DRY-RUN. """ self._dry_run = dry_run self._schema = loader.Schema.from_file(schema) kvobject.KVObject.setup(configuration.get(config))
[docs] def entity(self, entity_name): """Get the Conftool specific entity class. Arguments: entity_name (str): the name of the entiryself. Returns: spicerack.confctl.ConftoolEntity: and entity-specific class to perform Conftool operations. """ return ConftoolEntity(self._schema.entities[entity_name], dry_run=self._dry_run)
[docs]class ConftoolEntity: """ConftoolEntity class to perform operations on a specific Conftool entity.""" def __init__(self, entity, dry_run=True): """Initialize the instance. Arguments: entity (conftool.kvobject.Entity): an instance of Conftool entity. dry_run (bool, optional): whether this is a DRY-RUN. """ self._entity = entity self._dry_run = dry_run def _select(self, tags): """Generator that yields the selected objects based on the provided tags. Arguments: tags (dict): dictionary with tag: expression pairs of Conftool selectors. Yields: conftool.kvobject.Entity: the selected object. Raises: spicerack.confctl.ConfctlError: if not match is found. """ selectors = {} for tag, expr in tags.items(): selectors[tag] = re.compile('^{}$'.format(expr)) obj = None for obj in self._entity.query(selectors): yield obj if obj is None: raise ConfctlError('No match found')
[docs] def update(self, changed, **tags): """Updates the value of conftool objects corresponding to the selection done with tags. Arguments: changed (dict): the new values to set for the selected objects. **tags: arbitrary Conftool tags as keyword arguments. Raises: spicerack.confctl.ConfctlError: on etcd or Conftool errors. Examples: >>> confctl.update({'pooled': False}, service='appservers-.*', name='eqiad') """ logger.debug('Updating conftool matching tags: %s', tags) if self._dry_run: message_prefix = 'Skipping conftool update on dry-run mode' else: message_prefix = 'Updating conftool' for obj in self._select(tags): logger.debug('%s: %s -> %s', message_prefix, obj, changed) if self._dry_run: continue try: obj.update(changed) except BackendError as e: raise ConfctlError('Error writing to etcd') from e except Exception as e: raise ConfctlError('Generic error in conftool') from e
[docs] def get(self, **tags): """Generator that yields conftool objects corresponding to the selection. Arguments: **tags: arbitrary Conftool tags as keyword arguments. Yields: conftool.kvobject.Entity: the selected object. """ for obj in self._select(tags): logger.debug('Selected conftool object: %s', obj) yield obj
[docs] def set_and_verify(self, key, value, **tags): """Set and verify a single Conftool value. Arguments: key (str): the key in Conftool to modify. value (mixed): the value to set. **tags: arbitrary Conftool tags as keyword arguments. Raises: spicerack.confctl.ConfctlError: on etcd or Conftool errors or failing to verify the changes. """ logger.info('Setting %s=%s for tags: %s', key, value, tags) self.update({key: value}, **tags) for obj in self.get(**tags): # Verify the changes were applied new = getattr(obj, key) if new != value and not self._dry_run: raise ConfctlError("Conftool key {key} has value '{new}', expecting '{value}' for tags: {tags}".format( key=key, new=new, value=value, tags=tags))