0ae9e8d489
Allows you to combine multiple environments by declaring any dependencies. Story: 2002009 Task: 42911 Change-Id: I4d9f96ec4cf3c6cd0d28dfe5ddb239d863498a72
230 lines
10 KiB
Python
230 lines
10 KiB
Python
# Copyright (c) 2017 StackHPC Ltd.
|
|
#
|
|
# 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 os.path
|
|
import subprocess
|
|
import sys
|
|
|
|
from kayobe import utils
|
|
from kayobe import vault
|
|
|
|
|
|
DEFAULT_CONFIG_PATH = "/etc/kolla"
|
|
|
|
CONFIG_PATH_ENV = "KOLLA_CONFIG_PATH"
|
|
|
|
DEFAULT_VENV_PATH = "venvs/kolla-ansible"
|
|
|
|
VENV_PATH_ENV = "KOLLA_VENV_PATH"
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
def add_args(parser):
|
|
"""Add arguments required for running Kolla Ansible to a parser."""
|
|
# $KOLLA_CONFIG_PATH or /etc/kolla.
|
|
default_config_path = os.getenv(CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH)
|
|
# $KOLLA_VENV_PATH or $PWD/venvs/kolla-ansible
|
|
default_venv = os.getenv(VENV_PATH_ENV,
|
|
os.path.join(os.getcwd(), DEFAULT_VENV_PATH))
|
|
parser.add_argument("--kolla-config-path", default=default_config_path,
|
|
help="path to Kolla configuration. "
|
|
"(default=$%s or %s)" %
|
|
(CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH))
|
|
parser.add_argument("-ke", "--kolla-extra-vars", metavar="EXTRA_VARS",
|
|
action="append",
|
|
help="set additional variables as key=value or "
|
|
"YAML/JSON for Kolla Ansible")
|
|
parser.add_argument("-ki", "--kolla-inventory", metavar="INVENTORY",
|
|
help="specify inventory host path "
|
|
"(default=$%s/inventory or %s/inventory) for "
|
|
"Kolla Ansible" %
|
|
(CONFIG_PATH_ENV, DEFAULT_CONFIG_PATH),
|
|
action='append')
|
|
parser.add_argument("-kl", "--kolla-limit", metavar="SUBSET",
|
|
help="further limit selected hosts to an additional "
|
|
"pattern")
|
|
parser.add_argument("-kp", "--kolla-playbook", metavar="PLAYBOOK",
|
|
help="path to Ansible playbook file")
|
|
parser.add_argument("--kolla-skip-tags", metavar="TAGS",
|
|
help="only run plays and tasks whose tags do not "
|
|
"match these values in Kolla Ansible")
|
|
parser.add_argument("-kt", "--kolla-tags", metavar="TAGS",
|
|
help="only run plays and tasks tagged with these "
|
|
"values in Kolla Ansible")
|
|
parser.add_argument("--kolla-venv", metavar="VENV", default=default_venv,
|
|
help="path to virtualenv where Kolla Ansible is "
|
|
"installed (default=$%s or $PWD/%s)" %
|
|
(VENV_PATH_ENV, DEFAULT_VENV_PATH))
|
|
|
|
|
|
def _get_inventory_paths(parsed_args, inventory_filename):
|
|
"""Return the path to the Kolla inventory."""
|
|
if parsed_args.kolla_inventory:
|
|
return parsed_args.kolla_inventory
|
|
else:
|
|
paths = [os.path.join(parsed_args.kolla_config_path, "inventory",
|
|
inventory_filename)]
|
|
# Inventory in the base layer is placed in the "kayobe"
|
|
# directory. This means that you can't have an environment
|
|
# called kayobe as it would conflict.
|
|
environments = ["kayobe"]
|
|
if parsed_args.environment:
|
|
environments.append(parsed_args.environment)
|
|
else:
|
|
environment_finder = utils.EnvironmentFinder(
|
|
parsed_args.config_path, parsed_args.environment)
|
|
for environment in environment_finder.ordered():
|
|
environments.append(environment)
|
|
for environment in environments:
|
|
candidate_path = os.path.join(
|
|
parsed_args.kolla_config_path, "extra-inventories",
|
|
environment)
|
|
if utils.is_readable_dir(candidate_path)["result"]:
|
|
paths.append(candidate_path)
|
|
return paths
|
|
|
|
|
|
def _validate_args(parsed_args, inventory_filename):
|
|
"""Validate Kayobe Ansible arguments."""
|
|
vault.enforce_single_password_source(parsed_args)
|
|
result = utils.is_readable_dir(parsed_args.kolla_config_path)
|
|
if not result["result"]:
|
|
LOG.error("Kolla configuration path %s is invalid: %s",
|
|
parsed_args.kolla_config_path, result["message"])
|
|
sys.exit(1)
|
|
|
|
inventories = _get_inventory_paths(parsed_args, inventory_filename)
|
|
for inventory in inventories:
|
|
result = utils.is_readable_dir(inventory)
|
|
if not result["result"]:
|
|
# NOTE(mgoddard): Previously the inventory was a file, now it is a
|
|
# directory to allow us to support inventory host_vars. Support
|
|
# both formats for now.
|
|
result_f = utils.is_readable_file(inventory)
|
|
if not result_f["result"]:
|
|
LOG.error("Kolla inventory %s is invalid: %s",
|
|
inventory, result["message"])
|
|
sys.exit(1)
|
|
|
|
result = utils.is_readable_dir(parsed_args.kolla_venv)
|
|
if not result["result"]:
|
|
LOG.error("Kolla virtualenv %s is invalid: %s",
|
|
parsed_args.kolla_venv, result["message"])
|
|
sys.exit(1)
|
|
|
|
if parsed_args.kolla_playbook:
|
|
result = utils.is_readable_file(parsed_args.kolla_playbook)
|
|
if not result["result"]:
|
|
LOG.error("Kolla Ansible playbook %s is invalid: %s",
|
|
parsed_args.kolla_playbook, result["message"])
|
|
sys.exit(1)
|
|
|
|
|
|
def build_args(parsed_args, command, inventory_filename, extra_vars=None,
|
|
tags=None, verbose_level=None, extra_args=None, limit=None):
|
|
"""Build arguments required for running Kolla Ansible."""
|
|
venv_activate = os.path.join(parsed_args.kolla_venv, "bin", "activate")
|
|
cmd = [".", venv_activate, "&&"]
|
|
cmd += ["kolla-ansible", command]
|
|
if verbose_level:
|
|
cmd += ["-" + "v" * verbose_level]
|
|
if parsed_args.kolla_playbook:
|
|
cmd += ["--playbook", parsed_args.kolla_playbook]
|
|
cmd += vault.build_args(parsed_args, "--key")
|
|
inventories = _get_inventory_paths(parsed_args, inventory_filename)
|
|
for inventory in inventories:
|
|
cmd += ["--inventory", inventory]
|
|
if parsed_args.kolla_config_path != DEFAULT_CONFIG_PATH:
|
|
cmd += ["--configdir", parsed_args.kolla_config_path]
|
|
cmd += ["--passwords",
|
|
os.path.join(parsed_args.kolla_config_path, "passwords.yml")]
|
|
if parsed_args.kolla_extra_vars:
|
|
for extra_var in parsed_args.kolla_extra_vars:
|
|
# Don't quote or escape variables passed via the kayobe -e CLI
|
|
# argument, to match Ansible's behaviour.
|
|
cmd += ["-e", extra_var]
|
|
if extra_vars:
|
|
for extra_var_name, extra_var_value in extra_vars.items():
|
|
# Quote and escape variables originating within the python CLI.
|
|
extra_var_value = utils.quote_and_escape(extra_var_value)
|
|
cmd += ["-e", "%s=%s" % (extra_var_name, extra_var_value)]
|
|
if parsed_args.kolla_limit or limit:
|
|
limit_arg = utils.intersect_limits(parsed_args.kolla_limit, limit)
|
|
cmd += ["--limit", utils.quote_and_escape(limit_arg)]
|
|
if parsed_args.kolla_skip_tags:
|
|
cmd += ["--skip-tags", parsed_args.kolla_skip_tags]
|
|
if parsed_args.kolla_tags or tags:
|
|
all_tags = [t for t in [parsed_args.kolla_tags, tags] if t]
|
|
cmd += ["--tags", ",".join(all_tags)]
|
|
if extra_args:
|
|
cmd += extra_args
|
|
return cmd
|
|
|
|
|
|
def _get_environment(parsed_args):
|
|
"""Return an environment dict for executing Kolla Ansible."""
|
|
env = os.environ.copy()
|
|
vault.update_environment(parsed_args, env)
|
|
# If a custom Ansible configuration file exists, use it. Allow
|
|
# etc/kayobe/kolla/ansible.cfg or etc/kayobe/ansible.cfg.
|
|
ansible_cfg_path = os.path.join(parsed_args.config_path, "kolla",
|
|
"ansible.cfg")
|
|
if utils.is_readable_file(ansible_cfg_path)["result"]:
|
|
env.setdefault("ANSIBLE_CONFIG", ansible_cfg_path)
|
|
else:
|
|
ansible_cfg_path = os.path.join(parsed_args.config_path, "ansible.cfg")
|
|
if utils.is_readable_file(ansible_cfg_path)["result"]:
|
|
env.setdefault("ANSIBLE_CONFIG", ansible_cfg_path)
|
|
# kolla-ansible allows passing additional arguments to ansible-playbook via
|
|
# EXTRA_OPTS.
|
|
if parsed_args.check or parsed_args.diff:
|
|
extra_opts = env.setdefault("EXTRA_OPTS", "")
|
|
if parsed_args.check and "--check" not in extra_opts:
|
|
env["EXTRA_OPTS"] += " --check"
|
|
if parsed_args.diff and "--diff" not in extra_opts:
|
|
env["EXTRA_OPTS"] += " --diff"
|
|
return env
|
|
|
|
|
|
def run(parsed_args, command, inventory_filename, extra_vars=None,
|
|
tags=None, quiet=False, verbose_level=None, extra_args=None,
|
|
limit=None):
|
|
"""Run a Kolla Ansible command."""
|
|
_validate_args(parsed_args, inventory_filename)
|
|
cmd = build_args(parsed_args, command,
|
|
inventory_filename=inventory_filename,
|
|
extra_vars=extra_vars, tags=tags,
|
|
verbose_level=verbose_level,
|
|
extra_args=extra_args,
|
|
limit=limit)
|
|
env = _get_environment(parsed_args)
|
|
try:
|
|
utils.run_command(" ".join(cmd), quiet=quiet, shell=True, env=env)
|
|
except subprocess.CalledProcessError as e:
|
|
LOG.error("kolla-ansible %s exited %d", command, e.returncode)
|
|
sys.exit(e.returncode)
|
|
|
|
|
|
def run_seed(*args, **kwargs):
|
|
"""Run a Kolla Ansible command using the seed inventory."""
|
|
return run(*args, inventory_filename="seed", **kwargs)
|
|
|
|
|
|
def run_overcloud(*args, **kwargs):
|
|
"""Run a Kolla Ansible command using the overcloud inventory."""
|
|
return run(*args, inventory_filename="overcloud", **kwargs)
|