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
This commit is contained in:
Sam Yaple 2015-09-17 09:53:31 +00:00
parent 1b33345fb8
commit 77800984bc
2 changed files with 121 additions and 146 deletions

View File

@ -26,186 +26,161 @@ LOG = logging.getLogger(__name__)
LOG.setLevel(logging.INFO) LOG.setLevel(logging.INFO)
def json_key_validation(json_file): def validate_config(config):
valid_keys = ['source', 'dest', 'owner', 'perm'] required_keys = {'source', 'dest', 'owner', 'perm'}
# 'command' is not present in the json file if 'command' not in config:
if json_file.get('command') is None: LOG.error('Config is missing required "command" key')
LOG.error('command was never specified in your json file. Command '
'is what your container will execute upon start.')
sys.exit(1) sys.exit(1)
# Check for valid keys # Validate config sections
for data in json_file.get('config_files'): for data in config.get('config_files', list()):
key_not_found = '' # Verify required keys exist. Only 'source' and 'dest' are
for valid_key in valid_keys: # required. 'owner' and 'perm' should user system defaults if not
if valid_key not in data.keys(): # specified
key_not_found += valid_key + ' ' if not data.viewkeys() >= required_keys:
LOG.error('Config is missing required keys: {}'.format(data))
if key_not_found is not '':
LOG.error('JSON data "%s" is missing keys "%s"'
% (data.keys(), key_not_found))
sys.exit(1) 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'. if not os.path.exists(source):
# which will be added to the end of $CMD in start.sh as $ARGS. LOG.error('The source to copy does not exist: {}'.format(source))
def write_command_options(args): sys.exit(1)
with open('/command_options', 'w+') as f:
f.write(args)
if os.path.exists(dest):
def copy_configurations(): LOG.info('Removing existing destination: {}'.format(dest))
json_path = '/opt/kolla/config_files/config.json' if os.path.isdir(dest):
shutil.rmtree(dest)
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: else:
# The destination is a file os.remove(dest)
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): if os.path.isdir(source):
LOG.info('Config destination "%s" has the proper directories ' source_path = source
'in place.' % dest_path) dest_path = dest
else: else:
os.makedirs(dest_path) source_path = os.path.dirname(source)
LOG.info('Creating directory "%s" because it was not found.' dest_path = os.path.dirname(dest)
% dest_path)
# Copy over the config file(s). if not os.path.exists(dest_path):
if os.path.isdir(source_path): LOG.info('Creating dest parent directory: {}'.format(dest_path))
# The source is a dir os.makedirs(dest_path)
LOG.info('Checking if there are any config files mounted '
'in "%s".' % source_path) if source != source_path:
config_files = os.listdir(source_path) # Source is file
if config_files == []: LOG.info('Copying {} to {}'.format(source, dest))
LOG.warning('The source directory "%s" is empty. No ' shutil.copy(source, dest)
'config files will be copied.' else:
% source_path) # 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: else:
# Source and dest need to either both be dirs or files shutil.copy(src, dest_path)
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)
def set_permissions(data):
def set_perms(file_, uid, guid, perm):
LOG.info('Setting permissions for {}'.format(file_))
# Give config file proper perms. # Give config file proper perms.
try: try:
os.chown(dest_path, uid, gid) os.chown(file_, uid, gid)
except OSError as e: except OSError as e:
LOG.error("Couldn't chown file %s because of" LOG.error('While trying to chown {} received error: {}'.format(
"os error %s." % (dest_path, e)) file_, e))
sys.exit(1) sys.exit(1)
try: try:
os.chmod(dest_path, int(data.get('perm'))) os.chmod(file_, perm)
except OSError as e: except OSError as e:
LOG.error("Couldn't chown file %s because of" LOG.error('While trying to chmod {} received error: {}'.format(
"os error %s." % (dest_path, e)) file_, e))
sys.exit(1) 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(): def execute_config_strategy():
try: 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: except KeyError:
LOG.error("KOLLA_CONFIG_STRATEGY is not set properly.") LOG.error("KOLLA_CONFIG_STRATEGY is not set properly.")
sys.exit(1) sys.exit(1)
if kolla_config_strategy == "COPY_ALWAYS": if config_strategy == "COPY_ALWAYS":
# Read all existing json files. load_config()
copy_configurations() elif config_strategy == "COPY_ONCE":
elif kolla_config_strategy == "COPY_ONCE":
if os.path.exists('/configured'): if os.path.exists('/configured'):
LOG.info("This container has already been configured; " LOG.info("The config strategy prevents copying new configs")
"Refusing to copy new configs.")
sys.exit(0) sys.exit(0)
else: else:
copy_configurations() load_config()
f = open('/configured', 'w+') f = open('/configured', 'w+')
f.close() f.close()
else: else:
LOG.error("KOLLA_CONFIG_STRATEGY is not set properly: %s." LOG.error('KOLLA_CONFIG_STRATEGY is not set properly')
% kolla_config_strategy)
sys.exit(1) sys.exit(1)

View File

@ -7,7 +7,7 @@ source /opt/kolla/kolla-common.sh
# Generate run command # Generate run command
python /opt/kolla/set_configs.py python /opt/kolla/set_configs.py
CMD=$(cat /command_options) CMD=$(cat /run_command)
# Loading functions # Loading functions
source /opt/kolla/config/config-galera.sh source /opt/kolla/config/config-galera.sh