docker_container: simplify minimal required version per option handling (#47711)

* Store parsed docker-py / docker API versions in client.

* Began refactoring 'minimal required version' for docker_container options.

* Removing some fake defaults.

* Added changelog.

* Improve tests (check older docker versions).

* Fix comparison. The breaking point is not docker-py 2.0.0, but 1.10.0.

(Verified by testing with these versions.)

* Move docker-py/API version detection to setup_docker.

* Add YAML document starter.

* docker_network requirement for docker-py was bumped to 1.10.0 in #47492.
pull/48087/head
Felix Fontein 6 years ago committed by Will Thames
parent 788247583b
commit 3cca4185be

@ -0,0 +1,2 @@
bugfixes:
- "docker_container - refactored minimal docker-py/API version handling, and fixing such handling of some options."

@ -188,6 +188,8 @@ class AnsibleDockerClient(Client):
NEEDS_DOCKER_PY2 = (LooseVersion(min_docker_version) >= LooseVersion('2.0.0')) NEEDS_DOCKER_PY2 = (LooseVersion(min_docker_version) >= LooseVersion('2.0.0'))
self.docker_py_version = LooseVersion(docker_version)
if HAS_DOCKER_MODELS and HAS_DOCKER_SSLADAPTER: if HAS_DOCKER_MODELS and HAS_DOCKER_SSLADAPTER:
self.fail("Cannot have both the docker-py and docker python modules installed together as they use the same namespace and " self.fail("Cannot have both the docker-py and docker python modules installed together as they use the same namespace and "
"cause a corrupt installation. Please uninstall both packages, and re-install only the docker-py or docker python " "cause a corrupt installation. Please uninstall both packages, and re-install only the docker-py or docker python "
@ -201,7 +203,7 @@ class AnsibleDockerClient(Client):
msg = "Failed to import docker or docker-py - %s. Try `pip install docker` or `pip install docker-py` (Python 2.6)" msg = "Failed to import docker or docker-py - %s. Try `pip install docker` or `pip install docker-py` (Python 2.6)"
self.fail(msg % HAS_DOCKER_ERROR) self.fail(msg % HAS_DOCKER_ERROR)
if LooseVersion(docker_version) < LooseVersion(min_docker_version): if self.docker_py_version < LooseVersion(min_docker_version):
if NEEDS_DOCKER_PY2: if NEEDS_DOCKER_PY2:
if docker_version < LooseVersion('2.0'): if docker_version < LooseVersion('2.0'):
msg = "Error: docker-py version is %s, while this module requires docker %s. Try `pip uninstall docker-py` and then `pip install docker`" msg = "Error: docker-py version is %s, while this module requires docker %s. Try `pip uninstall docker-py` and then `pip install docker`"
@ -226,9 +228,10 @@ class AnsibleDockerClient(Client):
self.fail("Error connecting: %s" % exc) self.fail("Error connecting: %s" % exc)
if min_docker_api_version is not None: if min_docker_api_version is not None:
docker_api_version = self.version()['ApiVersion'] self.docker_api_version_str = self.version()['ApiVersion']
if LooseVersion(docker_api_version) < LooseVersion(min_docker_api_version): self.docker_api_version = LooseVersion(self.docker_api_version_str)
self.fail('docker API version is %s. Minimum version required is %s.' % (docker_api_version, min_docker_api_version)) 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))
def log(self, msg, pretty_print=False): def log(self, msg, pretty_print=False):
pass pass

@ -76,11 +76,9 @@ options:
cpu_period: cpu_period:
description: description:
- Limit CPU CFS (Completely Fair Scheduler) period - Limit CPU CFS (Completely Fair Scheduler) period
default: 0
cpu_quota: cpu_quota:
description: description:
- Limit CPU CFS (Completely Fair Scheduler) quota - Limit CPU CFS (Completely Fair Scheduler) quota
default: 0
cpuset_cpus: cpuset_cpus:
description: description:
- CPUs in which to allow execution C(1,3) or C(1-3). - CPUs in which to allow execution C(1,3) or C(1-3).
@ -243,7 +241,6 @@ options:
Unit can be C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte), Unit can be C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
C(T) (tebibyte), or C(P) (pebibyte). Minimum is C(4M)." C(T) (tebibyte), or C(P) (pebibyte). Minimum is C(4M)."
- Omitting the unit defaults to bytes. - Omitting the unit defaults to bytes.
default: 0
labels: labels:
description: description:
- Dictionary of key value pairs. - Dictionary of key value pairs.
@ -278,14 +275,12 @@ options:
Unit can be C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte), Unit can be C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
C(T) (tebibyte), or C(P) (pebibyte)." C(T) (tebibyte), or C(P) (pebibyte)."
- Omitting the unit defaults to bytes. - Omitting the unit defaults to bytes.
default: 0
memory_swap: memory_swap:
description: description:
- "Total memory limit (memory + swap, format: C(<number>[<unit>])). - "Total memory limit (memory + swap, format: C(<number>[<unit>])).
Number is a positive integer. Unit can be C(B) (byte), C(K) (kibibyte, 1024B), Number is a positive integer. Unit can be C(B) (byte), C(K) (kibibyte, 1024B),
C(M) (mebibyte), C(G) (gibibyte), C(T) (tebibyte), or C(P) (pebibyte)." C(M) (mebibyte), C(G) (gibibyte), C(T) (tebibyte), or C(P) (pebibyte)."
- Omitting the unit defaults to bytes. - Omitting the unit defaults to bytes.
default: 0
memory_swappiness: memory_swappiness:
description: description:
- Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
@ -318,11 +313,9 @@ options:
description: description:
- Whether or not to disable OOM Killer for the container. - Whether or not to disable OOM Killer for the container.
type: bool type: bool
default: 'no'
oom_score_adj: oom_score_adj:
description: description:
- An integer value containing the score given to the container in order to tune OOM killer preferences. - An integer value containing the score given to the container in order to tune OOM killer preferences.
default: 0
version_added: "2.2" version_added: "2.2"
output_logs: output_logs:
description: description:
@ -405,7 +398,6 @@ options:
restart_retries: restart_retries:
description: description:
- Use with restart policy to control maximum number of restart attempts. - Use with restart policy to control maximum number of restart attempts.
default: 0
runtime: runtime:
description: description:
- Runtime to use for the container. - Runtime to use for the container.
@ -797,7 +789,7 @@ from distutils.version import LooseVersion
from ansible.module_utils.basic import human_to_bytes from ansible.module_utils.basic import human_to_bytes
from ansible.module_utils.docker_common import ( from ansible.module_utils.docker_common import (
HAS_DOCKER_PY_2, HAS_DOCKER_PY_3, AnsibleDockerClient, AnsibleDockerClient,
DockerBaseClass, sanitize_result, is_image_name_id, DockerBaseClass, sanitize_result, is_image_name_id,
compare_generic, compare_generic,
) )
@ -805,11 +797,11 @@ from ansible.module_utils.six import string_types
try: try:
from docker import utils from docker import utils
if HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3: from ansible.module_utils.docker_common import docker_version
if LooseVersion(docker_version) >= LooseVersion('1.10.0'):
from docker.types import Ulimit, LogConfig from docker.types import Ulimit, LogConfig
else: else:
from docker.utils.types import Ulimit, LogConfig from docker.utils.types import Ulimit, LogConfig
from ansible.module_utils.docker_common import docker_version
except Exception as dummy: except Exception as dummy:
# missing docker-py handled in ansible.module_utils.docker # missing docker-py handled in ansible.module_utils.docker
pass pass
@ -1058,28 +1050,23 @@ class TaskParameters(DockerBaseClass):
''' '''
update_parameters = dict( update_parameters = dict(
blkio_weight='blkio_weight',
cpu_period='cpu_period', cpu_period='cpu_period',
cpu_quota='cpu_quota', cpu_quota='cpu_quota',
cpu_shares='cpu_shares', cpu_shares='cpu_shares',
cpuset_cpus='cpuset_cpus', cpuset_cpus='cpuset_cpus',
cpuset_mems='cpuset_mems',
mem_limit='memory', mem_limit='memory',
mem_reservation='memory_reservation', mem_reservation='memory_reservation',
memswap_limit='memory_swap', memswap_limit='memory_swap',
kernel_memory='kernel_memory', kernel_memory='kernel_memory',
) )
if self.client.HAS_BLKIO_WEIGHT_OPT:
# blkio_weight is only supported in docker>=1.9
update_parameters['blkio_weight'] = 'blkio_weight'
if self.client.HAS_CPUSET_MEMS_OPT:
# cpuset_mems is only supported in docker>=2.3
update_parameters['cpuset_mems'] = 'cpuset_mems'
result = dict() result = dict()
for key, value in update_parameters.items(): for key, value in update_parameters.items():
if getattr(self, value, None) is not None: if getattr(self, value, None) is not None:
result[key] = getattr(self, value) if self.client.option_minimal_versions[value]['supported']:
result[key] = getattr(self, value)
return result return result
@property @property
@ -1103,18 +1090,15 @@ class TaskParameters(DockerBaseClass):
labels='labels', labels='labels',
stop_signal='stop_signal', stop_signal='stop_signal',
working_dir='working_dir', working_dir='working_dir',
stop_timeout='stop_timeout',
healthcheck='healthcheck',
) )
if not HAS_DOCKER_PY_3: if self.client.docker_py_version < LooseVersion('3.0'):
# cpu_shares and volume_driver moved to create_host_config in > 3
create_params['cpu_shares'] = 'cpu_shares' create_params['cpu_shares'] = 'cpu_shares'
create_params['volume_driver'] = 'volume_driver' create_params['volume_driver'] = 'volume_driver'
if self.client.HAS_STOP_TIMEOUT_OPT:
create_params['stop_timeout'] = 'stop_timeout'
if self.client.HAS_HEALTHCHECK_OPT:
create_params['healthcheck'] = 'healthcheck'
result = dict( result = dict(
host_config=self._host_config(), host_config=self._host_config(),
volumes=self._get_mounts(), volumes=self._get_mounts(),
@ -1122,7 +1106,8 @@ class TaskParameters(DockerBaseClass):
for key, value in create_params.items(): for key, value in create_params.items():
if getattr(self, value, None) is not None: if getattr(self, value, None) is not None:
result[key] = getattr(self, value) if self.client.option_minimal_versions[value]['supported']:
result[key] = getattr(self, value)
return result return result
def _expand_host_paths(self): def _expand_host_paths(self):
@ -1205,41 +1190,31 @@ class TaskParameters(DockerBaseClass):
devices='devices', devices='devices',
pid_mode='pid_mode', pid_mode='pid_mode',
tmpfs='tmpfs', tmpfs='tmpfs',
init='init',
uts_mode='uts',
runtime='runtime',
auto_remove='auto_remove',
device_read_bps='device_read_bps',
device_write_bps='device_write_bps',
device_read_iops='device_read_iops',
device_write_iops='device_write_iops',
) )
if self.client.HAS_AUTO_REMOVE_OPT: if self.client.docker_py_version >= LooseVersion('1.9') and self.client.docker_api_version >= LooseVersion('1.22'):
# auto_remove is only supported in docker>=2 # blkio_weight can always be updated, but can only be set on creation
host_config_params['auto_remove'] = 'auto_remove' # when docker-py and docker API are new enough
if self.client.HAS_BLKIO_WEIGHT_OPT:
# blkio_weight is only supported in docker>=1.9
host_config_params['blkio_weight'] = 'blkio_weight' host_config_params['blkio_weight'] = 'blkio_weight'
if HAS_DOCKER_PY_3: if self.client.docker_py_version >= LooseVersion('3.0'):
# cpu_shares and volume_driver moved to create_host_config in > 3 # cpu_shares and volume_driver moved to create_host_config in > 3
host_config_params['cpu_shares'] = 'cpu_shares' host_config_params['cpu_shares'] = 'cpu_shares'
host_config_params['volume_driver'] = 'volume_driver' host_config_params['volume_driver'] = 'volume_driver'
if self.client.HAS_INIT_OPT:
host_config_params['init'] = 'init'
if self.client.HAS_UTS_MODE_OPT:
host_config_params['uts_mode'] = 'uts'
if self.client.HAS_RUNTIME_OPT:
host_config_params['runtime'] = 'runtime'
if self.client.HAS_DEVICE_RW_LIMIT_OPT:
# device_read/write_bps/iops are only supported in docker>=1.9 and docker-api>=1.22
host_config_params['device_read_bps'] = 'device_read_bps'
host_config_params['device_write_bps'] = 'device_write_bps'
host_config_params['device_read_iops'] = 'device_read_iops'
host_config_params['device_write_iops'] = 'device_write_iops'
params = dict() params = dict()
for key, value in host_config_params.items(): for key, value in host_config_params.items():
if getattr(self, value, None) is not None: if getattr(self, value, None) is not None:
params[key] = getattr(self, value) if self.client.option_minimal_versions[value]['supported']:
params[key] = getattr(self, value)
if self.restart_policy: if self.restart_policy:
params['restart_policy'] = dict(Name=self.restart_policy, params['restart_policy'] = dict(Name=self.restart_policy,
@ -1733,6 +1708,7 @@ class Container(DockerBaseClass):
uts=host_config.get('UTSMode'), uts=host_config.get('UTSMode'),
expected_volumes=config.get('Volumes'), expected_volumes=config.get('Volumes'),
expected_binds=host_config.get('Binds'), expected_binds=host_config.get('Binds'),
volume_driver=host_config.get('VolumeDriver'),
volumes_from=host_config.get('VolumesFrom'), volumes_from=host_config.get('VolumesFrom'),
working_dir=config.get('WorkingDir'), working_dir=config.get('WorkingDir'),
publish_all_ports=host_config.get('PublishAllPorts'), publish_all_ports=host_config.get('PublishAllPorts'),
@ -1743,23 +1719,42 @@ class Container(DockerBaseClass):
device_read_iops=host_config.get('BlkioDeviceReadIOps'), device_read_iops=host_config.get('BlkioDeviceReadIOps'),
device_write_iops=host_config.get('BlkioDeviceWriteIOps'), device_write_iops=host_config.get('BlkioDeviceWriteIOps'),
) )
# Options which don't make sense without their accompanying option
if self.parameters.restart_policy: if self.parameters.restart_policy:
config_mapping['restart_retries'] = restart_policy.get('MaximumRetryCount') config_mapping['restart_retries'] = restart_policy.get('MaximumRetryCount')
if self.parameters.log_driver: if self.parameters.log_driver:
config_mapping['log_driver'] = log_config.get('Type') config_mapping['log_driver'] = log_config.get('Type')
config_mapping['log_options'] = log_config.get('Config') config_mapping['log_options'] = log_config.get('Config')
if self.parameters.client.HAS_AUTO_REMOVE_OPT: if self.parameters.client.option_minimal_versions['auto_remove']['supported']:
# auto_remove is only supported in docker>=2 # auto_remove is only supported in docker>=2; unfortunately it has a default
# value, that's why we have to jump through the hoops here
config_mapping['auto_remove'] = host_config.get('AutoRemove') config_mapping['auto_remove'] = host_config.get('AutoRemove')
if self.parameters.client.HAS_STOP_TIMEOUT_OPT: if self.parameters.client.option_minimal_versions['stop_timeout']['supported']:
# stop_timeout is only supported in docker>=2.1 # stop_timeout is only supported in docker>=2.1. Note that stop_timeout
# has a hybrid role, in that it used to be something only used for stopping
# containers, and is now also used as a container property. That's why
# it needs special handling here.
config_mapping['stop_timeout'] = config.get('StopTimeout') config_mapping['stop_timeout'] = config.get('StopTimeout')
if HAS_DOCKER_PY_3: if self.parameters.client.docker_api_version < LooseVersion('1.22'):
# volume_driver moved to create_host_config in > 3 # For docker API < 1.22, update_container() is not supported. Thus
config_mapping['volume_driver'] = host_config.get('VolumeDriver') # we need to handle all limits which are usually handled by
# update_container() as configuration changes which require a container
# restart.
config_mapping.update(dict(
blkio_weight=host_config.get('BlkioWeight'),
cpu_period=host_config.get('CpuPeriod'),
cpu_quota=host_config.get('CpuQuota'),
cpu_shares=host_config.get('CpuShares'),
cpuset_cpus=host_config.get('CpusetCpus'),
cpuset_mems=host_config.get('CpusetMems'),
kernel_memory=host_config.get("KernelMemory"),
memory=host_config.get('Memory'),
memory_reservation=host_config.get('MemoryReservation'),
memory_swap=host_config.get('MemorySwap'),
))
differences = [] differences = []
for key, value in config_mapping.items(): for key, value in config_mapping.items():
@ -1786,33 +1781,25 @@ class Container(DockerBaseClass):
''' '''
if not self.container.get('HostConfig'): if not self.container.get('HostConfig'):
self.fail("limits_differ_from_container: Error parsing container properties. HostConfig missing.") self.fail("limits_differ_from_container: Error parsing container properties. HostConfig missing.")
if self.parameters.client.docker_api_version < LooseVersion('1.22'):
# update_container() call not supported
return False, []
host_config = self.container['HostConfig'] host_config = self.container['HostConfig']
config_mapping = dict( config_mapping = dict(
blkio_weight=host_config.get('BlkioWeight'),
cpu_period=host_config.get('CpuPeriod'), cpu_period=host_config.get('CpuPeriod'),
cpu_quota=host_config.get('CpuQuota'), cpu_quota=host_config.get('CpuQuota'),
cpu_shares=host_config.get('CpuShares'),
cpuset_cpus=host_config.get('CpusetCpus'), cpuset_cpus=host_config.get('CpusetCpus'),
cpuset_mems=host_config.get('CpusetMems'),
kernel_memory=host_config.get("KernelMemory"), kernel_memory=host_config.get("KernelMemory"),
memory=host_config.get('Memory'), memory=host_config.get('Memory'),
memory_reservation=host_config.get('MemoryReservation'), memory_reservation=host_config.get('MemoryReservation'),
memory_swap=host_config.get('MemorySwap'), memory_swap=host_config.get('MemorySwap'),
oom_score_adj=host_config.get('OomScoreAdj'),
oom_killer=host_config.get('OomKillDisable'),
) )
if self.parameters.client.HAS_BLKIO_WEIGHT_OPT:
# blkio_weight is only supported in docker>=1.9
config_mapping['blkio_weight'] = host_config.get('BlkioWeight')
if self.parameters.client.HAS_CPUSET_MEMS_OPT:
# cpuset_mems is only supported in docker>=2.3
config_mapping['cpuset_mems'] = host_config.get('CpusetMems')
if HAS_DOCKER_PY_3:
# cpu_shares moved to create_host_config in > 3
config_mapping['cpu_shares'] = host_config.get('CpuShares')
differences = [] differences = []
for key, value in config_mapping.items(): for key, value in config_mapping.items():
if getattr(self.parameters, key, None): if getattr(self.parameters, key, None):
@ -2148,7 +2135,7 @@ class ContainerManager(DockerBaseClass):
client.module.warn('log_options is ignored when log_driver is not specified') client.module.warn('log_options is ignored when log_driver is not specified')
if client.module.params.get('healthcheck') and not client.module.params.get('healthcheck').get('test'): if client.module.params.get('healthcheck') and not client.module.params.get('healthcheck').get('test'):
client.module.warn('healthcheck is ignored when test is not specified') client.module.warn('healthcheck is ignored when test is not specified')
if client.module.params.get('restart_retries') and not client.module.params.get('restart_policy'): if client.module.params.get('restart_retries') is not None and not client.module.params.get('restart_policy'):
client.module.warn('restart_retries is ignored when restart_policy is not specified') client.module.warn('restart_retries is ignored when restart_policy is not specified')
self.client = client self.client = client
@ -2389,7 +2376,7 @@ class ContainerManager(DockerBaseClass):
self.fail("Error starting container %s: %s" % (container_id, str(exc))) self.fail("Error starting container %s: %s" % (container_id, str(exc)))
if not self.parameters.detach: if not self.parameters.detach:
if HAS_DOCKER_PY_3: if self.client.docker_py_version >= LooseVersion('3.0'):
status = self.client.wait(container_id)['StatusCode'] status = self.client.wait(container_id)['StatusCode']
else: else:
status = self.client.wait(container_id) status = self.client.wait(container_id)
@ -2480,6 +2467,14 @@ class ContainerManager(DockerBaseClass):
class AnsibleDockerClientContainer(AnsibleDockerClient): class AnsibleDockerClientContainer(AnsibleDockerClient):
# A list of module options which are not docker container properties
__NON_CONTAINER_PROPERTY_OPTIONS = (
'docker_host', 'tls_hostname', 'api_version', 'timeout', 'cacert_path', 'cert_path',
'key_path', 'ssl_version', 'tls', 'tls_verify', 'debug', 'env_file', 'force_kill',
'keep_volumes', 'ignore_image', 'name', 'pull', 'purge_networks', 'recreate',
'restart', 'state', 'trust_image_content', 'networks', 'cleanup', 'kill_signal',
'output_logs', 'paused'
)
def _parse_comparisons(self): def _parse_comparisons(self):
comparisons = {} comparisons = {}
@ -2508,10 +2503,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
for alias in data.get('aliases', []): for alias in data.get('aliases', []):
all_options.add(alias) all_options.add(alias)
# Ignore options which aren't used as container properties # Ignore options which aren't used as container properties
if option in ('docker_host', 'tls_hostname', 'api_version', 'timeout', 'cacert_path', 'cert_path', if option in self.__NON_CONTAINER_PROPERTY_OPTIONS:
'key_path', 'ssl_version', 'tls', 'tls_verify', 'debug', 'env_file', 'force_kill',
'keep_volumes', 'ignore_image', 'name', 'pull', 'purge_networks', 'recreate',
'restart', 'state', 'trust_image_content', 'networks'):
continue continue
# Determine option type # Determine option type
if option in explicit_types: if option in explicit_types:
@ -2581,37 +2573,102 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
self.module.warn('The ignore_image option has been overridden by the comparisons option!') self.module.warn('The ignore_image option has been overridden by the comparisons option!')
self.comparisons = comparisons self.comparisons = comparisons
def __init__(self, **kwargs): def _get_minimal_versions(self):
super(AnsibleDockerClientContainer, self).__init__(**kwargs) # Helper function to detect whether any specified network uses ipv4_address or ipv6_address
def detect_ipvX_address_usage():
docker_api_version = self.version()['ApiVersion'] for network in self.module.params.get("networks") or []:
init_supported = LooseVersion(docker_api_version) >= LooseVersion('1.25') if 'ipv4_address' in network or 'ipv6_address' in network:
if self.module.params.get("init") and not init_supported: return True
self.fail('docker API version is %s. Minimum version required is 1.25 to set init option.' % (docker_api_version,)) return False
init_supported = init_supported and LooseVersion(docker_version) >= LooseVersion('2.2')
if self.module.params.get("init") and not init_supported:
self.fail("docker or docker-py version is %s. Minimum version required is 2.2 to set init option. "
"If you use the 'docker-py' module, you have to switch to the docker 'Python' package." % (docker_version,))
uts_mode_supported = LooseVersion(docker_version) >= LooseVersion('3.5')
if self.module.params.get("uts") is not None and not uts_mode_supported:
self.fail("docker or docker-py version is %s. Minimum version required is 3.5 to set uts option. "
"If you use the 'docker-py' module, you have to switch to the docker 'Python' package." % (docker_version,))
blkio_weight_supported = LooseVersion(docker_version) >= LooseVersion('1.9')
if self.module.params.get("blkio_weight") is not None and not blkio_weight_supported:
self.fail("docker or docker-py version is %s. Minimum version required is 1.9 to set blkio_weight option.")
cpuset_mems_supported = LooseVersion(docker_version) >= LooseVersion('2.3') self.option_minimal_versions = dict(
if self.module.params.get("cpuset_mems") is not None and not cpuset_mems_supported: # internal options
self.fail("docker or docker-py version is %s. Minimum version required is 2.3 to set cpuset_mems option. " log_config=dict(),
"If you use the 'docker-py' module, you have to switch to the docker 'Python' package." % (docker_version,)) publish_all_ports=dict(),
ports=dict(),
volume_binds=dict(),
name=dict(),
)
for option, data in self.module.argument_spec.items():
if option in self.__NON_CONTAINER_PROPERTY_OPTIONS:
continue
self.option_minimal_versions[option] = dict()
self.option_minimal_versions.update(dict(
device_read_bps=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_read_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_write_bps=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
device_write_iops=dict(docker_py_version='1.9.0', docker_api_version='1.22'),
dns_opts=dict(docker_api_version='1.21', docker_py_version='1.10.0'),
ipc_mode=dict(docker_api_version='1.25'),
mac_address=dict(docker_api_version='1.25'),
oom_killer=dict(docker_py_version='2.0.0'),
oom_score_adj=dict(docker_api_version='1.22', docker_py_version='2.0.0'),
shm_size=dict(docker_api_version='1.22'),
stop_signal=dict(docker_api_version='1.21'),
tmpfs=dict(docker_api_version='1.22'),
volume_driver=dict(docker_api_version='1.21'),
memory_reservation=dict(docker_api_version='1.21'),
kernel_memory=dict(docker_api_version='1.21'),
auto_remove=dict(docker_py_version='2.1.0', docker_api_version='1.25'),
healthcheck=dict(docker_py_version='2.0.0', docker_api_version='1.24'),
init=dict(docker_py_version='2.2.0', docker_api_version='1.25'),
runtime=dict(docker_py_version='2.4.0', docker_api_version='1.25'),
sysctls=dict(docker_py_version='1.10.0', docker_api_version='1.24'),
userns_mode=dict(docker_py_version='1.10.0', docker_api_version='1.23'),
uts=dict(docker_py_version='3.5.0', docker_api_version='1.25'),
# specials
ipvX_address_supported=dict(docker_py_version='1.9.0', detect_usage=detect_ipvX_address_usage,
usage_msg='ipv4_address or ipv6_address in networks'),
stop_timeout=dict(), # see below!
))
for option, data in self.option_minimal_versions.items():
# Test whether option is supported, and store result
support_docker_py = True
support_docker_api = True
if 'docker_py_version' in data:
support_docker_py = self.docker_py_version >= LooseVersion(data['docker_py_version'])
if 'docker_api_version' in data:
support_docker_api = self.docker_api_version >= LooseVersion(data['docker_api_version'])
data['supported'] = support_docker_py and support_docker_api
# Fail if option is not supported but used
if not data['supported']:
# Test whether option is specified
if 'detect_usage' in data:
used = data['detect_usage']()
else:
used = self.module.params.get(option) is not None
if used and 'default' in self.module.argument_spec[option]:
used = self.module.params[option] != self.module.argument_spec[option]['default']
if used:
# If the option is used, compose error message.
if 'usage_msg' in data:
usg = data['usage_msg']
else:
usg = 'set %s option' % (option, )
if not support_docker_api:
msg = 'docker API version is %s. Minimum version required is %s to %s.'
msg = msg % (self.docker_api_version_str, data['docker_api_version'], usg)
elif not support_docker_py:
if LooseVersion(data['docker_py_version']) < LooseVersion('2.0.0'):
msg = ("docker-py version is %s. Minimum version required is %s to %s. "
"Consider switching to the 'docker' package if you do not require Python 2.6 support.")
elif self.docker_py_version < LooseVersion('2.0.0'):
msg = ("docker-py version is %s. Minimum version required is %s to %s. "
"You have to switch to the Python 'docker' package. First uninstall 'docker-py' before "
"installing 'docker' to avoid a broken installation.")
else:
msg = "docker version is %s. Minimum version required is %s to %s."
msg = msg % (docker_version, data['docker_py_version'], usg)
else:
# should not happen
msg = 'Cannot %s with your configuration.' % (usg, )
self.fail(msg)
stop_timeout_supported = LooseVersion(docker_api_version) >= LooseVersion('1.25') stop_timeout_supported = self.docker_api_version >= LooseVersion('1.25')
stop_timeout_needed_for_update = self.module.params.get("stop_timeout") is not None and self.module.params.get('state') != 'absent' stop_timeout_needed_for_update = self.module.params.get("stop_timeout") is not None and self.module.params.get('state') != 'absent'
if stop_timeout_supported: if stop_timeout_supported:
stop_timeout_supported = LooseVersion(docker_version) >= LooseVersion('2.1') stop_timeout_supported = self.docker_py_version >= LooseVersion('2.1')
if stop_timeout_needed_for_update and not stop_timeout_supported: if stop_timeout_needed_for_update and not stop_timeout_supported:
# We warn (instead of fail) since in older versions, stop_timeout was not used # We warn (instead of fail) since in older versions, stop_timeout was not used
# to update the container's configuration, but only when stopping a container. # to update the container's configuration, but only when stopping a container.
@ -2623,55 +2680,12 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
# We warn (instead of fail) since in older versions, stop_timeout was not used # We warn (instead of fail) since in older versions, stop_timeout was not used
# to update the container's configuration, but only when stopping a container. # to update the container's configuration, but only when stopping a container.
self.module.warn("docker API version is %s. Minimum version required is 1.25 to set or " self.module.warn("docker API version is %s. Minimum version required is 1.25 to set or "
"update the container's stop_timeout configuration." % (docker_api_version,)) "update the container's stop_timeout configuration." % (self.docker_api_version_str,))
self.option_minimal_versions['stop_timeout']['supported'] = stop_timeout_supported
ipvX_address_supported = LooseVersion(docker_version) >= LooseVersion('1.9')
if not ipvX_address_supported:
ipvX_address_used = False
for network in self.module.params.get("networks", []):
if 'ipv4_address' in network or 'ipv6_address' in network:
ipvX_address_used = True
if ipvX_address_used:
self.fail("docker or docker-py version is %s. Minimum version required is 1.9 to use "
"ipv4_address or ipv6_address in networks." % (docker_version,))
runtime_supported = LooseVersion(docker_api_version) >= LooseVersion('1.12')
if self.module.params.get("runtime") and not runtime_supported:
self.fail('docker API version is %s. Minimum version required is 1.12 to set runtime option.' % (docker_api_version,))
healthcheck_supported = LooseVersion(docker_version) >= LooseVersion('2.0')
if self.module.params.get("healthcheck") and not healthcheck_supported:
self.fail("docker or docker-py version is %s. Minimum version required is 2.0 to set healthcheck option." % (docker_version,))
found_device_limit_param = False
for x in ["device_read_bps", "device_write_bps", "device_read_iops", "device_write_iops"]:
if self.module.params.get(x):
found_device_limit_param = True
break
device_rw_limit_supported = LooseVersion(docker_api_version) >= LooseVersion('1.22')
if found_device_limit_param and not device_rw_limit_supported:
self.fail('docker API version is %s. Minimum version required is 1.22 to set device IO limit options.' % (docker_api_version,))
device_rw_limit_supported = device_rw_limit_supported and LooseVersion(docker_version) >= LooseVersion('1.9.0')
if found_device_limit_param and not device_rw_limit_supported:
self.fail("docker or docker-py version is %s. Minimum version required is 1.9 to set device IO limit optons. "
"If you use the 'docker-py' module, you have to switch to the docker 'Python' package." % (docker_version,))
self.HAS_INIT_OPT = init_supported
self.HAS_UTS_MODE_OPT = uts_mode_supported
self.HAS_BLKIO_WEIGHT_OPT = blkio_weight_supported
self.HAS_CPUSET_MEMS_OPT = cpuset_mems_supported
self.HAS_STOP_TIMEOUT_OPT = stop_timeout_supported
self.HAS_HEALTHCHECK_OPT = healthcheck_supported
self.HAS_DEVICE_RW_LIMIT_OPT = device_rw_limit_supported
self.HAS_AUTO_REMOVE_OPT = HAS_DOCKER_PY_2 or HAS_DOCKER_PY_3
self.HAS_RUNTIME_OPT = runtime_supported
if self.module.params.get('auto_remove') and not self.HAS_AUTO_REMOVE_OPT:
self.fail("'auto_remove' is not compatible with the 'docker-py' Python package. It requires the newer 'docker' Python package.")
def __init__(self, **kwargs):
super(AnsibleDockerClientContainer, self).__init__(**kwargs)
self._get_minimal_versions()
self._parse_comparisons() self._parse_comparisons()

