tobiko/tobiko/tripleo/_ansible.py

190 lines
6.4 KiB
Python

# Copyright 2020 Red Hat
#
# 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.
from __future__ import absolute_import
import functools
import io
import os
import re
import typing
from oslo_log import log
from packaging import version
import tobiko
from tobiko.shell import ansible
from tobiko.shell import sh
from tobiko.shell import ssh
from tobiko.tripleo import _overcloud
from tobiko.tripleo import _undercloud
from tobiko.tripleo import _config
LOG = log.getLogger(__name__)
def get_tripleo_ansible_inventory():
inventory_file = get_tripleo_ansible_inventory_file()
with io.open(inventory_file, 'rb') as fd:
return tobiko.load_yaml(fd)
def has_tripleo_ansible_inventory() -> bool:
inventory_file = get_tripleo_ansible_inventory_file()
return inventory_file is not None
skip_if_missing_tripleo_ansible_inventory = \
tobiko.skip_unless("Can't read TripleO Ansible inventory",
has_tripleo_ansible_inventory)
@functools.lru_cache()
def get_tripleo_ansible_inventory_file() -> typing.Optional[str]:
if _undercloud.has_undercloud():
inventory_file = _config.get_tripleo_config().inventory_file
if inventory_file:
inventory_file = tobiko.tobiko_config_path(inventory_file)
fetch_tripleo_inventary_file(inventory_file=inventory_file)
return inventory_file
return None
def skip_unless_undercloud_has_ansible(min_version: float = None):
reason = ("Ansible not found on undercloud host "
f"(min_version={min_version})")
return tobiko.skip_unless(reason=reason,
predicate=has_undercloud_ansible_version,
min_version=min_version)
def has_undercloud_ansible_version(min_version: float = None) -> bool:
ssh_client = _undercloud.undercloud_ssh_client()
try:
output = sh.execute('ansible --version',
ssh_client=ssh_client).stdout
except sh.ShellCommandFailed:
LOG.debug("Error getting ansible version", exc_info=1)
return False
if min_version is not None:
first_line = output.splitlines()[0]
# obtain the ansible version with a regex
# the expected output can have a format like these
# ansible 2.9.27
# ansible [core 2.12.2]
search_version = re.search(r'(\d+\.)?(\d+\.)?(\*|\d+)', first_line)
if not search_version:
return False
ansible_version_full = search_version.group(0)
version_pair = ansible_version_full.split('.', 2)[:2]
# compare versions rather than floats - example of a posible mistake:
# version 2.12 is higher than version 2.9
current_version = version.parse('.'.join(version_pair))
if current_version < version.parse(str(min_version)):
LOG.debug(f"Ansible version is < {min_version}:\n"
f"{first_line}\n")
return False
return True
READ_TRIPLEO_ANSIBLE_INVENTORY_SCRIPT = """
source {undercloud_rcfile} || exit 1
set -x
if [ "$OS_CLOUD" == "" ]; then
# on some releases tripleo-ansible-inventory command is supported
INVENTORY_FILE=$(mktemp tripleo-hosts-XXXXXXXXXX.yaml)
tripleo-ansible-inventory --ansible_ssh_user "{overcloud_ssh_username}" \\
--static-yaml-inventory "$INVENTORY_FILE"
RC=$?
if [ $RC == 0 ]; then
cat "$INVENTORY_FILE"
fi
rm -fR "$INVENTORY_FILE"
exit $RC
fi
# on latest releases, the updated inventory can always be found into the
# following path
TAI_DIR=~/overcloud-deploy/overcloud/config-download/overcloud
cat $TAI_DIR/tripleo-ansible-inventory.yaml
"""
def read_tripleo_ansible_inventory():
tripleo = _config.get_tripleo_config()
ssh_client = _undercloud.undercloud_ssh_client()
script = READ_TRIPLEO_ANSIBLE_INVENTORY_SCRIPT.format(
undercloud_rcfile=tripleo.undercloud_rcfile[0],
overcloud_ssh_username=_overcloud.get_overcloud_ssh_username())
return sh.execute('/bin/bash', stdin=script, ssh_client=ssh_client).stdout
def fetch_tripleo_inventary_file(inventory_file: str):
content = read_tripleo_ansible_inventory()
tobiko.makedirs(os.path.dirname(inventory_file))
with io.open(inventory_file, 'w') as fd:
fd.write(content)
class UndercloudAnsiblePlaybook(ansible.AnsiblePlaybook):
@property
def ssh_client(self) -> ssh.SSHClientType:
return _undercloud.undercloud_ssh_client()
def _ensure_inventory_files(self, *inventory_filenames: str) \
-> typing.List[str]:
inventory_file = get_tripleo_ansible_inventory_file()
if inventory_file is not None:
inventory_filenames += (inventory_file,)
return super()._ensure_inventory_files(*inventory_filenames)
def undercloud_ansible_playbook() -> UndercloudAnsiblePlaybook:
return tobiko.get_fixture(UndercloudAnsiblePlaybook)
def run_playbook_from_undercloud(
command: sh.ShellCommand = None,
playbook: str = None,
playbook_dirname: str = None,
playbook_filename: str = None,
inventory_filenames: typing.Iterable[str] = None,
playbook_files: typing.Iterable[str] = None,
requirements_files: typing.Iterable[str] = None,
roles: typing.Iterable[str] = None,
roles_path: typing.Iterable[str] = None,
verbosity: int = None):
return undercloud_ansible_playbook().run_playbook(
command=command,
playbook=playbook,
playbook_dirname=playbook_dirname,
playbook_filename=playbook_filename,
inventory_filenames=inventory_filenames,
playbook_files=playbook_files,
requirements_files=requirements_files,
roles=roles,
roles_path=roles_path,
verbosity=verbosity)
def setup_undercloud_ansible_playbook():
if _undercloud.has_undercloud():
ansible.register_ansible_playbook(
undercloud_ansible_playbook())