You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pyentrypoint/pyentrypoint/config.py

296 lines
9.2 KiB
Python

"""
Configuration object
"""
import os
from distutils.util import strtobool
from fnmatch import fnmatch
from grp import getgrnam
from io import open
from pwd import getpwnam
from six import string_types
from yaml import safe_load
from .command import Command
from .constants import ENTRYPOINT_FILE
from .docker_links import DockerLinks
from .links import Links
from .logs import Logs
from .reloader import Reloader
__all__ = ['Config']
def envtobool(key, default):
return bool(strtobool(os.environ.get(key, str(default))))
class ConfigMeta(object):
def _return_item_lst(self, item):
"""Return item as a list"""
if item in self._config:
if isinstance(self._config[item], string_types):
return [self._config[item]]
return self._config[item]
return []
def get_templates(self):
"""Returns iterator of tuple (template, config_file)"""
config_files = self.config_files
if isinstance(config_files, list):
for item in config_files:
if isinstance(item, string_types):
template = item
outfile = item[:-4] if item.endswith('.tpl') else item
if isinstance(item, dict):
template = list(item.keys())[0]
outfile = item[template]
yield (template, outfile)
else:
raise Exception("config_files setup missformated.")
def get_reloader(self):
"""Setup and get reloader"""
config_files = [file[1] for file in self.get_templates()]
reload = self._config['reload']
if isinstance(reload, bool):
return Reloader(files=config_files)
if isinstance(reload, dict):
signal = reload.get('signal', 'SIGHUP')
watch_config_files = bool(reload.get('watch_config_files'))
files = reload.get('files', [])
if not isinstance(files, list):
raise Exception('Reload files is not a list')
if watch_config_files:
files.extend(config_files)
return Reloader(files=files, sig=signal)
def _check_config(self):
for key in self._config:
if not hasattr(Config, key) or key == '__init__':
self.log.warning(
'"{key}" is not a valid option'.format(key=key)
)
def _get_from_env(self, env, key):
val = os.environ.get(env, None)
if val is None:
return None
try:
val = int(val)
except ValueError:
pass
self._config[key] = val
def set_to_env(self):
self.log.debug('Add conf to environment')
for attr in [a for a in dir(self) if not a.startswith('_')]:
setup = getattr(self, attr)
env = 'ENTRYPOINT_{attr}'.format(attr=attr.upper())
if type(setup) is bool and setup:
self.log.debug('set env {env} with "true"'.format(
env=env
))
os.environ[env] = 'true'
if type(setup) is int or type(setup) is str:
if env not in os.environ:
self.log.debug('set env {env} with "{val}"'.format(
env=env,
val=str(setup),
))
os.environ[env] = str(setup)
class Config(ConfigMeta):
"""
Get entrypoint config
Parse entrypoint-config.yml
Config file should always be in WORKDIR and named entrypoint-config.yml
"""
def __init__(self, conf=ENTRYPOINT_FILE, args=[]):
self._config = {}
self.log = Logs().log
self._args = args
self._links = None
self._command = None
self._reload = None
self._config_file = conf
if not os.path.isfile(self._config_file):
self.log.critical('Entrypoint config file does not provided')
return
with open(self._config_file) as f:
self._config = safe_load(stream=f)
self._check_config()
@property
def has_config(self):
"Has config file provided."
return len(self._config) != 0
@property
def commands(self):
"Handled commands"
rtn = self._return_item_lst('commands')
return rtn
@property
def command(self):
"Main command to run."
if self._command:
return self._command
cmd = self._args[0] if self._args else ''
if 'commands' in self._config:
commands = self._return_item_lst('commands')
if [p for p in commands if fnmatch(self._args[0], p)]:
self._command = Command(cmd, self, self._args)
return self._command
for key in ['command', 'cmd']:
if key in self._config:
self.log.warning(
'"command" is deprecated, use "commands" instead',
)
cmd = self._config[key]
self._command = Command(cmd, self, self._args)
return self._command
@property
def subcommands(self):
"""Subcommands to handle as arguments."""
rtn = self._return_item_lst('subcommands')
if rtn:
self.log.warning(
'"subcommands" is deprecated, '
'subcommands will not be handled anymore.',
)
if self.commands:
self.log.warning(
'"subcommands" is ignored as long "commands" in present',
)
return rtn
else:
return ['-*']
@property
def user(self):
"Unix user or uid to run command."
self._get_from_env(env='ENTRYPOINT_USER', key='user')
if 'user' in self._config:
if isinstance(self._config['user'], int):
return self._config['user']
return getpwnam(self._config['user']).pw_uid
return os.getuid()
@property
def group(self):
"Unix group or gid to run command."
self._get_from_env(env='ENTRYPOINT_GROUP', key='group')
if 'group' in self._config:
if isinstance(self._config['group'], int):
return self._config['group']
return getgrnam(self._config['group']).gr_gid
return os.getgid()
@property
def config_files(self):
"List of template config files."
return self._return_item_lst('config_files')
@property
def secret_env(self):
"""Environment variables to delete before running command."""
return self._return_item_lst('secret_env')
@property
def links(self):
"""Links configs singleton."""
if self._links:
return self._links
if 'links' not in self._config:
self._links = Links(links=DockerLinks())
return self._links
self._links = Links(config=self._config['links'], links=DockerLinks())
for name in self._config['links']:
self._links._add_name(name)
return self._links
@property
def pre_conf_commands(self):
"""Return list of preconf commands"""
return self._return_item_lst('pre_conf_commands')
@property
def post_conf_commands(self):
"""Return list of postconf commands"""
return self._return_item_lst('post_conf_commands')
@property
def post_run_commands(self):
"""Return list of post run commands"""
return self._return_item_lst('post_run_commands')
@property
def reload(self):
"""Return Reloader object if reload is set"""
if self._reload:
return self._reload
if (not self._config.get('reload') or
envtobool('ENTRYPOINT_DISABLE_RELOAD', False)):
return None
self._reload = self.get_reloader()
return self._reload
@property
def clean_env(self):
"""Clean env from linked containers before running command"""
if 'clean_env' in self._config:
return bool(self._config['clean_env'])
return True
@property
def remove_dockerenv(self):
"""Remove dockerenv and dockerinit files"""
if 'remove_dockerenv' in self._config:
return bool(self._config['remove_dockerenv'])
return True
@property
def debug(self):
"""Enable debug logs."""
if envtobool('ENTRYPOINT_DEBUG', False):
return True
if 'debug' in self._config:
return bool(self._config['debug'])
return False
@property
def quiet(self):
"""Disable logging"""
if self.debug:
return False
if envtobool('ENTRYPOINT_QUIET', False):
return True
return bool(self._config.get('quiet', False))
@property
def is_disabled(self):
"""Return if service is disabled using environment"""
return envtobool('ENTRYPOINT_DISABLE_SERVICE', False)
@property
def should_config(self):
"""Check environment to tell if config should apply anyway"""
return envtobool('ENTRYPOINT_FORCE', False)
@property
def raw_output(self):
"""Check if command output should be displayed using logging or not"""
if envtobool('ENTRYPOINT_RAW', False):
return True
return bool(self._config.get('raw_output', False))