@ -1,7 +1,3 @@
- name: Check Docker API version ---
command: "{{ ansible_python.executable }} -c 'import docker; print(docker.from_env().version()[\"ApiVersion\"])'"
register: docker_api_version
ignore_errors: yes
- include_tasks: test_docker_config.yml - include_tasks: test_docker_config.yml
when: docker_api_version.rc == 0 and docker_api_version.stdout is version('1.30', '>=') when: docker_api_version is version('1.30', '>=')

@ -1,3 +1,4 @@
---
- name: Make sure we're not already using Docker swarm - name: Make sure we're not already using Docker swarm
docker_swarm: docker_swarm:
state: absent state: absent

@ -1,4 +1,5 @@
--- ---
# Create random name prefix (for containers, networks, ...)
- name: Create random container name prefix - name: Create random container name prefix
set_fact: set_fact:
cname_prefix: "{{ 'ansible-test-%0x' % ((2**32) | random) }}" cname_prefix: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
@ -8,6 +9,7 @@
- debug: - debug:
msg: "Using container name prefix {{ cname_prefix }}" msg: "Using container name prefix {{ cname_prefix }}"
# Run the tests
- block: - block:
- include_tasks: run-test.yml - include_tasks: run-test.yml
with_fileglob: with_fileglob:
@ -26,6 +28,6 @@
state: absent state: absent
force: yes force: yes
with_items: "{{ dnetworks }}" with_items: "{{ dnetworks }}"
when: docker_py_version is version('1.10.0', '>=')
# Skip for CentOS 6 when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')
when: ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6

