Pete Vander Giessen 322514211b snap-config-keys is now a map
It maps the name of the config values that we use in our templates to
the name of the key in the snap config. This allows us to stick a
bunch of stuff in the questions namespace in the snap config, and to
use dashes, with minimal changes to our templates.

Drop Python 2 support, to fix tests.

Change-Id: I48b86b5e557e30f81e9cc415e7fa3a9133aa9f39
2019-10-07 12:58:12 -04:00

336 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 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_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)
def _get_os_config_files(entry_point, key_name):
'''Get OpenStack config files from dictionary and convert to CLI format
If a config file path doesn't exist on disk, it won't be added to the
options array.
@entry_point: entry_point dictionary
@key_name: key name (either config-files or config-files-override)
@return options: array of CLI '--config-file' options
'''
utils = SnapUtils()
options = []
for cfile in entry_point.get(key_name, []):
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
options.append('--config-file={}'.format(cfile))
else:
LOG.debug('Configuration file {} not found'
', skipping'.format(cfile))
return options
def _get_os_config_dirs(entry_point):
'''Get OpenStack config dirs from dictionary and convert to CLI format
If a config dir path doesn't exist on disk, it won't be added to the
options array.
@entry_point: entry_point dictionary
@return options: array of CLI '--config-dir' options
'''
utils = SnapUtils()
options = []
for cdir in entry_point.get('config-dirs', []):
cdir = cdir.format(**utils.snap_env)
if os.path.exists(cdir):
options.append('--config-dir={}'.format(cdir))
else:
LOG.debug('Configuration directory {} not found'
', skipping'.format(cdir))
return options
def _get_os_log_file(entry_point):
'''Get OpenStack log file from dictionary and convert to CLI format
@entry_point: entry_point dictionary
@return options: string containing CLI '--log-file' option
'''
utils = SnapUtils()
option = None
log_file = entry_point.get('log-file', [])
if log_file:
log_file = log_file.format(**utils.snap_env)
option = '--log-file={}'.format(log_file)
return option
def _build_environment():
'''Prepare any snap specific environment additions
This function will automatically add REQUEST_CA_BUNDLE
if $SNAP_COMMON/etc/ssl/certs/ca-certificates.crt is detected.
'''
utils = SnapUtils()
env = os.environ.copy()
ca_certs = (
'{snap_common}/etc/ssl/certs/ca-certificates.crt'.format(
**utils.snap_env)
)
if os.path.exists(ca_certs):
env['REQUESTS_CA_BUNDLE'] = ca_certs
return env
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 _patch_env(self, utils, setup):
'''Add values from snap config to snap_env.
Note that if there are duplicate keys in the snap config and
snap environment, we'll clobber the keys in the environment.
'''
snap_config = utils.snap_config(
keys=setup.get('snap-config-keys', {}))
for key in snap_config.keys():
utils.snap_env[key] = snap_config[key]
def setup(self):
'''Pre-launch setup.
Write out templates that might be shared between daemons and
perform other setup tasks.
This is generally executed during a seperate invocation of the
script, before running "snap-openstack launch foo"
'''
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):
for directory in setup.get('dirs', []):
dir_name = directory.format(**utils.snap_env)
utils.ensure_dir(dir_name, perms=DEFAULT_DIR_MODE)
self._patch_env(utils, setup)
_render_templates(setup.get('templates', []), utils.snap_env,
DEFAULT_FILE_MODE, 'root', 'root')
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 launch(self, argv):
'''Launch a daemon, building out configuration and log options'''
utils = SnapUtils()
entry_point = self.configuration['entry_points'].get(argv[2])
if not entry_point:
_msg = 'Unable to find entry point for {}'.format(argv[2])
LOG.error(_msg)
raise ValueError(_msg)
other_args = argv[3:]
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)
self._patch_env(utils, setup=self.configuration['setup'])
if cmd_type == DEFAULT_EP_TYPE:
cmd = [entry_point['binary'].format(**utils.snap_env)]
cfiles = _get_os_config_files(entry_point, 'config-files-override')
for cfile in _get_os_config_files(entry_point, 'config-files'):
if os.path.basename(cfile) not in ' '.join(cfiles):
cfiles.append(cfile)
if cfiles:
cmd.extend(cfiles)
cdirs = _get_os_config_dirs(entry_point)
if cdirs:
cmd.extend(cdirs)
log_file = _get_os_log_file(entry_point)
if log_file:
cmd.append(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])
cfiles = _get_os_config_files(entry_point, 'config-files-override')
for cfile in _get_os_config_files(entry_point, 'config-files'):
if os.path.basename(cfile) not in ' '.join(cfiles):
cfiles.append(cfile)
if cfiles:
pyargv.extend(cfiles)
cdirs = _get_os_config_dirs(entry_point)
if cdirs:
pyargv.extend(cdirs)
log_file = _get_os_log_file(entry_point)
if log_file:
pyargv.append(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)))
_render_templates(entry_point.get('templates', []), snap_env,
DEFAULT_FILE_MODE, 'root', 'root')
elif cmd_type == NGINX_EP_TYPE:
cmd = ["{snap}/usr/sbin/nginx".format(**utils.snap_env)]
cmd.extend(DEFAULT_NGINX_ARGS)
cfile = entry_point.get('config-file-override')
if cfile:
cfile = cfile.format(**utils.snap_env)
if os.path.exists(cfile):
cmd.extend(['-c', '{}'.format(cfile)])
else:
cfile = None
if not cfile:
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))
LOG.debug('Executing command {}'.format(' '.join(cmd)))
os.execvpe(cmd[0], cmd, _build_environment())