From 3d7f9fd624ce058cc43371e97425c5f2091d9c2b Mon Sep 17 00:00:00 2001 From: rthallisey Date: Tue, 25 Aug 2015 17:41:57 -0400 Subject: [PATCH] Replace config-external with a JSON file Replace config-external with JSON file. The JSON file will be placed in each of the services directories with expected location and destination. Set-configs.py will be responsible for interpreting the JSON file, creating the necessary directories, moving config files, and and providing a command line for start.sh to run the service with the correct config files specified. Partially-Implements: blueprint replace-config-external Change-Id: I5e2e69dfe3ae7f938fcf51f1cd450aaa10e7f1e3 --- docker/base/Dockerfile.j2 | 2 +- docker/base/set_configs.py | 218 +++++++++++++++++++++++++++++++++++++ 2 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 docker/base/set_configs.py diff --git a/docker/base/Dockerfile.j2 b/docker/base/Dockerfile.j2 index 26bb55addd..e9d0f97002 100644 --- a/docker/base/Dockerfile.j2 +++ b/docker/base/Dockerfile.j2 @@ -205,4 +205,4 @@ RUN apt-key adv --recv-keys --keyserver hkp://keyserver.ubuntu.com 199369E5404BD {% endif %} -COPY kolla-common.sh /opt/kolla/ +COPY set_configs.py kolla-common.sh /opt/kolla/ diff --git a/docker/base/set_configs.py b/docker/base/set_configs.py new file mode 100644 index 0000000000..0c86c0621d --- /dev/null +++ b/docker/base/set_configs.py @@ -0,0 +1,218 @@ +#!/usr/bin/env python + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import json +import logging +import os +from pwd import getpwnam +import shutil +import sys + + +# TODO(rhallisey): add docstring. +logging.basicConfig() +LOG = logging.getLogger(__name__) +LOG.setLevel(logging.INFO) + + +def json_key_validation(json_file): + valid_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.') + 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)) + 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) + + +# 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) + + +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) + 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) + + 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) + + # 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) + 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)) + + 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) + + # Give config file proper perms. + try: + os.chown(dest_path, uid, gid) + except OSError as e: + LOG.error("Couldn't chown file %s because of" + "os error %s." % (dest_path, e)) + sys.exit(1) + try: + os.chmod(dest_path, int(data.get('perm'))) + except OSError as e: + LOG.error("Couldn't chown file %s because of" + "os error %s." % (dest_path, e)) + sys.exit(1) + + +def execute_config_strategy(): + try: + kolla_config_strategy = os.environ.get("KOLLA_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 os.path.exists('/configured'): + LOG.info("This container has already been configured; " + "Refusing to copy new configs.") + sys.exit(0) + else: + copy_configurations() + f = open('/configured', 'w+') + f.close() + + else: + LOG.error("KOLLA_CONFIG_STRATEGY is not set properly: %s." + % kolla_config_strategy) + sys.exit(1) + + +def main(): + execute_config_strategy() + return 0 + + +if __name__ == "__main__": + sys.exit(main())