@ -21,6 +21,7 @@
- "{{ nname_2 }}" - "{{ nname_2 }}"
loop_control: loop_control:
loop_var: network_name loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')
#################################################################### ####################################################################
## auto_remove ##################################################### ## auto_remove #####################################################
@ -34,6 +35,7 @@
state: started state: started
auto_remove: yes auto_remove: yes
register: auto_remove_1 register: auto_remove_1
ignore_errors: yes
- name: Give container 1 second to be sure it terminated - name: Give container 1 second to be sure it terminated
pause: pause:
@ -44,11 +46,18 @@
name: "{{ cname }}" name: "{{ cname }}"
state: absent state: absent
register: auto_remove_2 register: auto_remove_2
ignore_errors: yes
- assert: - assert:
that: that:
- auto_remove_1 is changed - auto_remove_1 is changed
- auto_remove_2 is not changed - auto_remove_2 is not changed
when: docker_py_version is version('2.1.0', '>=')
- assert:
that:
- auto_remove_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.1.0') in auto_remove_1.msg"
when: docker_py_version is version('2.1.0', '<')
#################################################################### ####################################################################
## blkio_weight #################################################### ## blkio_weight ####################################################
@ -509,6 +518,7 @@
auto_remove: yes auto_remove: yes
cleanup: yes cleanup: yes
register: detach_auto_remove register: detach_auto_remove
ignore_errors: yes
- name: cleanup (unnecessary) - name: cleanup (unnecessary)
docker_container: docker_container:
@ -522,8 +532,11 @@
- detach_no_cleanup_cleanup is changed - detach_no_cleanup_cleanup is changed
- "'Hello from Docker!' in detach_cleanup.ansible_facts.docker_container.Output" - "'Hello from Docker!' in detach_cleanup.ansible_facts.docker_container.Output"
- detach_cleanup_cleanup is not changed - detach_cleanup_cleanup is not changed
- assert:
that:
- "'Cannot retrieve result as auto_remove is enabled' == detach_auto_remove.ansible_facts.docker_container.Output" - "'Cannot retrieve result as auto_remove is enabled' == detach_auto_remove.ansible_facts.docker_container.Output"
- detach_auto_remove_cleanup is not changed - detach_auto_remove_cleanup is not changed
when: docker_py_version is version('2.1.0', '>=')
#################################################################### ####################################################################
## devices ######################################################### ## devices #########################################################
@ -602,6 +615,7 @@
- path: /dev/urandom - path: /dev/urandom
rate: 10K rate: 10K
register: device_read_bps_1 register: device_read_bps_1
ignore_errors: yes
- name: device_read_bps (idempotency) - name: device_read_bps (idempotency)
docker_container: docker_container:
@ -615,6 +629,7 @@
- path: /dev/random - path: /dev/random
rate: 20M rate: 20M
register: device_read_bps_2 register: device_read_bps_2
ignore_errors: yes
- name: device_read_bps (lesser entries) - name: device_read_bps (lesser entries)
docker_container: docker_container:
@ -626,6 +641,7 @@
- path: /dev/random - path: /dev/random
rate: 20M rate: 20M
register: device_read_bps_3 register: device_read_bps_3
ignore_errors: yes
- name: device_read_bps (changed) - name: device_read_bps (changed)
docker_container: docker_container:
@ -640,6 +656,7 @@
rate: 5K rate: 5K
stop_timeout: 1 stop_timeout: 1
register: device_read_bps_4 register: device_read_bps_4
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -653,6 +670,12 @@
- device_read_bps_2 is not changed - device_read_bps_2 is not changed
- device_read_bps_3 is not changed - device_read_bps_3 is not changed
- device_read_bps_4 is changed - device_read_bps_4 is changed
when: docker_py_version is version('1.9.0', '>=')
- assert:
that:
- device_read_bps_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.9.0') in device_read_bps_1.msg"
when: docker_py_version is version('1.9.0', '<')
#################################################################### ####################################################################
## device_read_iops ################################################ ## device_read_iops ################################################
@ -670,6 +693,7 @@
- path: /dev/urandom - path: /dev/urandom
rate: 20 rate: 20
register: device_read_iops_1 register: device_read_iops_1
ignore_errors: yes
- name: device_read_iops (idempotency) - name: device_read_iops (idempotency)
docker_container: docker_container:
@ -683,6 +707,7 @@
- path: /dev/random - path: /dev/random
rate: 10 rate: 10
register: device_read_iops_2 register: device_read_iops_2
ignore_errors: yes
- name: device_read_iops (less) - name: device_read_iops (less)
docker_container: docker_container:
@ -694,6 +719,7 @@
- path: /dev/random - path: /dev/random
rate: 10 rate: 10
register: device_read_iops_3 register: device_read_iops_3
ignore_errors: yes
- name: device_read_iops (changed) - name: device_read_iops (changed)
docker_container: docker_container:
@ -708,6 +734,7 @@
rate: 50 rate: 50
stop_timeout: 1 stop_timeout: 1
register: device_read_iops_4 register: device_read_iops_4
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -721,6 +748,12 @@
- device_read_iops_2 is not changed - device_read_iops_2 is not changed
- device_read_iops_3 is not changed - device_read_iops_3 is not changed
- device_read_iops_4 is changed - device_read_iops_4 is changed
when: docker_py_version is version('1.9.0', '>=')
- assert:
that:
- device_read_iops_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.9.0') in device_read_iops_1.msg"
when: docker_py_version is version('1.9.0', '<')
#################################################################### ####################################################################
## device_write_bps and device_write_iops ########################## ## device_write_bps and device_write_iops ##########################
@ -739,6 +772,7 @@
- path: /dev/urandom - path: /dev/urandom
rate: 30 rate: 30
register: device_write_limit_1 register: device_write_limit_1
ignore_errors: yes
- name: device_write_bps and device_write_iops (idempotency) - name: device_write_bps and device_write_iops (idempotency)
docker_container: docker_container:
@ -753,6 +787,7 @@
- path: /dev/urandom - path: /dev/urandom
rate: 30 rate: 30
register: device_write_limit_2 register: device_write_limit_2
ignore_errors: yes
- name: device_write_bps device_write_iops (changed) - name: device_write_bps device_write_iops (changed)
docker_container: docker_container:
@ -768,6 +803,7 @@
rate: 100 rate: 100
stop_timeout: 1 stop_timeout: 1
register: device_write_limit_3 register: device_write_limit_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -780,6 +816,12 @@
- device_write_limit_1 is changed - device_write_limit_1 is changed
- device_write_limit_2 is not changed - device_write_limit_2 is not changed
- device_write_limit_3 is changed - device_write_limit_3 is changed
when: docker_py_version is version('1.9.0', '>=')
- assert:
that:
- device_write_limit_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.9.0') in device_write_limit_1.msg"
when: docker_py_version is version('1.9.0', '<')
#################################################################### ####################################################################
## dns_opts ######################################################## ## dns_opts ########################################################
@ -795,6 +837,7 @@
- "timeout:10" - "timeout:10"
- rotate - rotate
register: dns_opts_1 register: dns_opts_1
ignore_errors: yes
- name: dns_opts (idempotency) - name: dns_opts (idempotency)
docker_container: docker_container:
@ -806,6 +849,7 @@
- rotate - rotate
- "timeout:10" - "timeout:10"
register: dns_opts_2 register: dns_opts_2
ignore_errors: yes
- name: dns_opts (less resolv.conf options) - name: dns_opts (less resolv.conf options)
docker_container: docker_container:
@ -816,6 +860,7 @@
dns_opts: dns_opts:
- "timeout:10" - "timeout:10"
register: dns_opts_3 register: dns_opts_3
ignore_errors: yes
- name: dns_opts (more resolv.conf options) - name: dns_opts (more resolv.conf options)
docker_container: docker_container:
@ -828,6 +873,7 @@
- no-check-names - no-check-names
stop_timeout: 1 stop_timeout: 1
register: dns_opts_4 register: dns_opts_4
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -841,6 +887,12 @@
- dns_opts_2 is not changed - dns_opts_2 is not changed
- dns_opts_3 is not changed - dns_opts_3 is not changed
- dns_opts_4 is changed - dns_opts_4 is changed
when: docker_py_version is version('1.10.0', '>=')
- assert:
that:
- dns_opts_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.10.0') in dns_opts_1.msg"
when: docker_py_version is version('1.10.0', '<')
#################################################################### ####################################################################
## dns_search_domains ############################################## ## dns_search_domains ##############################################
@ -1391,6 +1443,7 @@
retries: 2 retries: 2
stop_timeout: 1 stop_timeout: 1
register: healthcheck_1 register: healthcheck_1
ignore_errors: yes
- name: healthcheck (idempotency) - name: healthcheck (idempotency)
docker_container: docker_container:
@ -1408,6 +1461,7 @@
retries: 2 retries: 2
stop_timeout: 1 stop_timeout: 1
register: healthcheck_2 register: healthcheck_2
ignore_errors: yes
- name: healthcheck (changed) - name: healthcheck (changed)
docker_container: docker_container:
@ -1425,6 +1479,7 @@
retries: 3 retries: 3
stop_timeout: 1 stop_timeout: 1
register: healthcheck_3 register: healthcheck_3
ignore_errors: yes
- name: healthcheck (no change) - name: healthcheck (no change)
docker_container: docker_container:
@ -1434,6 +1489,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: healthcheck_4 register: healthcheck_4
ignore_errors: yes
- name: healthcheck (disabled) - name: healthcheck (disabled)
docker_container: docker_container:
@ -1446,6 +1502,7 @@
- NONE - NONE
stop_timeout: 1 stop_timeout: 1
register: healthcheck_5 register: healthcheck_5
ignore_errors: yes
- name: healthcheck (disabled, idempotency) - name: healthcheck (disabled, idempotency)
docker_container: docker_container:
@ -1458,6 +1515,7 @@
- NONE - NONE
stop_timeout: 1 stop_timeout: 1
register: healthcheck_6 register: healthcheck_6
ignore_errors: yes
- name: healthcheck (string in healthcheck test, changed) - name: healthcheck (string in healthcheck test, changed)
docker_container: docker_container:
@ -1469,6 +1527,7 @@
test: "sleep 1" test: "sleep 1"
stop_timeout: 1 stop_timeout: 1
register: healthcheck_7 register: healthcheck_7
ignore_errors: yes
- name: healthcheck (string in healthcheck test, idempotency) - name: healthcheck (string in healthcheck test, idempotency)
docker_container: docker_container:
@ -1480,6 +1539,7 @@
test: "sleep 1" test: "sleep 1"
stop_timeout: 1 stop_timeout: 1
register: healthcheck_8 register: healthcheck_8
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -1497,6 +1557,12 @@
- healthcheck_6 is not changed - healthcheck_6 is not changed
- healthcheck_7 is changed - healthcheck_7 is changed
- healthcheck_8 is not changed - healthcheck_8 is not changed
when: docker_py_version is version('2.0.0', '>=')
- assert:
that:
- healthcheck_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.0.0') in healthcheck_1.msg"
when: docker_py_version is version('2.0.0', '<')
#################################################################### ####################################################################
## hostname ######################################################## ## hostname ########################################################
@ -1554,6 +1620,7 @@
init: yes init: yes
state: started state: started
register: init_1 register: init_1
ignore_errors: yes
- name: init (idempotency) - name: init (idempotency)
docker_container: docker_container:
@ -1563,6 +1630,7 @@
init: yes init: yes
state: started state: started
register: init_2 register: init_2
ignore_errors: yes
- name: init (change) - name: init (change)
docker_container: docker_container:
@ -1573,6 +1641,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: init_3 register: init_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -1585,6 +1654,12 @@
- init_1 is changed - init_1 is changed
- init_2 is not changed - init_2 is not changed
- init_3 is changed - init_3 is changed
when: docker_py_version is version('2.2.0', '>=')
- assert:
that:
- init_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.2.0') in init_1.msg"
when: docker_py_version is version('2.2.0', '<')
#################################################################### ####################################################################
## interactive ##################################################### ## interactive #####################################################
@ -2335,77 +2410,80 @@
## networks, purge_networks ######################################## ## networks, purge_networks ########################################
#################################################################### ####################################################################
- name: networks, purge_networks - block:
docker_container: - name: networks, purge_networks
image: alpine:3.8 docker_container:
command: '/bin/sh -c "sleep 10m"' image: alpine:3.8
name: "{{ cname }}" command: '/bin/sh -c "sleep 10m"'
state: started name: "{{ cname }}"
purge_networks: yes state: started
networks: purge_networks: yes
- name: bridge networks:
- name: "{{ nname_1 }}" - name: bridge
register: networks_1 - name: "{{ nname_1 }}"
register: networks_1
- name: networks, purge_networks (idempotency)
docker_container: - name: networks, purge_networks (idempotency)
image: alpine:3.8 docker_container:
command: '/bin/sh -c "sleep 10m"' image: alpine:3.8
name: "{{ cname }}" command: '/bin/sh -c "sleep 10m"'
state: started name: "{{ cname }}"
purge_networks: yes state: started
networks: purge_networks: yes
- name: "{{ nname_1 }}" networks:
- name: bridge - name: "{{ nname_1 }}"
register: networks_2 - name: bridge
register: networks_2
- name: networks (less networks)
docker_container: - name: networks (less networks)
image: alpine:3.8 docker_container:
command: '/bin/sh -c "sleep 10m"' image: alpine:3.8
name: "{{ cname }}" command: '/bin/sh -c "sleep 10m"'
state: started name: "{{ cname }}"
networks: state: started
- name: bridge networks:
register: networks_3 - name: bridge
register: networks_3
- name: networks, purge_networks (less networks)
docker_container: - name: networks, purge_networks (less networks)
image: alpine:3.8 docker_container:
command: '/bin/sh -c "sleep 10m"' image: alpine:3.8
name: "{{ cname }}" command: '/bin/sh -c "sleep 10m"'
state: started name: "{{ cname }}"
purge_networks: yes state: started
networks: purge_networks: yes
- name: bridge networks:
register: networks_4 - name: bridge
register: networks_4
- name: networks, purge_networks (more networks)
docker_container: - name: networks, purge_networks (more networks)
image: alpine:3.8 docker_container:
command: '/bin/sh -c "sleep 10m"' image: alpine:3.8
name: "{{ cname }}" command: '/bin/sh -c "sleep 10m"'
state: started name: "{{ cname }}"
purge_networks: yes state: started
networks: purge_networks: yes
- name: bridge networks:
- name: "{{ nname_2 }}" - name: bridge
stop_timeout: 1 - name: "{{ nname_2 }}"
register: networks_5 stop_timeout: 1
register: networks_5
- name: cleanup
docker_container: - name: cleanup
name: "{{ cname }}" docker_container:
state: absent name: "{{ cname }}"
stop_timeout: 1 state: absent
stop_timeout: 1
- assert:
that: - assert:
- networks_1 is changed that:
- networks_2 is not changed - networks_1 is changed
- networks_3 is not changed - networks_2 is not changed
- networks_4 is changed - networks_3 is not changed
- networks_5 is changed - networks_4 is changed
- networks_5 is changed
when: docker_py_version is version('1.10.0', '>=')
#################################################################### ####################################################################
## oom_killer ###################################################### ## oom_killer ######################################################
@ -2419,6 +2497,7 @@
oom_killer: yes oom_killer: yes
state: started state: started
register: oom_killer_1 register: oom_killer_1
ignore_errors: yes
- name: oom_killer (idempotency) - name: oom_killer (idempotency)
docker_container: docker_container:
@ -2428,6 +2507,7 @@
oom_killer: yes oom_killer: yes
state: started state: started
register: oom_killer_2 register: oom_killer_2
ignore_errors: yes
- name: oom_killer (change) - name: oom_killer (change)
docker_container: docker_container:
@ -2438,6 +2518,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: oom_killer_3 register: oom_killer_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -2450,6 +2531,12 @@
- oom_killer_1 is changed - oom_killer_1 is changed
- oom_killer_2 is not changed - oom_killer_2 is not changed
- oom_killer_3 is changed - oom_killer_3 is changed
when: docker_py_version is version('2.0.0', '>=')
- assert:
that:
- oom_killer_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.0.0') in oom_killer_1.msg"
when: docker_py_version is version('2.0.0', '<')
#################################################################### ####################################################################
## oom_score_adj ################################################### ## oom_score_adj ###################################################
@ -2463,6 +2550,7 @@
oom_score_adj: 5 oom_score_adj: 5
state: started state: started
register: oom_score_adj_1 register: oom_score_adj_1
ignore_errors: yes
- name: oom_score_adj (idempotency) - name: oom_score_adj (idempotency)
docker_container: docker_container:
@ -2472,6 +2560,7 @@
oom_score_adj: 5 oom_score_adj: 5
state: started state: started
register: oom_score_adj_2 register: oom_score_adj_2
ignore_errors: yes
- name: oom_score_adj (change) - name: oom_score_adj (change)
docker_container: docker_container:
@ -2482,6 +2571,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: oom_score_adj_3 register: oom_score_adj_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -2494,6 +2584,12 @@
- oom_score_adj_1 is changed - oom_score_adj_1 is changed
- oom_score_adj_2 is not changed - oom_score_adj_2 is not changed
- oom_score_adj_3 is changed - oom_score_adj_3 is changed
when: docker_py_version is version('2.0.0', '>=')
- assert:
that:
- oom_score_adj_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.0.0') in oom_score_adj_1.msg"
when: docker_py_version is version('2.0.0', '<')
#################################################################### ####################################################################
## output_logs ##################################################### ## output_logs #####################################################
@ -2589,6 +2685,8 @@
state: started state: started
pid_mode: "container:{{ pid_mode_helper.ansible_facts.docker_container.Id }}" pid_mode: "container:{{ pid_mode_helper.ansible_facts.docker_container.Id }}"
register: pid_mode_1 register: pid_mode_1
ignore_errors: yes
# docker-py < 2.0 does not support "arbitrary" pid_mode values
- name: pid_mode (idempotency) - name: pid_mode (idempotency)
docker_container: docker_container:
@ -2598,6 +2696,8 @@
state: started state: started
pid_mode: "container:{{ pid_mode_helper.ansible_facts.docker_container.Id }}" pid_mode: "container:{{ pid_mode_helper.ansible_facts.docker_container.Id }}"
register: pid_mode_2 register: pid_mode_2
ignore_errors: yes
# docker-py < 2.0 does not support "arbitrary" pid_mode values
- name: pid_mode (change) - name: pid_mode (change)
docker_container: docker_container:
@ -2625,6 +2725,13 @@
- pid_mode_1 is changed - pid_mode_1 is changed
- pid_mode_2 is not changed - pid_mode_2 is not changed
- pid_mode_3 is changed - pid_mode_3 is changed
when: docker_py_version is version('2.0.0', '>=')
- assert:
that:
- pid_mode_1 is failed
- pid_mode_2 is failed
- pid_mode_3 is changed
when: docker_py_version is version('2.0.0', '<')
#################################################################### ####################################################################
## privileged ###################################################### ## privileged ######################################################
@ -2985,6 +3092,7 @@
runtime: runc runtime: runc
state: started state: started
register: runtime_1 register: runtime_1
ignore_errors: yes
- name: runtime (idempotency) - name: runtime (idempotency)
docker_container: docker_container:
@ -2994,6 +3102,7 @@
runtime: runc runtime: runc
state: started state: started
register: runtime_2 register: runtime_2
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -3005,6 +3114,12 @@
that: that:
- runtime_1 is changed - runtime_1 is changed
- runtime_2 is not changed - runtime_2 is not changed
when: docker_py_version is version('2.4.0', '>=')
- assert:
that:
- runtime_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 2.4.0') in runtime_1.msg"
when: docker_py_version is version('2.4.0', '<')
#################################################################### ####################################################################
## security_opts ################################################### ## security_opts ###################################################
@ -3239,6 +3354,7 @@
net.ipv4.icmp_echo_ignore_all: 1 net.ipv4.icmp_echo_ignore_all: 1
net.ipv4.ip_forward: 1 net.ipv4.ip_forward: 1
register: sysctls_1 register: sysctls_1
ignore_errors: yes
- name: sysctls (idempotency) - name: sysctls (idempotency)
docker_container: docker_container:
@ -3250,6 +3366,7 @@
net.ipv4.ip_forward: 1 net.ipv4.ip_forward: 1
net.ipv4.icmp_echo_ignore_all: 1 net.ipv4.icmp_echo_ignore_all: 1
register: sysctls_2 register: sysctls_2
ignore_errors: yes
- name: sysctls (less sysctls) - name: sysctls (less sysctls)
docker_container: docker_container:
@ -3260,6 +3377,7 @@
sysctls: sysctls:
net.ipv4.icmp_echo_ignore_all: 1 net.ipv4.icmp_echo_ignore_all: 1
register: sysctls_3 register: sysctls_3
ignore_errors: yes
- name: sysctls (more sysctls) - name: sysctls (more sysctls)
docker_container: docker_container:
@ -3272,6 +3390,7 @@
net.ipv6.conf.default.accept_redirects: 0 net.ipv6.conf.default.accept_redirects: 0
stop_timeout: 1 stop_timeout: 1
register: sysctls_4 register: sysctls_4
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -3285,6 +3404,12 @@
- sysctls_2 is not changed - sysctls_2 is not changed
- sysctls_3 is not changed - sysctls_3 is not changed
- sysctls_4 is changed - sysctls_4 is changed
when: docker_py_version is version('1.10.0', '>=')
- assert:
that:
- sysctls_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.10.0') in sysctls_1.msg"
when: docker_py_version is version('1.10.0', '<')
#################################################################### ####################################################################
## tmpfs ########################################################### ## tmpfs ###########################################################
@ -3514,6 +3639,7 @@
userns_mode: host userns_mode: host
state: started state: started
register: userns_mode_1 register: userns_mode_1
ignore_errors: yes
- name: userns_mode (idempotency) - name: userns_mode (idempotency)
docker_container: docker_container:
@ -3523,6 +3649,7 @@
userns_mode: host userns_mode: host
state: started state: started
register: userns_mode_2 register: userns_mode_2
ignore_errors: yes
- name: userns_mode (change) - name: userns_mode (change)
docker_container: docker_container:
@ -3533,6 +3660,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: userns_mode_3 register: userns_mode_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -3545,6 +3673,12 @@
- userns_mode_1 is changed - userns_mode_1 is changed
- userns_mode_2 is not changed - userns_mode_2 is not changed
- userns_mode_3 is changed - userns_mode_3 is changed
when: docker_py_version is version('1.10.0', '>=')
- assert:
that:
- userns_mode_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 1.10.0') in userns_mode_1.msg"
when: docker_py_version is version('1.10.0', '<')
#################################################################### ####################################################################
## uts ############################################################# ## uts #############################################################
@ -3558,6 +3692,7 @@
uts: host uts: host
state: started state: started
register: uts_1 register: uts_1
ignore_errors: yes
- name: uts (idempotency) - name: uts (idempotency)
docker_container: docker_container:
@ -3567,6 +3702,7 @@
uts: host uts: host
state: started state: started
register: uts_2 register: uts_2
ignore_errors: yes
- name: uts (change) - name: uts (change)
docker_container: docker_container:
@ -3577,6 +3713,7 @@
state: started state: started
stop_timeout: 1 stop_timeout: 1
register: uts_3 register: uts_3
ignore_errors: yes
- name: cleanup - name: cleanup
docker_container: docker_container:
@ -3589,6 +3726,12 @@
- uts_1 is changed - uts_1 is changed
- uts_2 is not changed - uts_2 is not changed
- uts_3 is changed - uts_3 is changed
when: docker_py_version is version('3.5.0', '>=')
- assert:
that:
- uts_1 is failed
- "('version is ' ~ docker_py_version ~'. Minimum version required is 3.5.0') in uts_1.msg"
when: docker_py_version is version('3.5.0', '<')
#################################################################### ####################################################################
## keep_volumes #################################################### ## keep_volumes ####################################################
@ -3836,3 +3979,4 @@
- "{{ nname_2 }}" - "{{ nname_2 }}"
loop_control: loop_control:
loop_var: network_name loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')

@ -17,3 +17,21 @@
state: present state: present
name: 'docker{{ extra_packages }}' name: 'docker{{ extra_packages }}'
extra_args: "-c {{ role_path }}/../../../runner/requirements/constraints.txt" extra_args: "-c {{ role_path }}/../../../runner/requirements/constraints.txt"
# Detect docker API and docker-py versions
- name: Check Docker API version
command: "{{ ansible_python.executable }} -c 'import docker; print(docker.from_env().version()[\"ApiVersion\"])'"
register: docker_api_version_stdout
ignore_errors: yes
- name: Check docker-py API version
command: "{{ ansible_python.executable }} -c 'import docker; print(docker.__version__)'"
register: docker_py_version_stdout
ignore_errors: yes
- set_fact:
docker_api_version: "{{ docker_api_version_stdout.stdout or '0.0' }}"
docker_py_version: "{{ docker_py_version_stdout.stdout or '0.0' }}"
- debug:
msg: "Docker API version: {{ docker_api_version }}; docker-py library version: {{ docker_py_version }}"

Loading…
Cancel
Save