docker_*: make modules more robust on Docker errors (#57913)

* Improve general error behavior if a Docker error is not caught.

* Don't die when network doesn't exist.

* Add changelog.

* Make API version always available. Also catch errors when retrieving version.

* Fix error message.

* Adjust fallback error messages.

(cherry picked from commit f8f2738351)
This commit is contained in:
Felix Fontein 2019-06-20 22:59:15 +02:00 committed by Toshio Kuratomi
parent 04e94274fb
commit 6a71aef6c5
22 changed files with 288 additions and 150 deletions

View file

@ -0,0 +1,2 @@
bugfixes:
- "docker_* modules - improve robustness when not handled Docker errors occur."

View file

@ -332,14 +332,14 @@ class AnsibleDockerClient(Client):
try:
super(AnsibleDockerClient, self).__init__(**self._connect_params)
self.docker_api_version_str = self.version()['ApiVersion']
except APIError as exc:
self.fail("Docker API error: %s" % exc)
except Exception as exc:
self.fail("Error connecting: %s" % exc)
self.docker_api_version = LooseVersion(self.docker_api_version_str)
if min_docker_api_version is not None:
self.docker_api_version_str = self.version()['ApiVersion']
self.docker_api_version = LooseVersion(self.docker_api_version_str)
if self.docker_api_version < LooseVersion(min_docker_api_version):
self.fail('Docker API version is %s. Minimum version required is %s.' % (self.docker_api_version_str, min_docker_api_version))

View file

@ -443,6 +443,7 @@ import os
import re
import sys
import tempfile
import traceback
from contextlib import contextmanager
from distutils.version import LooseVersion
@ -452,7 +453,13 @@ try:
HAS_YAML_EXC = None
except ImportError as exc:
HAS_YAML = False
HAS_YAML_EXC = str(exc)
HAS_YAML_EXC = traceback.format_exc()
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
try:
from compose import __version__ as compose_version
@ -463,10 +470,9 @@ try:
HAS_COMPOSE = True
HAS_COMPOSE_EXC = None
MINIMUM_COMPOSE_VERSION = '1.7.0'
except ImportError as exc:
HAS_COMPOSE = False
HAS_COMPOSE_EXC = str(exc)
HAS_COMPOSE_EXC = traceback.format_exc()
DEFAULT_TIMEOUT = 10
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass
@ -1090,8 +1096,11 @@ def main():
if client.module._name == 'docker_service':
client.module.deprecate("The 'docker_service' module has been renamed to 'docker_compose'.", version='2.12')
result = ContainerManager(client).exec_module()
client.module.exit_json(**result)
try:
result = ContainerManager(client).exec_module()
client.module.exit_json(**result)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -152,9 +152,10 @@ config_id:
import base64
import hashlib
import traceback
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -281,12 +282,15 @@ def main():
min_docker_api_version='1.30',
)
results = dict(
changed=False,
)
try:
results = dict(
changed=False,
)
ConfigManager(client, results)()
client.module.exit_json(**results)
ConfigManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -942,6 +942,7 @@ container:
import os
import re
import shlex
import traceback
from distutils.version import LooseVersion
from ansible.module_utils.common.text.formatters import human_to_bytes
@ -964,7 +965,7 @@ try:
from docker.types import Ulimit, LogConfig
else:
from docker.utils.types import Ulimit, LogConfig
from docker.errors import APIError, NotFound
from docker.errors import DockerException, APIError, NotFound
except Exception:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -1410,11 +1411,17 @@ class TaskParameters(DockerBaseClass):
return ip
for net in self.networks:
if net.get('name'):
network = self.client.inspect_network(net['name'])
if network.get('Driver') == 'bridge' and \
network.get('Options', {}).get('com.docker.network.bridge.host_binding_ipv4'):
ip = network['Options']['com.docker.network.bridge.host_binding_ipv4']
break
try:
network = self.client.inspect_network(net['name'])
if network.get('Driver') == 'bridge' and \
network.get('Options', {}).get('com.docker.network.bridge.host_binding_ipv4'):
ip = network['Options']['com.docker.network.bridge.host_binding_ipv4']
break
except NotFound as e:
self.client.fail(
"Cannot inspect the network '{0}' to determine the default IP.".format(net['name']),
exception=traceback.format_exc()
)
return ip
def _parse_publish_ports(self):
@ -3017,8 +3024,11 @@ def main():
version='2.12'
)
cm = ContainerManager(client)
client.module.exit_json(**sanitize_result(cm.results))
try:
cm = ContainerManager(client)
client.module.exit_json(**sanitize_result(cm.results))
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -107,6 +107,14 @@ container:
}'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient
@ -121,13 +129,16 @@ def main():
min_docker_api_version='1.20',
)
container = client.get_container(client.module.params['name'])
try:
container = client.get_container(client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if container else False),
container=container,
)
client.module.exit_json(
changed=False,
exists=(True if container else False),
container=container,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -185,11 +185,13 @@ disk_usage:
'''
import traceback
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass
from ansible.module_utils._text import to_native
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# Missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -321,12 +323,15 @@ def main():
)
client.fail_results['can_talk_to_docker'] = True
results = dict(
changed=False,
)
try:
results = dict(
changed=False,
)
DockerHostManager(client, results)
client.module.exit_json(**results)
DockerHostManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -410,8 +410,10 @@ image:
type: dict
sample: {}
'''
import os
import re
import traceback
from distutils.version import LooseVersion
@ -427,6 +429,7 @@ if docker_version is not None:
else:
from docker.auth.auth import resolve_repository_name
from docker.utils.utils import parse_repository_tag
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in module_utils.docker.common
pass
@ -914,14 +917,17 @@ def main():
'use the "force_source", "force_absent" or "force_tag" option '
'instead, depending on what you want to force.')
results = dict(
changed=False,
actions=[],
image={}
)
try:
results = dict(
changed=False,
actions=[],
image={}
)
ImageManager(client, results)
client.module.exit_json(**results)
ImageManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -154,8 +154,11 @@ images:
]
'''
import traceback
try:
from docker import utils
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -234,13 +237,16 @@ def main():
if client.module._name == 'docker_image_facts':
client.module.deprecate("The 'docker_image_facts' module has been renamed to 'docker_image_info'", version='2.12')
results = dict(
changed=False,
images=[]
)
try:
results = dict(
changed=False,
images=[]
)
ImageManager(client, results)
client.module.exit_json(**results)
ImageManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -129,6 +129,13 @@ import base64
import json
import os
import re
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils._text import to_bytes, to_text
from ansible.module_utils.docker.common import AnsibleDockerClient, DEFAULT_DOCKER_REGISTRY, DockerBaseClass, EMAIL_REGEX
@ -311,16 +318,19 @@ def main():
min_docker_api_version='1.20',
)
results = dict(
changed=False,
actions=[],
login_result={}
)
try:
results = dict(
changed=False,
actions=[],
login_result={}
)
LoginManager(client, results)
if 'actions' in results:
del results['actions']
client.module.exit_json(**results)
LoginManager(client, results)
if 'actions' in results:
del results['actions']
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -276,6 +276,7 @@ network:
'''
import re
import traceback
from distutils.version import LooseVersion
@ -289,6 +290,7 @@ from ansible.module_utils.docker.common import (
try:
from docker import utils
from docker.errors import DockerException
if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
from docker.types import IPAMPool, IPAMConfig
except Exception:
@ -681,8 +683,11 @@ def main():
option_minimal_versions=option_minimal_versions,
)
cm = DockerNetworkManager(client)
client.module.exit_json(**cm.results)
try:
cm = DockerNetworkManager(client)
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -103,6 +103,14 @@ network:
}'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient
@ -117,13 +125,16 @@ def main():
min_docker_api_version='1.21',
)
network = client.get_network(client.module.params['name'])
try:
network = client.get_network(client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if network else False),
network=network,
)
client.module.exit_json(
changed=False,
exists=(True if network else False),
network=network,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -129,8 +129,10 @@ node:
'''
import traceback
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -277,12 +279,15 @@ def main():
min_docker_api_version='1.25',
)
results = dict(
changed=False,
)
try:
results = dict(
changed=False,
)
SwarmNodeManager(client, results)
client.module.exit_json(**results)
SwarmNodeManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -87,10 +87,12 @@ nodes:
type: list
'''
import traceback
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
try:
from docker.errors import APIError, NotFound
from docker.errors import DockerException, APIError, NotFound
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -136,12 +138,15 @@ def main():
client.fail_task_if_not_swarm_manager()
nodes = get_node_facts(client)
try:
nodes = get_node_facts(client)
client.module.exit_json(
changed=False,
nodes=nodes,
)
client.module.exit_json(
changed=False,
nodes=nodes,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -177,6 +177,14 @@ builder_cache_space_reclaimed:
sample: '0'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from distutils.version import LooseVersion
from ansible.module_utils.docker.common import AnsibleDockerClient
@ -214,36 +222,39 @@ def main():
msg = "Error: Docker SDK for Python's version is %s. Minimum version required for builds option is %s. Use `pip install --upgrade docker` to upgrade."
client.fail(msg % (docker_version, cache_min_version))
result = dict()
try:
result = dict()
if client.module.params['containers']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('containers_filters'))
res = client.prune_containers(filters=filters)
result['containers'] = res.get('ContainersDeleted') or []
result['containers_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['containers']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('containers_filters'))
res = client.prune_containers(filters=filters)
result['containers'] = res.get('ContainersDeleted') or []
result['containers_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['images']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('images_filters'))
res = client.prune_images(filters=filters)
result['images'] = res.get('ImagesDeleted') or []
result['images_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['images']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('images_filters'))
res = client.prune_images(filters=filters)
result['images'] = res.get('ImagesDeleted') or []
result['images_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['networks']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('networks_filters'))
res = client.prune_networks(filters=filters)
result['networks'] = res.get('NetworksDeleted') or []
if client.module.params['networks']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('networks_filters'))
res = client.prune_networks(filters=filters)
result['networks'] = res.get('NetworksDeleted') or []
if client.module.params['volumes']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('volumes_filters'))
res = client.prune_volumes(filters=filters)
result['volumes'] = res.get('VolumesDeleted') or []
result['volumes_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['volumes']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('volumes_filters'))
res = client.prune_volumes(filters=filters)
result['volumes'] = res.get('VolumesDeleted') or []
result['volumes_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['builder_cache']:
res = client.prune_builds()
result['builder_cache_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['builder_cache']:
res = client.prune_builds()
result['builder_cache_space_reclaimed'] = res['SpaceReclaimed']
client.module.exit_json(**result)
client.module.exit_json(**result)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -152,9 +152,10 @@ secret_id:
import base64
import hashlib
import traceback
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -281,13 +282,16 @@ def main():
min_docker_api_version='1.25',
)
results = dict(
changed=False,
secret_id=''
)
try:
results = dict(
changed=False,
secret_id=''
)
SecretManager(client, results)()
client.module.exit_json(**results)
SecretManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -267,9 +267,10 @@ actions:
'''
import json
import traceback
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -659,14 +660,17 @@ def main():
option_minimal_versions=option_minimal_versions,
)
results = dict(
changed=False,
result='',
actions=[]
)
try:
results = dict(
changed=False,
result='',
actions=[]
)
SwarmManager(client, results)()
client.module.exit_json(**results)
SwarmManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -192,8 +192,10 @@ tasks:
'''
import traceback
try:
from docker.errors import APIError, NotFound
from docker.errors import DockerException, APIError, NotFound
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker_common
pass
@ -361,13 +363,16 @@ def main():
client.fail_results['docker_swarm_active'] = client.check_if_swarm_node()
client.fail_results['docker_swarm_manager'] = client.check_if_swarm_manager()
results = dict(
changed=False,
)
try:
results = dict(
changed=False,
)
DockerSwarmManager(client, results)
results.update(client.fail_results)
client.module.exit_json(**results)
DockerSwarmManager(client, results)
results.update(client.fail_results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -1046,6 +1046,7 @@ EXAMPLES = '''
import shlex
import time
import operator
import traceback
from distutils.version import LooseVersion
@ -2740,21 +2741,24 @@ def main():
option_minimal_versions=option_minimal_versions,
)
dsm = DockerServiceManager(client)
msg, changed, rebuilt, changes, facts = dsm.run_safe()
try:
dsm = DockerServiceManager(client)
msg, changed, rebuilt, changes, facts = dsm.run_safe()
results = dict(
msg=msg,
changed=changed,
rebuilt=rebuilt,
changes=changes,
swarm_service=facts,
)
if client.module._diff:
before, after = dsm.diff_tracker.get_before_after()
results['diff'] = dict(before=before, after=after)
results = dict(
msg=msg,
changed=changed,
rebuilt=rebuilt,
changes=changes,
swarm_service=facts,
)
if client.module._diff:
before, after = dsm.diff_tracker.get_before_after()
results['diff'] = dict(before=before, after=after)
client.module.exit_json(**results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -66,6 +66,14 @@ service:
type: dict
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
@ -91,13 +99,16 @@ def main():
client.fail_task_if_not_swarm_manager()
service = get_service_info(client)
try:
service = get_service_info(client)
client.module.exit_json(
changed=False,
service=service,
exists=bool(service)
)
client.module.exit_json(
changed=False,
service=service,
exists=bool(service)
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -125,8 +125,10 @@ volume:
sample: {}
'''
import traceback
try:
from docker.errors import APIError
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -323,8 +325,11 @@ def main():
option_minimal_versions=option_minimal_versions,
)
cm = DockerVolumeManager(client)
client.module.exit_json(**cm.results)
try:
cm = DockerVolumeManager(client)
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -80,8 +80,10 @@ volume:
}'
'''
import traceback
try:
from docker.errors import NotFound
from docker.errors import DockerException, NotFound
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -110,13 +112,16 @@ def main():
min_docker_api_version='1.21',
)
volume = get_existing_volume(client, client.module.params['name'])
try:
volume = get_existing_volume(client, client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if volume else False),
volume=volume,
)
client.module.exit_json(
changed=False,
exists=(True if volume else False),
volume=volume,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':