docker_* modules: improve tag validation and requests error handling (#58791)

* Add method to validate docker tags.

* Validate tag option of docker_image.

* Fix regex. Always return boolean, not None vs. Matcher object.

* Also catch requests errors.

* Linting.

* Add changelog.
This commit is contained in:
Felix Fontein 2019-07-13 22:36:18 +02:00 committed by GitHub
parent fa7c387f9b
commit 8d6f1846a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 150 additions and 23 deletions

View file

@ -0,0 +1,3 @@
bugfixes:
- "docker_image - validate ``tag`` option value."
- "docker_* modules - behave better when requests errors are not caught by docker-py."

View file

@ -74,6 +74,16 @@ except ImportError:
HAS_DOCKER_SSLADAPTER = False
try:
from request.exceptions import RequestException
except ImportError:
# Either docker-py is no longer using requests, or docker-py isn't around either,
# or docker-py's dependency requests is missing. In any case, define an exception
# class RequestException so that our code doesn't break.
class RequestException(Exception):
pass
DEFAULT_DOCKER_HOST = 'unix://var/run/docker.sock'
DEFAULT_TLS = False
DEFAULT_TLS_VERIFY = False
@ -123,12 +133,21 @@ if not HAS_DOCKER_PY:
def is_image_name_id(name):
"""Checks whether the given image name is in fact an image ID (hash)."""
"""Check whether the given image name is in fact an image ID (hash)."""
if re.match('^sha256:[0-9a-fA-F]{64}$', name):
return True
return False
def is_valid_tag(tag, allow_empty=False):
"""Check whether the given string is a valid docker tag name."""
if not tag:
return allow_empty
# See here ("Extended description") for a definition what tags can be:
# https://docs.docker.com/engine/reference/commandline/tag/
return bool(re.match('^[a-zA-Z0-9_][a-zA-Z0-9_.-]{0,127}$', tag))
def sanitize_result(data):
"""Sanitize data object for return to Ansible.

View file

@ -451,7 +451,7 @@ try:
import yaml
HAS_YAML = True
HAS_YAML_EXC = None
except ImportError as exc:
except ImportError as dummy:
HAS_YAML = False
HAS_YAML_EXC = traceback.format_exc()
@ -470,12 +470,16 @@ try:
HAS_COMPOSE = True
HAS_COMPOSE_EXC = None
MINIMUM_COMPOSE_VERSION = '1.7.0'
except ImportError as exc:
except ImportError as dummy:
HAS_COMPOSE = False
HAS_COMPOSE_EXC = traceback.format_exc()
DEFAULT_TIMEOUT = 10
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
RequestException,
)
AUTH_PARAM_MAPPING = {
@ -1101,6 +1105,8 @@ def main():
client.module.exit_json(**result)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -160,7 +160,12 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass, compare_generic
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
compare_generic,
RequestException,
)
from ansible.module_utils._text import to_native, to_bytes
@ -291,6 +296,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -955,6 +955,7 @@ from ansible.module_utils.docker.common import (
sanitize_result,
parse_healthcheck,
DOCKER_COMMON_ARGS,
RequestException,
)
from ansible.module_utils.six import string_types
@ -1419,7 +1420,7 @@ class TaskParameters(DockerBaseClass):
break
except NotFound as e:
self.client.fail(
"Cannot inspect the network '{0}' to determine the default IP.".format(net['name']),
"Cannot inspect the network '{0}' to determine the default IP: {1}".format(net['name'], e),
exception=traceback.format_exc()
)
return ip
@ -2651,9 +2652,9 @@ class ContainerManager(DockerBaseClass):
if not self.check_mode:
try:
if self.parameters.stop_timeout:
response = self.client.restart(container_id, timeout=self.parameters.stop_timeout)
dummy = self.client.restart(container_id, timeout=self.parameters.stop_timeout)
else:
response = self.client.restart(container_id)
dummy = self.client.restart(container_id)
except Exception as exc:
self.fail("Error restarting container %s: %s" % (container_id, str(exc)))
return self._get_container(container_id)
@ -3029,6 +3030,8 @@ def main():
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())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -115,7 +115,10 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def main():
@ -139,6 +142,8 @@ def main():
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -187,7 +187,11 @@ disk_usage:
import traceback
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
RequestException,
)
from ansible.module_utils._text import to_native
try:
@ -332,6 +336,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -424,7 +424,12 @@ import traceback
from distutils.version import LooseVersion
from ansible.module_utils.docker.common import (
docker_version, AnsibleDockerClient, DockerBaseClass, is_image_name_id,
docker_version,
AnsibleDockerClient,
DockerBaseClass,
is_image_name_id,
is_valid_tag,
RequestException,
)
from ansible.module_utils._text import to_native
@ -879,6 +884,9 @@ def main():
'and will be removed in Ansible 2.11. Please use the'
'"tls" and "tls_verify" options instead.')
if not is_valid_tag(client.module.params['tag'], allow_empty=True):
client.fail('"{0}" is not a valid docker tag!'.format(client.module.params['tag']))
build_options = dict(
container_limits='container_limits',
dockerfile='dockerfile',
@ -942,6 +950,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -163,7 +163,12 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass, is_image_name_id
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
is_image_name_id,
RequestException,
)
class ImageManager(DockerBaseClass):
@ -247,6 +252,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -138,7 +138,13 @@ except ImportError:
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
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DEFAULT_DOCKER_REGISTRY,
DockerBaseClass,
EMAIL_REGEX,
RequestException,
)
class LoginManager(DockerBaseClass):
@ -331,6 +337,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -286,6 +286,7 @@ from ansible.module_utils.docker.common import (
docker_version,
DifferenceTracker,
clean_dict_booleans_for_docker_api,
RequestException,
)
try:
@ -688,6 +689,8 @@ def main():
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -111,7 +111,10 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def main():
@ -135,6 +138,8 @@ def main():
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -139,6 +139,7 @@ except ImportError:
from ansible.module_utils.docker.common import (
DockerBaseClass,
RequestException,
)
from ansible.module_utils._text import to_native
@ -288,6 +289,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -89,10 +89,13 @@ nodes:
import traceback
from ansible.module_utils.docker.common import (
RequestException,
)
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
try:
from docker.errors import DockerException, APIError, NotFound
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
@ -147,6 +150,8 @@ def main():
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -187,7 +187,10 @@ except ImportError:
from distutils.version import LooseVersion
from ansible.module_utils.docker.common import AnsibleDockerClient
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
try:
from ansible.module_utils.docker.common import docker_version, clean_dict_booleans_for_docker_api
@ -255,6 +258,8 @@ def main():
client.module.exit_json(**result)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -160,7 +160,12 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient, DockerBaseClass, compare_generic
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
compare_generic,
RequestException,
)
from ansible.module_utils._text import to_native, to_bytes
@ -292,6 +297,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -278,7 +278,7 @@ except ImportError:
from ansible.module_utils.docker.common import (
DockerBaseClass,
DifferenceTracker,
LooseVersion,
RequestException,
)
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
@ -671,6 +671,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -195,7 +195,7 @@ tasks:
import traceback
try:
from docker.errors import DockerException, APIError, NotFound
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker_common
pass
@ -203,7 +203,11 @@ except ImportError:
from ansible.module_utils._text import to_native
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
from ansible.module_utils.docker.common import DockerBaseClass, clean_dict_booleans_for_docker_api
from ansible.module_utils.docker.common import (
DockerBaseClass,
clean_dict_booleans_for_docker_api,
RequestException,
)
class DockerSwarmManager(DockerBaseClass):
@ -373,6 +377,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -1051,9 +1051,10 @@ from ansible.module_utils.docker.common import (
DifferenceTracker,
DockerBaseClass,
convert_duration_to_nanosecond,
parse_healthcheck
parse_healthcheck,
RequestException,
)
from ansible.module_utils.basic import human_to_bytes
from ansible.module_utils.six import string_types
from ansible.module_utils._text import to_text
@ -2813,6 +2814,8 @@ def main():
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -74,6 +74,10 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import (
RequestException,
)
from ansible.module_utils.docker.swarm import AnsibleDockerSwarmClient
@ -109,6 +113,8 @@ def main():
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -137,6 +137,7 @@ from ansible.module_utils.docker.common import (
DockerBaseClass,
AnsibleDockerClient,
DifferenceTracker,
RequestException,
)
from ansible.module_utils.six import iteritems, text_type
@ -330,6 +331,8 @@ def main():
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':

View file

@ -88,7 +88,10 @@ except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils.docker.common import AnsibleDockerClient
from ansible.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def get_existing_volume(client, volume_name):
@ -122,6 +125,8 @@ def main():
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':