Fall back to regexes in kolla_toolbox with Docker API 1.24

Since
70b515bf12
was merged, we implicitly require Docker API version 1.25
(https://docs.docker.com/engine/api/v1.25/) to support passing
environment variables to docker exec. The version of docker we deployed
before the Docker CE upgrade was 1.12.0, which is Docker API version
1.24, and so does not support this. We get the following error:

    Setting environment for exec is not supported in API < 1.25

This change modifies the kolla_toolbox module to use the new JSON
method for parsing Ansible's output when Docker API 1.25 is available,
falling back to the old regex-based method otherwise.

This change can be reverted when we require a minimum Docker API version
of 1.25+.

Change-Id: Ie671624ecca5b43d7bd8fbd959d701d9e21d66b3
Closes-Bug: #1845681
This commit is contained in:
Mark Goddard 2019-09-30 13:30:00 +01:00
parent 53a05b54ed
commit 8d25b306f5

View File

@ -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<host>\w+) \| (?P<status>\w+)!? =>(?P<stdout>.*)$',
re.MULTILINE | re.DOTALL)
NON_JSON_REG = re.compile(('^(?P<host>\w+) \| (?P<status>\w+)!? \| '
'rc=(?P<exit_code>\d+) >>\n(?P<stdout>.*)\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": {
# <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)
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": {
# <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)
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)