Corey Bryant c1b51aa5e0 Enable override of config files
Enables the ability for default config files to be overriden for
each entry point type (simple, uwsgi, and nginx).

Additionally, align unit tests with keystone's snap-openstack.yaml,
as features tend to land in the keystone snap first.

Change-Id: I9fe54f1dae2b94d2f78783f38ced48ebe7c66f0f
2017-05-25 10:16:10 -04:00

320 lines
12 KiB
Python

#!/usr/bin/env python
# Copyright 2016 Canonical UK Limited
#
# 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 logging
import os
import shutil
import yaml
from oslo_concurrency import lockutils
from snap_openstack.renderer import SnapFileRenderer
from snap_openstack.utils import SnapUtils
LOG = logging.getLogger(__name__)
DEFAULT_EP_TYPE = 'simple'
UWSGI_EP_TYPE = 'uwsgi'
NGINX_EP_TYPE = 'nginx'
VALID_EP_TYPES = (DEFAULT_EP_TYPE, UWSGI_EP_TYPE, NGINX_EP_TYPE)
DEFAULT_UWSGI_ARGS = ["--master",
"--die-on-term",
"-H", "{snap}/usr",
"--emperor"]
DEFAULT_NGINX_ARGS = ["-g",
"daemon on; master_process on;"]
DEFAULT_OWNER = "root:root"
DEFAULT_DIR_MODE = 0o750
DEFAULT_FILE_MODE = 0o640
def _render_templates(templates, snap_env, file_mode, user, group):
'''Render file templates using snap environment variables
Render provided dictionary of templates using snap_env,
ensuring that the rendered files have the required ownership
and permissions.
@templates: dict of key value pairs mapping template -> target
@snap_env: dict of environment variables to use for rendering
@file_mode: mode to create any files (provided as hex)
@user: user ownership for files
@group: group ownership for files
'''
renderer = SnapFileRenderer()
utils = SnapUtils()
for template in templates:
target = templates[template]
target_file = target.format(**snap_env)
utils.ensure_dir(target_file, is_file=True)
LOG.debug('Rendering {} to {}'.format(template,
target_file))
with open(target_file, 'w') as tf:
tf.write(renderer.render(template, snap_env))
utils.chmod(target_file, file_mode)
utils.chown(target_file, user, group)
class OpenStackSnap(object):
'''Main executor class for snap-openstack'''
def __init__(self, config_file):
with open(config_file, 'r') as config:
self.configuration = yaml.load(config)
def setup(self):
'''Perform any pre-execution snap setup
Run this method prior to use of the execute method.
'''
utils = SnapUtils()
setup = self.configuration['setup']
LOG.debug(setup)
lock_file = "{snap_data}/snap-openstack".format(**utils.snap_env)
with lockutils.lock('setup.lock', external=True,
lock_path=lock_file):
if 'users' in setup.keys():
for user, groups in setup['users'].items():
home = os.path.join(
"{snap_common}".format(**utils.snap_env),
"lib", user
)
utils.add_user(user, groups, home)
default_owner = setup.get('default-owner', DEFAULT_OWNER)
default_user, default_group = default_owner.split(':')
default_dir_mode = setup.get('default-dir-mode',
DEFAULT_DIR_MODE)
default_file_mode = setup.get('default-file-mode',
DEFAULT_FILE_MODE)
for directory in setup.get('dirs', []):
dir_name = directory.format(**utils.snap_env)
utils.ensure_dir(dir_name, perms=default_dir_mode)
utils.rchmod(dir_name, default_dir_mode, default_file_mode)
utils.rchown(dir_name, default_user, default_group)
if 'copyfiles' in setup.keys():
for source, target in setup['copyfiles'].items():
source_dir = source.format(**utils.snap_env)
dest_dir = target.format(**utils.snap_env)
for source_name in os.listdir(source_dir):
s_file = os.path.join(source_dir, source_name)
d_file = os.path.join(dest_dir, source_name)
if not os.path.isfile(s_file):
continue
LOG.debug('Copying file {} to {}'.format(s_file,
d_file))
shutil.copy2(s_file, d_file)
utils.chmod(d_file, default_file_mode)
utils.chown(d_file, default_user, default_group)
_render_templates(setup.get('templates', []), utils.snap_env,
default_file_mode, default_user, default_group)
for target in setup.get('rchown', []):
target_path = target.format(**utils.snap_env)
user, group = setup['rchown'][target].split(':')
utils.rchown(target_path, user, group)
for target in setup.get('chmod', []):
target_path = target.format(**utils.snap_env)
if os.path.exists(target_path):
mode = setup['chmod'][target]
utils.chmod(target_path, mode)
else:
LOG.debug('Path not found: {}'.format(target_path))
for target in setup.get('chown', []):
target_path = target.format(**utils.snap_env)
if os.path.exists(target_path):
user, group = setup['chown'][target].split(':')
utils.chown(target_path, user, group)
else:
LOG.debug('Path not found: {}'.format(target_path))
def execute(self, argv):
'''Execute snap command building out configuration and log options'''
utils = SnapUtils()
setup = self.configuration['setup']
entry_point = self.configuration['entry_points'].get(argv[1])
if not entry_point:
_msg = 'Unable to find entry point for {}'.format(argv[1])
LOG.error(_msg)
raise ValueError(_msg)
other_args = argv[2:]
LOG.debug(entry_point)
# Build out command to run
cmd_type = entry_point.get('type', DEFAULT_EP_TYPE)
if cmd_type not in VALID_EP_TYPES:
_msg = 'Invalid entry point type: {}'.format(cmd_type)
LOG.error(_msg)
raise ValueError(_msg)
if cmd_type == DEFAULT_EP_TYPE:
cmd = [entry_point['binary'].format(**utils.snap_env)]
cfile_o = entry_point.get('config-file-override')
if cfile_o:
cfile_o = cfile_o.format(**utils.snap_env)
if os.path.exists(cfile_o):
cmd.append('--config-file={}'.format(cfile_o))
else:
cfile_o = None
if not cfile_o:
cfile = entry_point.get('config-file')
if cfile:
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
cmd.append('--config-file={}'.format(cfile))
else:
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
cdir = entry_point.get('config-dir')
if cdir:
cdir = cdir.format(**utils.snap_env)
if os.path.exists(cdir):
cmd.append('--config-dir={}'.format(cdir))
else:
LOG.debug('Configuration directory {} not found'
', skipping'.format(cdir))
log_file = entry_point.get('log-file')
if log_file:
log_file = log_file.format(**utils.snap_env)
cmd.append('--log-file={}'.format(log_file))
# Ensure any arguments passed to wrapper are propagated
cmd.extend(other_args)
elif cmd_type == UWSGI_EP_TYPE:
cmd = ["{snap}/bin/uwsgi".format(**utils.snap_env)]
defaults = [d.format(**utils.snap_env) for d in DEFAULT_UWSGI_ARGS]
cmd.extend(defaults)
pyargv = []
uwsgi_override = False
uwsgi_dir_o = entry_point.get('uwsgi-dir-override')
if uwsgi_dir_o:
uwsgi_dir_o = uwsgi_dir_o.format(**utils.snap_env)
for f in os.listdir(uwsgi_dir_o):
# Override is activated if a file exists in override dir
if os.path.isfile(os.path.join(uwsgi_dir_o, f)):
uwsgi_override = True
cmd.append(uwsgi_dir_o)
break
if not uwsgi_override:
uwsgi_dir = entry_point.get('uwsgi-dir')
if uwsgi_dir:
uwsgi_dir = uwsgi_dir.format(**utils.snap_env)
cmd.append(uwsgi_dir)
uwsgi_log = entry_point.get('uwsgi-log')
if uwsgi_log:
uwsgi_log = uwsgi_log.format(**utils.snap_env)
cmd.extend(['--logto', uwsgi_log])
cfile_o = entry_point.get('config-file-override')
if cfile_o:
cfile_o = cfile_o.format(**utils.snap_env)
if os.path.exists(cfile_o):
pyargv.append('--config-file={}'.format(cfile_o))
else:
cfile_o = None
if not cfile_o:
cfile = entry_point.get('config-file')
if cfile:
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
pyargv.append('--config-file={}'.format(cfile))
else:
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
cdir = entry_point.get('config-dir')
if cdir:
cdir = cdir.format(**utils.snap_env)
if os.path.exists(cdir):
pyargv.append('--config-dir={}'.format(cdir))
else:
LOG.debug('Configuration directory {} not found'
', skipping'.format(cdir))
log_file = entry_point.get('log-file')
if log_file:
log_file = log_file.format(**utils.snap_env)
pyargv.append('--log-file={}'.format(log_file))
# NOTE(jamespage): Pass dynamically built pyargv into
# context for template generation.
snap_env = utils.snap_env
if pyargv:
snap_env['pyargv'] = ' '.join(pyargv)
LOG.debug('Setting pyargv to: {}'.format(' '.join(pyargv)))
default_owner = setup.get('default-owner', DEFAULT_OWNER)
default_user, default_group = default_owner.split(':')
default_file_mode = setup.get('default-file-mode',
DEFAULT_FILE_MODE)
_render_templates(entry_point.get('templates', []), snap_env,
default_file_mode, default_user, default_group)
elif cmd_type == NGINX_EP_TYPE:
cmd = ["{snap}/usr/sbin/nginx".format(**utils.snap_env)]
cmd.extend(DEFAULT_NGINX_ARGS)
cfile_o = entry_point.get('config-file-override')
if cfile_o:
cfile_o = cfile_o.format(**utils.snap_env)
if os.path.exists(cfile_o):
cmd.extend(['-c', '{}'.format(cfile_o)])
else:
cfile_o = None
if not cfile_o:
cfile = entry_point.get('config-file')
if cfile:
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
cmd.extend(['-c', '{}'.format(cfile)])
else:
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
if 'run-as' in entry_point.keys():
user, groups = list(entry_point['run-as'].items())[0]
utils.drop_privileges(user, groups)
LOG.debug('Executing command {}'.format(' '.join(cmd)))
os.execvp(cmd[0], cmd)