Rewrite the docker_container_exec module (#401)

* Rewrite docker_container_exec.

* Improve error messages.
This commit is contained in:
Felix Fontein 2022-07-06 21:45:44 +02:00 committed by GitHub
parent 9e57f29b3b
commit 1101997844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 67 deletions

View File

@ -0,0 +1,4 @@
major_changes:
- "docker_container_exec - no longer uses the Docker SDK for Python. It requires ``requests`` to be installed,
and depending on the features used has some more requirements. If the Docker SDK for Python is installed,
these requirements are likely met (https://github.com/ansible-collections/community.docker/pull/401)."

View File

@ -82,15 +82,13 @@ options:
version_added: 2.1.0 version_added: 2.1.0
extends_documentation_fragment: extends_documentation_fragment:
- community.docker.docker - community.docker.docker.api_documentation
- community.docker.docker.docker_py_1_documentation
notes: notes:
- Does not support C(check_mode). - Does not support C(check_mode).
author: author:
- "Felix Fontein (@felixfontein)" - "Felix Fontein (@felixfontein)"
requirements: requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0"
- "Docker API >= 1.25" - "Docker API >= 1.25"
''' '''
@ -154,7 +152,7 @@ from ansible.module_utils.common.text.converters import to_text, to_bytes, to_na
from ansible.module_utils.compat import selectors from ansible.module_utils.compat import selectors
from ansible.module_utils.six import string_types from ansible.module_utils.six import string_types
from ansible_collections.community.docker.plugins.module_utils.common import ( from ansible_collections.community.docker.plugins.module_utils.common_api import (
AnsibleDockerClient, AnsibleDockerClient,
RequestException, RequestException,
) )
@ -168,11 +166,12 @@ from ansible_collections.community.docker.plugins.module_utils.socket_handler im
DockerSocketHandlerModule, DockerSocketHandlerModule,
) )
try: from ansible_collections.community.docker.plugins.module_utils._api.errors import (
from docker.errors import DockerException, APIError, NotFound APIError,
except Exception: DockerException,
# missing Docker SDK for Python handled in ansible.module_utils.docker.common NotFound,
pass )
from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import format_environment
def main(): def main():
@ -191,8 +190,7 @@ def main():
) )
option_minimal_versions = dict( option_minimal_versions = dict(
chdir=dict(docker_py_version='3.0.0', docker_api_version='1.35'), chdir=dict(docker_api_version='1.35'),
env=dict(docker_py_version='2.3.0'),
) )
client = AnsibleDockerClient( client = AnsibleDockerClient(
@ -231,34 +229,31 @@ def main():
stdin += '\n' stdin += '\n'
try: try:
kwargs = {} data = {
if chdir is not None: 'Container': container,
kwargs['workdir'] = chdir 'User': user or '',
if env is not None: 'Privileged': False,
kwargs['environment'] = env 'Tty': False,
exec_data = client.exec_create( 'AttachStdin': bool(stdin),
container, 'AttachStdout': True,
argv, 'AttachStderr': True,
stdout=True, 'Cmd': argv,
stderr=True, 'Env': format_environment(env) if env is not None else None,
stdin=bool(stdin), }
user=user or '', exec_data = client.post_json_to_json('/containers/{0}/exec', container, data=data)
**kwargs
)
exec_id = exec_data['Id'] exec_id = exec_data['Id']
data = {
'Tty': tty,
'Detach': detach,
}
if detach: if detach:
client.exec_start(exec_id, tty=tty, detach=True) client.post_json_to_text('/exec/{0}/start', exec_id, data=data)
client.module.exit_json(changed=True, exec_id=exec_id) client.module.exit_json(changed=True, exec_id=exec_id)
else: else:
if stdin and not detach: if stdin and not detach:
exec_socket = client.exec_start( exec_socket = client.post_json_to_stream_socket('/exec/{0}/start', exec_id, data=data)
exec_id,
tty=tty,
detach=False,
socket=True,
)
try: try:
with DockerSocketHandlerModule(exec_socket, client.module, selectors) as exec_socket_handler: with DockerSocketHandlerModule(exec_socket, client.module, selectors) as exec_socket_handler:
if stdin: if stdin:
@ -268,16 +263,9 @@ def main():
finally: finally:
exec_socket.close() exec_socket.close()
else: else:
stdout, stderr = client.exec_start( stdout, stderr = client.post_json_to_stream('/exec/{0}/start', exec_id, data=data, stream=False, tty=tty, demux=True)
exec_id,
tty=tty,
detach=False,
stream=False,
socket=False,
demux=True,
)
result = client.exec_inspect(exec_id) result = client.get_json('/exec/{0}/json', exec_id)
stdout = to_text(stdout or b'') stdout = to_text(stdout or b'')
stderr = to_text(stderr or b'') stderr = to_text(stderr or b'')
@ -296,12 +284,12 @@ def main():
except APIError as e: except APIError as e:
if e.response and e.response.status_code == 409: if e.response and e.response.status_code == 409:
client.fail('The container "{0}" has been paused ({1})'.format(container, to_native(e))) client.fail('The container "{0}" has been paused ({1})'.format(container, to_native(e)))
client.fail('An unexpected docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) client.fail('An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc())
except DockerException as e: except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc()) client.fail('An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc())
except RequestException as e: except RequestException as e:
client.fail( client.fail(
'An unexpected requests error occurred when Docker SDK for Python tried to talk to the docker daemon: {0}'.format(to_native(e)), 'An unexpected requests error occurred when trying to talk to the Docker daemon: {0}'.format(to_native(e)),
exception=traceback.format_exc()) exception=traceback.format_exc())

View File

@ -190,28 +190,26 @@
- "'stderr' not in result" - "'stderr' not in result"
- result.exec_id is string - result.exec_id is string
- when: docker_py_version is version('2.3.0', '>=') - name: Execute in a present container (environment variable)
block: docker_container_exec:
- name: Execute in a present container (environment variable) container: "{{ cname }}"
docker_container_exec: argv:
container: "{{ cname }}" - /bin/sh
argv: - '-c'
- /bin/sh - 'echo "$FOO" ; echo $FOO > /dev/stderr'
- '-c' env:
- 'echo "$FOO" ; echo $FOO > /dev/stderr' FOO: |-
env: bar
FOO: |- baz
bar register: result
baz
register: result
- assert: - assert:
that: that:
- result.rc == 0 - result.rc == 0
- result.stdout == 'bar\nbaz' - result.stdout == 'bar\nbaz'
- result.stdout_lines == ['bar', 'baz'] - result.stdout_lines == ['bar', 'baz']
- result.stderr == 'bar baz' - result.stderr == 'bar baz'
- result.stderr_lines == ['bar baz'] - result.stderr_lines == ['bar baz']
always: always:
- name: Cleanup - name: Cleanup
@ -220,7 +218,7 @@
state: absent state: absent
force_kill: yes force_kill: yes
when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=') when: docker_api_version is version('1.25', '>=')
- fail: msg="Too old docker / docker-py version to run docker_container_exec tests!" - fail: msg="Too old docker / docker-py version to run docker_container_exec tests!"
when: not(docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6) when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)