Files
kolla-ansible/ansible/library/kolla_toolbox.py
Mark Goddard 70b515bf12 Catch errors and changes in kolla_toolbox module
The kolla_toolbox Ansible module executes as-hoc ansible commands in the
kolla_toolbox container, and parses the output to make it look as if
ansible-playbook executed the command. Currently however, this module
sometimes fails to catch failures of the underlying command, and also
sometimes shows tasks as 'ok' when the underlying command was changed.
This has been tested both before and after the upgrade to ansible 2.8.

This change fixes this issue by configuring ansible to emit output in
JSON format, to make parsing simpler. We can now pick up errors and
changes, and signal them to the caller.

This change also adds an ansible playbook, tests/test-kolla-toolbox.yml,
that can be executed to test the module. It's not currently integrated
with any CI jobs.

Note that this change cannot be backported as the JSON output callback
plugin was added in Ansible 2.5.

Change-Id: I8236dd4165f760c819ca972b75cbebc62015fada
Closes-Bug: #1844114
2019-09-16 14:34:11 +01:00

176 lines
5.2 KiB
Python

#!/usr/bin/env python
# Copyright 2016 99cloud Inc.
#
# 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 docker
import json
from ansible.module_utils.basic import AnsibleModule
DOCUMENTATION = '''
---
module: kolla_toolbox
short_description: >
Module for invoking ansible module in kolla_toolbox container.
description:
- A module targerting at invoking ansible module in kolla_toolbox
container as used by Kolla project.
options:
module_name:
description:
- The module name to invoke
required: True
type: str
module_args:
description:
- The module args use by the module
required: False
type: str or dict
module_extra_vars:
description:
- The extra variables used by the module
required: False
type: str or dict
api_version:
description:
- The version of the API for docker-py to use when contacting Docker
required: False
type: str
default: auto
timeout:
description:
- The default timeout for docker-py client when contacting Docker API
required: False
type: int
default: 180
author: Jeffrey Zhang
'''
EXAMPLES = '''
- hosts: controller
tasks:
- name: Ensure the direct absent
kolla_toolbox:
module_name: file
module_args: path=/tmp/a state=absent
- name: Create mysql database
kolla_toolbox:
module_name: mysql_db
module_args:
login_host: 192.168.1.10
login_user: root
login_password: admin
name: testdb
- name: Creating default user role
kolla_toolbox:
module_name: os_keystone_role
module_args:
name: _member_
auth: "{{ '{{ openstack_keystone_auth }}' }}"
module_extra_vars:
openstack_keystone_auth:
auth_url: http://127.0.0.1:5000
username: admin
password: password
project_name: "admin"
domain_name: "default"
'''
def gen_commandline(params):
command = ['ansible', 'localhost']
if params.get('module_name'):
command.extend(['-m', params.get('module_name')])
if params.get('module_args'):
module_args = params.get('module_args')
if isinstance(module_args, dict):
module_args = ' '.join("{}='{}'".format(key, value)
for key, value in module_args.items())
command.extend(['-a', module_args])
if params.get('module_extra_vars'):
extra_vars = params.get('module_extra_vars')
if isinstance(extra_vars, dict):
extra_vars = json.dumps(extra_vars)
command.extend(['--extra-vars', extra_vars])
return command
def get_docker_client():
return docker.APIClient
def main():
specs = dict(
module_name=dict(required=True, type='str'),
module_args=dict(type='str'),
module_extra_vars=dict(type='json'),
api_version=dict(required=False, type='str', default='auto'),
timeout=dict(required=False, type='int', default=180),
)
module = AnsibleModule(argument_spec=specs, bypass_checks=True)
client = get_docker_client()(
version=module.params.get('api_version'),
timeout=module.params.get('timeout'))
command_line = gen_commandline(module.params)
kolla_toolbox = client.containers(filters=dict(name='kolla_toolbox',
status='running'))
if not kolla_toolbox:
module.fail_json(msg='kolla_toolbox container is not running.')
kolla_toolbox = kolla_toolbox[0]
# Use the JSON output formatter, so that we can parse it.
environment = {"ANSIBLE_STDOUT_CALLBACK": "json",
"ANSIBLE_LOAD_CALLBACK_PLUGINS": "True"}
job = client.exec_create(kolla_toolbox, command_line,
environment=environment)
json_output = client.exec_start(job)
try:
output = json.loads(json_output)
except Exception as e:
module.fail_json(
msg='Can not parse the inner module output: %s' % json_output)
# Expected format is the following:
# {
# "plays": [
# {
# "tasks": [
# {
# "hosts": {
# "localhost": {
# <module result>
# }
# }
# }
# ]
# {
# ]
# }
try:
ret = output['plays'][0]['tasks'][0]['hosts']['localhost']
except (KeyError, IndexError) as e:
module.fail_json(
msg='Ansible JSON output has unexpected format: %s' % output)
# Remove Ansible's internal variables from returned fields.
ret.pop('_ansible_no_log', None)
module.exit_json(**ret)
if __name__ == "__main__":
main()