From 77800984bc282c422c6e13b6285c15982e084fa4 Mon Sep 17 00:00:00 2001 From: Sam Yaple Date: Thu, 17 Sep 2015 09:53:31 +0000 Subject: [PATCH] Refactor set_configs.py This refactor brings the logging in line with the rest of Kolla. The fucntion names were updated to reflect thier new role. Additionally, it fixes several issues with the permissions which currently break all containers that use set_configs.py It will also work with source being a directory or a file now. Change-Id: I4a197a343e3baf3bd31532debdff5972adb8aefa Partially-Implements: blueprint replace-config-external --- docker/base/set_configs.py | 265 +++++++++++++++++-------------------- docker/mariadb/start.sh | 2 +- 2 files changed, 121 insertions(+), 146 deletions(-) diff --git a/docker/base/set_configs.py b/docker/base/set_configs.py index 0c86c0621d..7b57735506 100644 --- a/docker/base/set_configs.py +++ b/docker/base/set_configs.py @@ -26,186 +26,161 @@ LOG = logging.getLogger(__name__) LOG.setLevel(logging.INFO) -def json_key_validation(json_file): - valid_keys = ['source', 'dest', 'owner', 'perm'] +def validate_config(config): + required_keys = {'source', 'dest', 'owner', 'perm'} - # 'command' is not present in the json file - if json_file.get('command') is None: - LOG.error('command was never specified in your json file. Command ' - 'is what your container will execute upon start.') + if 'command' not in config: + LOG.error('Config is missing required "command" key') sys.exit(1) - # Check for valid keys - for data in json_file.get('config_files'): - key_not_found = '' - for valid_key in valid_keys: - if valid_key not in data.keys(): - key_not_found += valid_key + ' ' - - if key_not_found is not '': - LOG.error('JSON data "%s" is missing keys "%s"' - % (data.keys(), key_not_found)) + # Validate config sections + for data in config.get('config_files', list()): + # Verify required keys exist. Only 'source' and 'dest' are + # required. 'owner' and 'perm' should user system defaults if not + # specified + if not data.viewkeys() >= required_keys: + LOG.error('Config is missing required keys: {}'.format(data)) sys.exit(1) - for key in data.keys(): - # Invalid key in json file - if key not in valid_keys: - LOG.error('Unexpected JSON key "%s". This value is currently ' - 'not supported.' % key) - sys.exit(1) +def copy_files(data): + dest = data.get('dest') + source = data.get('source') -# The command option will be built up and written to '/command_options'. -# which will be added to the end of $CMD in start.sh as $ARGS. -def write_command_options(args): - with open('/command_options', 'w+') as f: - f.write(args) + if not os.path.exists(source): + LOG.error('The source to copy does not exist: {}'.format(source)) + sys.exit(1) - -def copy_configurations(): - json_path = '/opt/kolla/config_files/config.json' - - LOG.info('Loading config json file "%s".' % json_path) - - # If JSON file is empty don't move any configs. - # It's required there always be at least 'command' in the json file - with open(json_path) as conf: - try: - config = json.load(conf) - except ValueError: - LOG.error('Empty config json file present. There are no config ' - 'files being moved.') - sys.exit(1) - - json_key_validation(config) - - # Save the 'command' specified in the json file so start.sh can - # consume it. - cmd = config.get('command') - write_command_options(cmd) - - for data in config.get('config_files'): - dest_path = data.get('dest') - source_path = data.get('source') - config_owner = data.get('owner') - LOG.info('The command being run is "%s"' % cmd) - - # Make sure all the proper config dirs are in place. - if os.path.isdir(dest_path): - # The destination is a dir - LOG.info('Checking if parent directories for "%s" exist.' - % dest_path) + if os.path.exists(dest): + LOG.info('Removing existing destination: {}'.format(dest)) + if os.path.isdir(dest): + shutil.rmtree(dest) else: - # The destination is a file - dest_path = os.path.dirname(data.get('dest')) - LOG.info('Checking if parent directories for "%s" exist.' - % dest_path) + os.remove(dest) - if os.path.exists(dest_path): - LOG.info('Config destination "%s" has the proper directories ' - 'in place.' % dest_path) - else: - os.makedirs(dest_path) - LOG.info('Creating directory "%s" because it was not found.' - % dest_path) + if os.path.isdir(source): + source_path = source + dest_path = dest + else: + source_path = os.path.dirname(source) + dest_path = os.path.dirname(dest) - # Copy over the config file(s). - if os.path.isdir(source_path): - # The source is a dir - LOG.info('Checking if there are any config files mounted ' - 'in "%s".' % source_path) - config_files = os.listdir(source_path) - if config_files == []: - LOG.warning('The source directory "%s" is empty. No ' - 'config files will be copied.' - % source_path) + if not os.path.exists(dest_path): + LOG.info('Creating dest parent directory: {}'.format(dest_path)) + os.makedirs(dest_path) + + if source != source_path: + # Source is file + LOG.info('Copying {} to {}'.format(source, dest)) + shutil.copy(source, dest) + else: + # Source is a directory + for src in os.listdir(source_path): + LOG.info('Copying {} to {}'.format(src, dest_path)) + if os.path.isdir(src): + shutil.copytree(src, dest_path) else: - # Source and dest need to either both be dirs or files - if os.path.isdir(dest_path): - for config in config_files: - shutil.copy(config, dest_path) - LOG.info('Config file found. Copying config file ' - '"%s" to "%s".' - % (config, dest_path)) - else: - LOG.error('If you specify the config source as a ' - 'directory, then the destination also needs ' - 'to be a directory') - sys.exit(1) - else: - # The source is a file - LOG.info('Checking if there is a config file mounted in "%s".' - % (source_path)) - if os.path.exists(source_path): - shutil.copy(source_path, dest_path) - LOG.info('Config file found. Copying config file "%s" to ' - '"%s".' % (source_path, dest_path)) + shutil.copy(src, dest_path) - if dest_path in cmd: - LOG.info('Using config file: "%s" to start the %s ' - 'service' - % (source_path, config_owner)) - else: - LOG.warning('The config file "%s" is present, but you ' - 'are not using it when starting %s. ' - % (source_path, config_owner)) - else: - LOG.warning('Skipping config "%s" because it was not ' - 'mounted at the expected location: "%s".' - % (dest_path, source_path)) - - # Check for user and group id in the environment. - try: - uid = getpwnam(config_owner).pw_uid - except KeyError: - LOG.error('The user "%s" does not exist.' - % config_owner) - sys.exit(1) - try: - gid = getpwnam(config_owner).pw_gid - except KeyError: - LOG.error('The group "%s" doesn\'t exist.' - % config_owner) - sys.exit(1) +def set_permissions(data): + def set_perms(file_, uid, guid, perm): + LOG.info('Setting permissions for {}'.format(file_)) # Give config file proper perms. try: - os.chown(dest_path, uid, gid) + os.chown(file_, uid, gid) except OSError as e: - LOG.error("Couldn't chown file %s because of" - "os error %s." % (dest_path, e)) + LOG.error('While trying to chown {} received error: {}'.format( + file_, e)) sys.exit(1) try: - os.chmod(dest_path, int(data.get('perm'))) + os.chmod(file_, perm) except OSError as e: - LOG.error("Couldn't chown file %s because of" - "os error %s." % (dest_path, e)) + LOG.error('While trying to chmod {} received error: {}'.format( + file_, e)) sys.exit(1) + dest = data.get('dest') + owner = data.get('owner') + perm = int(data.get('perm'), 0) + + # Check for user and group id in the environment. + try: + uid = getpwnam(owner).pw_uid + except KeyError: + LOG.error('The specified user does not exist: {}'.format(owner)) + sys.exit(1) + try: + gid = getpwnam(owner).pw_gid + except KeyError: + LOG.error('The specified group does not exist: {}'.format(owner)) + sys.exit(1) + + # Set permissions on the top level dir or file + set_perms(dest, uid, gid, perm) + if os.path.isdir(dest): + # Recursively set permissions + for root, dirs, files in os.walk(dest): + for dir_ in dirs: + set_perms(os.path.join(root, dir_), uid, gid, perm) + for file_ in files: + set_perms(os.path.join(root, file_), uid, gid, perm) + + +def load_config(): + config_file = '/opt/kolla/config_files/config.json' + LOG.info('Loading config file at {}'.format(config_file)) + + # Attempt to read config file + with open(config_file) as f: + try: + config = json.load(f) + except ValueError: + LOG.error('Invalid json file found at {}'.format(config_file)) + sys.exit(1) + except IOError as e: + LOG.error('Could not read file {}. Failed with error {}'.format( + config_file, e)) + sys.exit(1) + + LOG.info('Validating config file') + validate_config(config) + + if 'config_files' in config: + LOG.info('Copying service configuration files') + for data in config['config_files']: + copy_files(data) + set_permissions(data) + else: + LOG.debug('No files to copy found in config') + + LOG.info('Writing out command to execute') + LOG.debug('Command is: {}'.format(config['command'])) + # The value from the 'command' key will be written to '/run_command' + with open('/run_command', 'w+') as f: + f.write(config['command']) + def execute_config_strategy(): try: - kolla_config_strategy = os.environ.get("KOLLA_CONFIG_STRATEGY") + config_strategy = os.environ.get("KOLLA_CONFIG_STRATEGY") + LOG.info('Kolla config strategy set to: {}'.format(config_strategy)) except KeyError: LOG.error("KOLLA_CONFIG_STRATEGY is not set properly.") sys.exit(1) - if kolla_config_strategy == "COPY_ALWAYS": - # Read all existing json files. - copy_configurations() - elif kolla_config_strategy == "COPY_ONCE": + if config_strategy == "COPY_ALWAYS": + load_config() + elif config_strategy == "COPY_ONCE": if os.path.exists('/configured'): - LOG.info("This container has already been configured; " - "Refusing to copy new configs.") + LOG.info("The config strategy prevents copying new configs") sys.exit(0) else: - copy_configurations() + load_config() f = open('/configured', 'w+') f.close() - else: - LOG.error("KOLLA_CONFIG_STRATEGY is not set properly: %s." - % kolla_config_strategy) + LOG.error('KOLLA_CONFIG_STRATEGY is not set properly') sys.exit(1) diff --git a/docker/mariadb/start.sh b/docker/mariadb/start.sh index f33dc74938..69f40818c8 100755 --- a/docker/mariadb/start.sh +++ b/docker/mariadb/start.sh @@ -7,7 +7,7 @@ source /opt/kolla/kolla-common.sh # Generate run command python /opt/kolla/set_configs.py -CMD=$(cat /command_options) +CMD=$(cat /run_command) # Loading functions source /opt/kolla/config/config-galera.sh