diff --git a/ansible/library/kolla_toolbox.py b/ansible/library/kolla_toolbox.py index 8873ea2156..4d9dd1c02f 100644 --- a/ansible/library/kolla_toolbox.py +++ b/ansible/library/kolla_toolbox.py @@ -14,8 +14,10 @@ # See the License for the specific language governing permissions and # limitations under the License. +from distutils.version import StrictVersion import docker import json +import re from ansible.module_utils.basic import AnsibleModule @@ -89,6 +91,13 @@ EXAMPLES = ''' ''' +JSON_REG = re.compile('^(?P\w+) \| (?P\w+)!? =>(?P.*)$', + re.MULTILINE | re.DOTALL) +NON_JSON_REG = re.compile(('^(?P\w+) \| (?P\w+)!? \| ' + 'rc=(?P\d+) >>\n(?P.*)\n$'), + re.MULTILINE | re.DOTALL) + + def gen_commandline(params): command = ['ansible', 'localhost'] if params.get('module_name'): @@ -111,6 +120,11 @@ def get_docker_client(): return docker.APIClient +def docker_supports_environment_in_exec(client): + docker_version = StrictVersion(client.api_version) + return docker_version >= StrictVersion('1.25') + + def main(): specs = dict( module_name=dict(required=True, type='str'), @@ -130,43 +144,79 @@ def main(): 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) + # NOTE(mgoddard): Docker 1.12 has API version 1.24, and was installed by + # kolla-ansible bootstrap-servers on Rocky and earlier releases. This API + # version does not have support for specifying environment variables for + # exec jobs, which is necessary to use the Ansible JSON output formatter. + # While we continue to support this version of Docker, fall back to the old + # regex-based method for API version 1.24 and earlier. + # TODO(mgoddard): Remove this conditional (keep the if) when we require + # Docker API version 1.25+. + if docker_supports_environment_in_exec(client): + # 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) - # Expected format is the following: - # { - # "plays": [ - # { - # "tasks": [ - # { - # "hosts": { - # "localhost": { - # - # } - # } - # } - # ] - # { - # ] - # } - 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) + try: + output = json.loads(json_output) + except Exception as e: + module.fail_json( + msg='Can not parse the inner module output: %s' % json_output) - # Remove Ansible's internal variables from returned fields. - ret.pop('_ansible_no_log', None) + # Expected format is the following: + # { + # "plays": [ + # { + # "tasks": [ + # { + # "hosts": { + # "localhost": { + # + # } + # } + # } + # ] + # { + # ] + # } + 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) + else: + job = client.exec_create(kolla_toolbox, command_line) + output = client.exec_start(job) + + for exp in [JSON_REG, NON_JSON_REG]: + m = exp.match(output) + if m: + inner_output = m.groupdict().get('stdout') + status = m.groupdict().get('status') + break + else: + module.fail_json( + msg='Can not parse the inner module output: %s' % output) + + ret = dict() + try: + ret = json.loads(inner_output) + except ValueError: + # Some modules (e.g. command) do not produce a JSON output. + # Instead, check the status, and assume changed on success. + ret['stdout'] = inner_output + if status != "SUCCESS": + ret['failed'] = True + else: + # No way to know whether changed - assume yes. + ret['changed'] = True module.exit_json(**ret)