mirror of
https://github.com/ansible-collections/community.docker.git
synced 2026-03-15 19:58:28 +00:00
Rewrite docker_container to use Docker API directly (#422)
* Begin experiments for docker_container rewrite. * Continued. * We support API >= 1.25 only anyway. * Continued. * Fix bugs. * Complete first basic implementation. * Continuing. * Improvements and fixes. * Continuing. * More 'easy' options. * More options. * Work on volumes and mounts. * Add more options. * The last option. * Copy over. * Fix exposed ports. * Fix bugs. * Fix command and entrypoint. * More fixes. * Fix more bugs. * ci_complete * Lint, fix Python 2.7 bugs, work around ansible-test bug. ci_complete * Remove no longer applicable test. ci_complete * Remove unnecessary ignore. ci_complete * Start with engine driver. * Refactoring. * Avoid using anything Docker specific from self.client. * Refactor. * Add Python 2.6 ignore.txt entries for ansible-core < 2.12. * Improve healthcheck handling. * Fix container removal logic. * ci_complete * Remove handling of older Docker SDK for Pyhon versions from integration tests. * Avoid recreation if a pure update is possible without losing the diff data. * Cover the case that blkio_weight does not work. * Update plugins/module_utils/module_container/docker_api.py Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com> * Improve memory_swap tests. * Fix URLs in changelog fragment. Co-authored-by: Brian Scholer <1260690+briantist@users.noreply.github.com>
This commit is contained in:
parent
04121b5882
commit
77e63e2cca
11
changelogs/fragments/docker_container.yml
Normal file
11
changelogs/fragments/docker_container.yml
Normal file
@ -0,0 +1,11 @@
|
||||
major_changes:
|
||||
- "docker_container - no longer uses the Docker SDK for Python. It requires ``requests`` to be installed,
|
||||
and depending on the features used has some more requirements. If the Docker SDK for Python is installed,
|
||||
these requirements are likely met (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
- "docker_container - the module was completely rewritten from scratch (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
breaking_changes:
|
||||
- "docker_container - ``publish_all_ports`` is no longer ignored in ``comparisons`` (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
- "docker_container - ``exposed_ports`` is no longer ignored in ``comparisons``. Before, its value was assumed to be identical with the value of ``published_ports`` (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
- "docker_container - ``log_options`` can no longer be specified when ``log_driver`` is not specified (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
- "docker_container - ``restart_retries`` can no longer be specified when ``restart_policy`` is not specified (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
- "docker_container - ``stop_timeout`` is no longer ignored for idempotency if told to be not ignored in ``comparisons``. So far it defaulted to ``ignore`` there, and setting it to ``strict`` had no effect (https://github.com/ansible-collections/community.docker/pull/422)."
|
||||
@ -545,6 +545,9 @@ class APIClient(
|
||||
def delete_json(self, pathfmt, *args, **kwargs):
|
||||
return self._result(self._delete(self._url(pathfmt, *args, versioned_api=True), **kwargs), json=True)
|
||||
|
||||
def post_call(self, pathfmt, *args, **kwargs):
|
||||
self._raise_for_status(self._post(self._url(pathfmt, *args, versioned_api=True), **kwargs))
|
||||
|
||||
def post_json(self, pathfmt, *args, **kwargs):
|
||||
data = kwargs.pop('data', None)
|
||||
self._raise_for_status(self._post_json(self._url(pathfmt, *args, versioned_api=True), data, **kwargs))
|
||||
|
||||
@ -557,8 +557,8 @@ class AnsibleDockerClientBase(Client):
|
||||
class AnsibleDockerClient(AnsibleDockerClientBase):
|
||||
|
||||
def __init__(self, argument_spec=None, supports_check_mode=False, mutually_exclusive=None,
|
||||
required_together=None, required_if=None, required_one_of=None, min_docker_version=None,
|
||||
min_docker_api_version=None, option_minimal_versions=None,
|
||||
required_together=None, required_if=None, required_one_of=None, required_by=None,
|
||||
min_docker_version=None, min_docker_api_version=None, option_minimal_versions=None,
|
||||
option_minimal_versions_ignore_params=None, fail_results=None):
|
||||
|
||||
# Modules can put information in here which will always be returned
|
||||
@ -588,6 +588,7 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
|
||||
required_together=required_together_params,
|
||||
required_if=required_if,
|
||||
required_one_of=required_one_of,
|
||||
required_by=required_by or {},
|
||||
)
|
||||
|
||||
self.debug = self.module.params.get('debug')
|
||||
|
||||
@ -467,7 +467,7 @@ class AnsibleDockerClientBase(Client):
|
||||
class AnsibleDockerClient(AnsibleDockerClientBase):
|
||||
|
||||
def __init__(self, argument_spec=None, supports_check_mode=False, mutually_exclusive=None,
|
||||
required_together=None, required_if=None, required_one_of=None,
|
||||
required_together=None, required_if=None, required_one_of=None, required_by=None,
|
||||
min_docker_api_version=None, option_minimal_versions=None,
|
||||
option_minimal_versions_ignore_params=None, fail_results=None):
|
||||
|
||||
@ -498,6 +498,7 @@ class AnsibleDockerClient(AnsibleDockerClientBase):
|
||||
required_together=required_together_params,
|
||||
required_if=required_if,
|
||||
required_one_of=required_one_of,
|
||||
required_by=required_by or {},
|
||||
)
|
||||
|
||||
self.debug = self.module.params.get('debug')
|
||||
|
||||
1185
plugins/module_utils/module_container/base.py
Normal file
1185
plugins/module_utils/module_container/base.py
Normal file
File diff suppressed because it is too large
Load Diff
1327
plugins/module_utils/module_container/docker_api.py
Normal file
1327
plugins/module_utils/module_container/docker_api.py
Normal file
File diff suppressed because it is too large
Load Diff
803
plugins/module_utils/module_container/module.py
Normal file
803
plugins/module_utils/module_container/module.py
Normal file
@ -0,0 +1,803 @@
|
||||
# Copyright (c) 2022 Felix Fontein <felix@fontein.de>
|
||||
# Copyright 2016 Red Hat | Ansible
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import absolute_import, division, print_function
|
||||
__metaclass__ = type
|
||||
|
||||
import re
|
||||
from time import sleep
|
||||
|
||||
from ansible.module_utils.common.text.converters import to_native, to_text
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils.util import (
|
||||
DifferenceTracker,
|
||||
DockerBaseClass,
|
||||
compare_generic,
|
||||
is_image_name_id,
|
||||
sanitize_result,
|
||||
)
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils._api.utils.utils import parse_repository_tag
|
||||
|
||||
|
||||
class Container(DockerBaseClass):
|
||||
def __init__(self, container, engine_driver):
|
||||
super(Container, self).__init__()
|
||||
self.raw = container
|
||||
self.id = None
|
||||
self.image = None
|
||||
self.container = container
|
||||
self.engine_driver = engine_driver
|
||||
if container:
|
||||
self.id = engine_driver.get_container_id(container)
|
||||
self.image = engine_driver.get_image_from_container(container)
|
||||
self.log(self.container, pretty_print=True)
|
||||
|
||||
@property
|
||||
def exists(self):
|
||||
return True if self.container else False
|
||||
|
||||
@property
|
||||
def removing(self):
|
||||
return self.engine_driver.is_container_removing(self.container) if self.container else False
|
||||
|
||||
@property
|
||||
def running(self):
|
||||
return self.engine_driver.is_container_running(self.container) if self.container else False
|
||||
|
||||
@property
|
||||
def paused(self):
|
||||
return self.engine_driver.is_container_paused(self.container) if self.container else False
|
||||
|
||||
|
||||
class ContainerManager(DockerBaseClass):
|
||||
def __init__(self, module, engine_driver, client, active_options):
|
||||
self.module = module
|
||||
self.engine_driver = engine_driver
|
||||
self.client = client
|
||||
self.options = active_options
|
||||
self.all_options = self._collect_all_options(active_options)
|
||||
self.check_mode = self.module.check_mode
|
||||
self.param_cleanup = self.module.params['cleanup']
|
||||
self.param_container_default_behavior = self.module.params['container_default_behavior']
|
||||
self.param_default_host_ip = self.module.params['default_host_ip']
|
||||
self.param_debug = self.module.params['debug']
|
||||
self.param_force_kill = self.module.params['force_kill']
|
||||
self.param_image = self.module.params['image']
|
||||
self.param_image_label_mismatch = self.module.params['image_label_mismatch']
|
||||
self.param_keep_volumes = self.module.params['keep_volumes']
|
||||
self.param_kill_signal = self.module.params['kill_signal']
|
||||
self.param_name = self.module.params['name']
|
||||
self.param_networks_cli_compatible = self.module.params['networks_cli_compatible']
|
||||
self.param_output_logs = self.module.params['output_logs']
|
||||
self.param_paused = self.module.params['paused']
|
||||
self.param_pull = self.module.params['pull']
|
||||
self.param_purge_networks = self.module.params['purge_networks']
|
||||
self.param_recreate = self.module.params['recreate']
|
||||
self.param_removal_wait_timeout = self.module.params['removal_wait_timeout']
|
||||
self.param_restart = self.module.params['restart']
|
||||
self.param_state = self.module.params['state']
|
||||
self._parse_comparisons()
|
||||
self._update_params()
|
||||
self.results = {'changed': False, 'actions': []}
|
||||
self.diff = {}
|
||||
self.diff_tracker = DifferenceTracker()
|
||||
self.facts = {}
|
||||
if self.param_default_host_ip:
|
||||
valid_ip = False
|
||||
if re.match(r'^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$', self.param_default_host_ip):
|
||||
valid_ip = True
|
||||
if re.match(r'^\[[0-9a-fA-F:]+\]$', self.param_default_host_ip):
|
||||
valid_ip = True
|
||||
if re.match(r'^[0-9a-fA-F:]+$', self.param_default_host_ip):
|
||||
self.param_default_host_ip = '[{0}]'.format(self.param_default_host_ip)
|
||||
valid_ip = True
|
||||
if not valid_ip:
|
||||
self.fail('The value of default_host_ip must be an empty string, an IPv4 address, '
|
||||
'or an IPv6 address. Got "{0}" instead.'.format(self.param_default_host_ip))
|
||||
|
||||
def _collect_all_options(self, active_options):
|
||||
all_options = {}
|
||||
for options in active_options:
|
||||
for option in options.options:
|
||||
all_options[option.name] = option
|
||||
return all_options
|
||||
|
||||
def _collect_all_module_params(self):
|
||||
all_module_options = set()
|
||||
for option, data in self.module.argument_spec.items():
|
||||
all_module_options.add(option)
|
||||
if 'aliases' in data:
|
||||
for alias in data['aliases']:
|
||||
all_module_options.add(alias)
|
||||
return all_module_options
|
||||
|
||||
def _parse_comparisons(self):
|
||||
# Keep track of all module params and all option aliases
|
||||
all_module_options = self._collect_all_module_params()
|
||||
comp_aliases = {}
|
||||
for option_name, option in self.all_options.items():
|
||||
if option.not_an_ansible_option:
|
||||
continue
|
||||
comp_aliases[option_name] = option_name
|
||||
for alias in option.ansible_aliases:
|
||||
comp_aliases[alias] = option_name
|
||||
# Process legacy ignore options
|
||||
if self.module.params['ignore_image']:
|
||||
self.all_options['image'].comparison = 'ignore'
|
||||
if self.param_purge_networks:
|
||||
self.all_options['networks'].comparison = 'strict'
|
||||
# Process comparsions specified by user
|
||||
if self.module.params.get('comparisons'):
|
||||
# If '*' appears in comparisons, process it first
|
||||
if '*' in self.module.params['comparisons']:
|
||||
value = self.module.params['comparisons']['*']
|
||||
if value not in ('strict', 'ignore'):
|
||||
self.fail("The wildcard can only be used with comparison modes 'strict' and 'ignore'!")
|
||||
for option in self.all_options.values():
|
||||
if option.name == 'networks':
|
||||
# `networks` is special: only update if
|
||||
# some value is actually specified
|
||||
if self.module.params['networks'] is None:
|
||||
continue
|
||||
option.comparison = value
|
||||
# Now process all other comparisons.
|
||||
comp_aliases_used = {}
|
||||
for key, value in self.module.params['comparisons'].items():
|
||||
if key == '*':
|
||||
continue
|
||||
# Find main key
|
||||
key_main = comp_aliases.get(key)
|
||||
if key_main is None:
|
||||
if key_main in all_module_options:
|
||||
self.fail("The module option '%s' cannot be specified in the comparisons dict, "
|
||||
"since it does not correspond to container's state!" % key)
|
||||
if key not in self.all_options or self.all_options[key].not_an_ansible_option:
|
||||
self.fail("Unknown module option '%s' in comparisons dict!" % key)
|
||||
key_main = key
|
||||
if key_main in comp_aliases_used:
|
||||
self.fail("Both '%s' and '%s' (aliases of %s) are specified in comparisons dict!" % (key, comp_aliases_used[key_main], key_main))
|
||||
comp_aliases_used[key_main] = key
|
||||
# Check value and update accordingly
|
||||
if value in ('strict', 'ignore'):
|
||||
self.all_options[key_main].comparison = value
|
||||
elif value == 'allow_more_present':
|
||||
if self.all_options[key_main].comparison_type == 'value':
|
||||
self.fail("Option '%s' is a value and not a set/list/dict, so its comparison cannot be %s" % (key, value))
|
||||
self.all_options[key_main].comparison = value
|
||||
else:
|
||||
self.fail("Unknown comparison mode '%s'!" % value)
|
||||
# Copy values
|
||||
for option in self.all_options.values():
|
||||
if option.copy_comparison_from is not None:
|
||||
option.comparison = self.all_options[option.copy_comparison_from].comparison
|
||||
# Check legacy values
|
||||
if self.module.params['ignore_image'] and self.all_options['image'].comparison != 'ignore':
|
||||
self.module.warn('The ignore_image option has been overridden by the comparisons option!')
|
||||
if self.param_purge_networks and self.all_options['networks'].comparison != 'strict':
|
||||
self.module.warn('The purge_networks option has been overridden by the comparisons option!')
|
||||
|
||||
def _update_params(self):
|
||||
if self.param_networks_cli_compatible is True and self.module.params['networks'] and self.module.params['network_mode'] is None:
|
||||
# Same behavior as Docker CLI: if networks are specified, use the name of the first network as the value for network_mode
|
||||
# (assuming no explicit value is specified for network_mode)
|
||||
self.module.params['network_mode'] = self.module.params['networks'][0]['name']
|
||||
if self.param_container_default_behavior == 'compatibility':
|
||||
old_default_values = dict(
|
||||
auto_remove=False,
|
||||
detach=True,
|
||||
init=False,
|
||||
interactive=False,
|
||||
memory='0',
|
||||
paused=False,
|
||||
privileged=False,
|
||||
read_only=False,
|
||||
tty=False,
|
||||
)
|
||||
for param, value in old_default_values.items():
|
||||
if self.module.params[param] is None:
|
||||
self.module.params[param] = value
|
||||
|
||||
def fail(self, *args, **kwargs):
|
||||
self.client.fail(*args, **kwargs)
|
||||
|
||||
def run(self):
|
||||
if self.param_state in ('stopped', 'started', 'present'):
|
||||
self.present(self.param_state)
|
||||
elif self.param_state == 'absent':
|
||||
self.absent()
|
||||
|
||||
if not self.check_mode and not self.param_debug:
|
||||
self.results.pop('actions')
|
||||
|
||||
if self.module._diff or self.param_debug:
|
||||
self.diff['before'], self.diff['after'] = self.diff_tracker.get_before_after()
|
||||
self.results['diff'] = self.diff
|
||||
|
||||
if self.facts:
|
||||
self.results['container'] = self.facts
|
||||
|
||||
def wait_for_state(self, container_id, complete_states=None, wait_states=None, accept_removal=False, max_wait=None):
|
||||
delay = 1.0
|
||||
total_wait = 0
|
||||
while True:
|
||||
# Inspect container
|
||||
result = self.engine_driver.inspect_container_by_id(self.client, container_id)
|
||||
if result is None:
|
||||
if accept_removal:
|
||||
return
|
||||
msg = 'Encontered vanished container while waiting for container "{0}"'
|
||||
self.fail(msg.format(container_id))
|
||||
# Check container state
|
||||
state = result.get('State', {}).get('Status')
|
||||
if complete_states is not None and state in complete_states:
|
||||
return
|
||||
if wait_states is not None and state not in wait_states:
|
||||
msg = 'Encontered unexpected state "{1}" while waiting for container "{0}"'
|
||||
self.fail(msg.format(container_id, state))
|
||||
# Wait
|
||||
if max_wait is not None:
|
||||
if total_wait > max_wait:
|
||||
msg = 'Timeout of {1} seconds exceeded while waiting for container "{0}"'
|
||||
self.fail(msg.format(container_id, max_wait))
|
||||
if total_wait + delay > max_wait:
|
||||
delay = max_wait - total_wait
|
||||
sleep(delay)
|
||||
total_wait += delay
|
||||
# Exponential backoff, but never wait longer than 10 seconds
|
||||
# (1.1**24 < 10, 1.1**25 > 10, so it will take 25 iterations
|
||||
# until the maximal 10 seconds delay is reached. By then, the
|
||||
# code will have slept for ~1.5 minutes.)
|
||||
delay = min(delay * 1.1, 10)
|
||||
|
||||
def _collect_params(self, active_options):
|
||||
parameters = []
|
||||
for options in active_options:
|
||||
values = {}
|
||||
engine = options.get_engine(self.engine_driver.name)
|
||||
for option in options.options:
|
||||
if not option.not_an_ansible_option and self.module.params[option.name] is not None:
|
||||
values[option.name] = self.module.params[option.name]
|
||||
values = options.preprocess(self.module, values)
|
||||
engine.preprocess_value(self.module, self.client, self.engine_driver.get_api_version(self.client), options.options, values)
|
||||
parameters.append((options, values))
|
||||
return parameters
|
||||
|
||||
def present(self, state):
|
||||
self.parameters = self._collect_params(self.options)
|
||||
container = self._get_container(self.param_name)
|
||||
was_running = container.running
|
||||
was_paused = container.paused
|
||||
container_created = False
|
||||
|
||||
# If the image parameter was passed then we need to deal with the image
|
||||
# version comparison. Otherwise we handle this depending on whether
|
||||
# the container already runs or not; in the former case, in case the
|
||||
# container needs to be restarted, we use the existing container's
|
||||
# image ID.
|
||||
image = self._get_image()
|
||||
self.log(image, pretty_print=True)
|
||||
if not container.exists or container.removing:
|
||||
# New container
|
||||
if container.removing:
|
||||
self.log('Found container in removal phase')
|
||||
else:
|
||||
self.log('No container found')
|
||||
if not self.param_image:
|
||||
self.fail('Cannot create container when image is not specified!')
|
||||
self.diff_tracker.add('exists', parameter=True, active=False)
|
||||
if container.removing and not self.check_mode:
|
||||
# Wait for container to be removed before trying to create it
|
||||
self.wait_for_state(
|
||||
container.id, wait_states=['removing'], accept_removal=True, max_wait=self.param_removal_wait_timeout)
|
||||
new_container = self.container_create(self.param_image)
|
||||
if new_container:
|
||||
container = new_container
|
||||
container_created = True
|
||||
else:
|
||||
# Existing container
|
||||
different, differences = self.has_different_configuration(container, image)
|
||||
image_different = False
|
||||
if self.all_options['image'].comparison == 'strict':
|
||||
image_different = self._image_is_different(image, container)
|
||||
if image_different or different or self.param_recreate:
|
||||
self.diff_tracker.merge(differences)
|
||||
self.diff['differences'] = differences.get_legacy_docker_container_diffs()
|
||||
if image_different:
|
||||
self.diff['image_different'] = True
|
||||
self.log("differences")
|
||||
self.log(differences.get_legacy_docker_container_diffs(), pretty_print=True)
|
||||
image_to_use = self.param_image
|
||||
if not image_to_use and container and container.image:
|
||||
image_to_use = container.image
|
||||
if not image_to_use:
|
||||
self.fail('Cannot recreate container when image is not specified or cannot be extracted from current container!')
|
||||
if container.running:
|
||||
self.container_stop(container.id)
|
||||
self.container_remove(container.id)
|
||||
if not self.check_mode:
|
||||
self.wait_for_state(
|
||||
container.id, wait_states=['removing'], accept_removal=True, max_wait=self.param_removal_wait_timeout)
|
||||
new_container = self.container_create(image_to_use)
|
||||
if new_container:
|
||||
container = new_container
|
||||
container_created = True
|
||||
|
||||
if container and container.exists:
|
||||
container = self.update_limits(container, image)
|
||||
container = self.update_networks(container, container_created)
|
||||
|
||||
if state == 'started' and not container.running:
|
||||
self.diff_tracker.add('running', parameter=True, active=was_running)
|
||||
container = self.container_start(container.id)
|
||||
elif state == 'started' and self.param_restart:
|
||||
self.diff_tracker.add('running', parameter=True, active=was_running)
|
||||
self.diff_tracker.add('restarted', parameter=True, active=False)
|
||||
container = self.container_restart(container.id)
|
||||
elif state == 'stopped' and container.running:
|
||||
self.diff_tracker.add('running', parameter=False, active=was_running)
|
||||
self.container_stop(container.id)
|
||||
container = self._get_container(container.id)
|
||||
|
||||
if state == 'started' and self.param_paused is not None and container.paused != self.param_paused:
|
||||
self.diff_tracker.add('paused', parameter=self.param_paused, active=was_paused)
|
||||
if not self.check_mode:
|
||||
try:
|
||||
if self.param_paused:
|
||||
self.engine_driver.pause_container(self.client, container.id)
|
||||
else:
|
||||
self.engine_driver.unpause_container(self.client, container.id)
|
||||
except Exception as exc:
|
||||
self.fail("Error %s container %s: %s" % (
|
||||
"pausing" if self.param_paused else "unpausing", container.id, to_native(exc)
|
||||
))
|
||||
container = self._get_container(container.id)
|
||||
self.results['changed'] = True
|
||||
self.results['actions'].append(dict(set_paused=self.param_paused))
|
||||
|
||||
self.facts = container.raw
|
||||
|
||||
def absent(self):
|
||||
container = self._get_container(self.param_name)
|
||||
if container.exists:
|
||||
if container.running:
|
||||
self.diff_tracker.add('running', parameter=False, active=True)
|
||||
self.container_stop(container.id)
|
||||
self.diff_tracker.add('exists', parameter=False, active=True)
|
||||
self.container_remove(container.id)
|
||||
|
||||
def _output_logs(self, msg):
|
||||
self.module.log(msg=msg)
|
||||
|
||||
def _get_container(self, container):
|
||||
'''
|
||||
Expects container ID or Name. Returns a container object
|
||||
'''
|
||||
container = self.engine_driver.inspect_container_by_name(self.client, container)
|
||||
return Container(container, self.engine_driver)
|
||||
|
||||
def _get_image(self):
|
||||
image_parameter = self.param_image
|
||||
if not image_parameter:
|
||||
self.log('No image specified')
|
||||
return None
|
||||
if is_image_name_id(image_parameter):
|
||||
image = self.engine_driver.inspect_image_by_id(self.client, image_parameter)
|
||||
else:
|
||||
repository, tag = parse_repository_tag(image_parameter)
|
||||
if not tag:
|
||||
tag = "latest"
|
||||
image = self.engine_driver.inspect_image_by_name(self.client, repository, tag)
|
||||
if not image or self.param_pull:
|
||||
if not self.check_mode:
|
||||
self.log("Pull the image.")
|
||||
image, alreadyToLatest = self.engine_driver.pull_image(self.client, repository, tag)
|
||||
if alreadyToLatest:
|
||||
self.results['changed'] = False
|
||||
else:
|
||||
self.results['changed'] = True
|
||||
self.results['actions'].append(dict(pulled_image="%s:%s" % (repository, tag)))
|
||||
elif not image:
|
||||
# If the image isn't there, claim we'll pull.
|
||||
# (Implicitly: if the image is there, claim it already was latest.)
|
||||
self.results['changed'] = True
|
||||
self.results['actions'].append(dict(pulled_image="%s:%s" % (repository, tag)))
|
||||
|
||||
self.log("image")
|
||||
self.log(image, pretty_print=True)
|
||||
return image
|
||||
|
||||
def _image_is_different(self, image, container):
|
||||
if image and image.get('Id'):
|
||||
if container and container.image:
|
||||
if image.get('Id') != container.image:
|
||||
self.diff_tracker.add('image', parameter=image.get('Id'), active=container.image)
|
||||
return True
|
||||
return False
|
||||
|
||||
def _compose_create_parameters(self, image):
|
||||
params = {}
|
||||
for options, values in self.parameters:
|
||||
engine = options.get_engine(self.engine_driver.name)
|
||||
if engine.can_set_value(self.engine_driver.get_api_version(self.client)):
|
||||
engine.set_value(self.module, params, self.engine_driver.get_api_version(self.client), options.options, values)
|
||||
params['Image'] = image
|
||||
return params
|
||||
|
||||
def _record_differences(self, differences, options, param_values, engine, container, image):
|
||||
container_values = engine.get_value(self.module, container.raw, self.engine_driver.get_api_version(self.client), options.options)
|
||||
expected_values = engine.get_expected_values(
|
||||
self.module, self.client, self.engine_driver.get_api_version(self.client), options.options, image, param_values.copy())
|
||||
for option in options.options:
|
||||
if option.name in expected_values:
|
||||
param_value = expected_values[option.name]
|
||||
container_value = container_values.get(option.name)
|
||||
match = compare_generic(param_value, container_value, option.comparison, option.comparison_type)
|
||||
|
||||
if not match:
|
||||
# No match.
|
||||
if engine.ignore_mismatching_result(self.module, self.client, self.engine_driver.get_api_version(self.client),
|
||||
option, image, container_value, param_value):
|
||||
# Ignore the result
|
||||
continue
|
||||
|
||||
# Record the differences
|
||||
p = param_value
|
||||
c = container_value
|
||||
if option.comparison_type == 'set':
|
||||
# Since the order does not matter, sort so that the diff output is better.
|
||||
if p is not None:
|
||||
p = sorted(p)
|
||||
if c is not None:
|
||||
c = sorted(c)
|
||||
elif option.comparison_type == 'set(dict)':
|
||||
# Since the order does not matter, sort so that the diff output is better.
|
||||
if option.name == 'expected_mounts':
|
||||
# For selected values, use one entry as key
|
||||
def sort_key_fn(x):
|
||||
return x['target']
|
||||
else:
|
||||
# We sort the list of dictionaries by using the sorted items of a dict as its key.
|
||||
def sort_key_fn(x):
|
||||
return sorted((a, to_text(b, errors='surrogate_or_strict')) for a, b in x.items())
|
||||
if p is not None:
|
||||
p = sorted(p, key=sort_key_fn)
|
||||
if c is not None:
|
||||
c = sorted(c, key=sort_key_fn)
|
||||
differences.add(option.name, parameter=p, active=c)
|
||||
|
||||
def has_different_configuration(self, container, image):
|
||||
differences = DifferenceTracker()
|
||||
update_differences = DifferenceTracker()
|
||||
for options, param_values in self.parameters:
|
||||
engine = options.get_engine(self.engine_driver.name)
|
||||
if engine.can_update_value(self.engine_driver.get_api_version(self.client)):
|
||||
self._record_differences(update_differences, options, param_values, engine, container, image)
|
||||
else:
|
||||
self._record_differences(differences, options, param_values, engine, container, image)
|
||||
has_differences = not differences.empty
|
||||
# Only consider differences of properties that can be updated when there are also other differences
|
||||
if has_differences:
|
||||
differences.merge(update_differences)
|
||||
return has_differences, differences
|
||||
|
||||
def has_different_resource_limits(self, container, image):
|
||||
differences = DifferenceTracker()
|
||||
for options, param_values in self.parameters:
|
||||
engine = options.get_engine(self.engine_driver.name)
|
||||
if not engine.can_update_value(self.engine_driver.get_api_version(self.client)):
|
||||
continue
|
||||
self._record_differences(differences, options, param_values, engine, container, image)
|
||||
has_differences = not differences.empty
|
||||
return has_differences, differences
|
||||
|
||||
def _compose_update_parameters(self):
|
||||
result = {}
|
||||
for options, values in self.parameters:
|
||||
engine = options.get_engine(self.engine_driver.name)
|
||||
if not engine.can_update_value(self.engine_driver.get_api_version(self.client)):
|
||||
continue
|
||||
engine.update_value(self.module, result, self.engine_driver.get_api_version(self.client), options.options, values)
|
||||
return result
|
||||
|
||||
def update_limits(self, container, image):
|
||||
limits_differ, different_limits = self.has_different_resource_limits(container, image)
|
||||
if limits_differ:
|
||||
self.log("limit differences:")
|
||||
self.log(different_limits.get_legacy_docker_container_diffs(), pretty_print=True)
|
||||
self.diff_tracker.merge(different_limits)
|
||||
if limits_differ and not self.check_mode:
|
||||
self.container_update(container.id, self._compose_update_parameters())
|
||||
return self._get_container(container.id)
|
||||
return container
|
||||
|
||||
def has_network_differences(self, container):
|
||||
'''
|
||||
Check if the container is connected to requested networks with expected options: links, aliases, ipv4, ipv6
|
||||
'''
|
||||
different = False
|
||||
differences = []
|
||||
|
||||
if not self.module.params['networks']:
|
||||
return different, differences
|
||||
|
||||
if not container.container.get('NetworkSettings'):
|
||||
self.fail("has_missing_networks: Error parsing container properties. NetworkSettings missing.")
|
||||
|
||||
connected_networks = container.container['NetworkSettings']['Networks']
|
||||
for network in self.module.params['networks']:
|
||||
network_info = connected_networks.get(network['name'])
|
||||
if network_info is None:
|
||||
different = True
|
||||
differences.append(dict(
|
||||
parameter=network,
|
||||
container=None
|
||||
))
|
||||
else:
|
||||
diff = False
|
||||
network_info_ipam = network_info.get('IPAMConfig') or {}
|
||||
if network.get('ipv4_address') and network['ipv4_address'] != network_info_ipam.get('IPv4Address'):
|
||||
diff = True
|
||||
if network.get('ipv6_address') and network['ipv6_address'] != network_info_ipam.get('IPv6Address'):
|
||||
diff = True
|
||||
if network.get('aliases'):
|
||||
if not compare_generic(network['aliases'], network_info.get('Aliases'), 'allow_more_present', 'set'):
|
||||
diff = True
|
||||
if network.get('links'):
|
||||
expected_links = []
|
||||
for link, alias in network['links']:
|
||||
expected_links.append("%s:%s" % (link, alias))
|
||||
if not compare_generic(expected_links, network_info.get('Links'), 'allow_more_present', 'set'):
|
||||
diff = True
|
||||
if diff:
|
||||
different = True
|
||||
differences.append(dict(
|
||||
parameter=network,
|
||||
container=dict(
|
||||
name=network['name'],
|
||||
ipv4_address=network_info_ipam.get('IPv4Address'),
|
||||
ipv6_address=network_info_ipam.get('IPv6Address'),
|
||||
aliases=network_info.get('Aliases'),
|
||||
links=network_info.get('Links')
|
||||
)
|
||||
))
|
||||
return different, differences
|
||||
|
||||
def has_extra_networks(self, container):
|
||||
'''
|
||||
Check if the container is connected to non-requested networks
|
||||
'''
|
||||
extra_networks = []
|
||||
extra = False
|
||||
|
||||
if not container.container.get('NetworkSettings'):
|
||||
self.fail("has_extra_networks: Error parsing container properties. NetworkSettings missing.")
|
||||
|
||||
connected_networks = container.container['NetworkSettings'].get('Networks')
|
||||
if connected_networks:
|
||||
for network, network_config in connected_networks.items():
|
||||
keep = False
|
||||
if self.module.params['networks']:
|
||||
for expected_network in self.module.params['networks']:
|
||||
if expected_network['name'] == network:
|
||||
keep = True
|
||||
if not keep:
|
||||
extra = True
|
||||
extra_networks.append(dict(name=network, id=network_config['NetworkID']))
|
||||
return extra, extra_networks
|
||||
|
||||
def update_networks(self, container, container_created):
|
||||
updated_container = container
|
||||
if self.all_options['networks'].comparison != 'ignore' or container_created:
|
||||
has_network_differences, network_differences = self.has_network_differences(container)
|
||||
if has_network_differences:
|
||||
if self.diff.get('differences'):
|
||||
self.diff['differences'].append(dict(network_differences=network_differences))
|
||||
else:
|
||||
self.diff['differences'] = [dict(network_differences=network_differences)]
|
||||
for netdiff in network_differences:
|
||||
self.diff_tracker.add(
|
||||
'network.{0}'.format(netdiff['parameter']['name']),
|
||||
parameter=netdiff['parameter'],
|
||||
active=netdiff['container']
|
||||
)
|
||||
self.results['changed'] = True
|
||||
updated_container = self._add_networks(container, network_differences)
|
||||
|
||||
if (self.all_options['networks'].comparison == 'strict' and self.module.params['networks'] is not None) or self.param_purge_networks:
|
||||
has_extra_networks, extra_networks = self.has_extra_networks(container)
|
||||
if has_extra_networks:
|
||||
if self.diff.get('differences'):
|
||||
self.diff['differences'].append(dict(purge_networks=extra_networks))
|
||||
else:
|
||||
self.diff['differences'] = [dict(purge_networks=extra_networks)]
|
||||
for extra_network in extra_networks:
|
||||
self.diff_tracker.add(
|
||||
'network.{0}'.format(extra_network['name']),
|
||||
active=extra_network
|
||||
)
|
||||
self.results['changed'] = True
|
||||
updated_container = self._purge_networks(container, extra_networks)
|
||||
return updated_container
|
||||
|
||||
def _add_networks(self, container, differences):
|
||||
for diff in differences:
|
||||
# remove the container from the network, if connected
|
||||
if diff.get('container'):
|
||||
self.results['actions'].append(dict(removed_from_network=diff['parameter']['name']))
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.disconnect_container_from_network(self.client, container.id, diff['parameter']['id'])
|
||||
except Exception as exc:
|
||||
self.fail("Error disconnecting container from network %s - %s" % (diff['parameter']['name'],
|
||||
to_native(exc)))
|
||||
# connect to the network
|
||||
self.results['actions'].append(dict(added_to_network=diff['parameter']['name'], network_parameters=diff['parameter']))
|
||||
if not self.check_mode:
|
||||
params = {key: value for key, value in diff['parameter'].items() if key not in ('id', 'name')}
|
||||
try:
|
||||
self.log("Connecting container to network %s" % diff['parameter']['id'])
|
||||
self.log(params, pretty_print=True)
|
||||
self.engine_driver.connect_container_to_network(self.client, container.id, diff['parameter']['id'], params)
|
||||
except Exception as exc:
|
||||
self.fail("Error connecting container to network %s - %s" % (diff['parameter']['name'], to_native(exc)))
|
||||
return self._get_container(container.id)
|
||||
|
||||
def _purge_networks(self, container, networks):
|
||||
for network in networks:
|
||||
self.results['actions'].append(dict(removed_from_network=network['name']))
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.disconnect_container_from_network(self.client, container.id, network['name'])
|
||||
except Exception as exc:
|
||||
self.fail("Error disconnecting container from network %s - %s" % (network['name'],
|
||||
to_native(exc)))
|
||||
return self._get_container(container.id)
|
||||
|
||||
def container_create(self, image):
|
||||
create_parameters = self._compose_create_parameters(image)
|
||||
self.log("create container")
|
||||
self.log("image: %s parameters:" % image)
|
||||
self.log(create_parameters, pretty_print=True)
|
||||
self.results['actions'].append(dict(created="Created container", create_parameters=create_parameters))
|
||||
self.results['changed'] = True
|
||||
new_container = None
|
||||
if not self.check_mode:
|
||||
try:
|
||||
container_id = self.engine_driver.create_container(self.client, self.param_name, create_parameters)
|
||||
except Exception as exc:
|
||||
self.fail("Error creating container: %s" % to_native(exc))
|
||||
return self._get_container(container_id)
|
||||
return new_container
|
||||
|
||||
def container_start(self, container_id):
|
||||
self.log("start container %s" % (container_id))
|
||||
self.results['actions'].append(dict(started=container_id))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.start_container(self.client, container_id)
|
||||
except Exception as exc:
|
||||
self.fail("Error starting container %s: %s" % (container_id, to_native(exc)))
|
||||
|
||||
if self.module.params['detach'] is False:
|
||||
status = self.engine_driver.wait_for_container(self.client, container_id)
|
||||
self.client.fail_results['status'] = status
|
||||
self.results['status'] = status
|
||||
|
||||
if self.module.params['auto_remove']:
|
||||
output = "Cannot retrieve result as auto_remove is enabled"
|
||||
if self.param_output_logs:
|
||||
self.module.warn('Cannot output_logs if auto_remove is enabled!')
|
||||
else:
|
||||
output, real_output = self.engine_driver.get_container_output(self.client, container_id)
|
||||
if real_output and self.param_output_logs:
|
||||
self._output_logs(msg=output)
|
||||
|
||||
if self.param_cleanup:
|
||||
self.container_remove(container_id, force=True)
|
||||
insp = self._get_container(container_id)
|
||||
if insp.raw:
|
||||
insp.raw['Output'] = output
|
||||
else:
|
||||
insp.raw = dict(Output=output)
|
||||
if status != 0:
|
||||
# Set `failed` to True and return output as msg
|
||||
self.results['failed'] = True
|
||||
self.results['msg'] = output
|
||||
return insp
|
||||
return self._get_container(container_id)
|
||||
|
||||
def container_remove(self, container_id, link=False, force=False):
|
||||
volume_state = (not self.param_keep_volumes)
|
||||
self.log("remove container container:%s v:%s link:%s force%s" % (container_id, volume_state, link, force))
|
||||
self.results['actions'].append(dict(removed=container_id, volume_state=volume_state, link=link, force=force))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.remove_container(self.client, container_id, remove_volumes=volume_state, link=link, force=force)
|
||||
except Exception as exc:
|
||||
self.client.fail("Error removing container %s: %s" % (container_id, to_native(exc)))
|
||||
|
||||
def container_update(self, container_id, update_parameters):
|
||||
if update_parameters:
|
||||
self.log("update container %s" % (container_id))
|
||||
self.log(update_parameters, pretty_print=True)
|
||||
self.results['actions'].append(dict(updated=container_id, update_parameters=update_parameters))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.update_container(self.client, container_id, update_parameters)
|
||||
except Exception as exc:
|
||||
self.fail("Error updating container %s: %s" % (container_id, to_native(exc)))
|
||||
return self._get_container(container_id)
|
||||
|
||||
def container_kill(self, container_id):
|
||||
self.results['actions'].append(dict(killed=container_id, signal=self.param_kill_signal))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.kill_container(self.client, container_id, kill_signal=self.param_kill_signal)
|
||||
except Exception as exc:
|
||||
self.fail("Error killing container %s: %s" % (container_id, to_native(exc)))
|
||||
|
||||
def container_restart(self, container_id):
|
||||
self.results['actions'].append(dict(restarted=container_id, timeout=self.module.params['stop_timeout']))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.restart_container(self.client, container_id, self.module.params['stop_timeout'] or 10)
|
||||
except Exception as exc:
|
||||
self.fail("Error restarting container %s: %s" % (container_id, to_native(exc)))
|
||||
return self._get_container(container_id)
|
||||
|
||||
def container_stop(self, container_id):
|
||||
if self.param_force_kill:
|
||||
self.container_kill(container_id)
|
||||
return
|
||||
self.results['actions'].append(dict(stopped=container_id, timeout=self.module.params['stop_timeout']))
|
||||
self.results['changed'] = True
|
||||
if not self.check_mode:
|
||||
try:
|
||||
self.engine_driver.stop_container(self.client, container_id, self.module.params['stop_timeout'])
|
||||
except Exception as exc:
|
||||
self.fail("Error stopping container %s: %s" % (container_id, to_native(exc)))
|
||||
|
||||
|
||||
def run_module(engine_driver):
|
||||
module, active_options, client = engine_driver.setup(
|
||||
argument_spec=dict(
|
||||
cleanup=dict(type='bool', default=False),
|
||||
comparisons=dict(type='dict'),
|
||||
container_default_behavior=dict(type='str', default='no_defaults', choices=['compatibility', 'no_defaults']),
|
||||
command_handling=dict(type='str', choices=['compatibility', 'correct'], default='correct'),
|
||||
default_host_ip=dict(type='str'),
|
||||
force_kill=dict(type='bool', default=False, aliases=['forcekill']),
|
||||
ignore_image=dict(type='bool', default=False),
|
||||
image=dict(type='str'),
|
||||
image_label_mismatch=dict(type='str', choices=['ignore', 'fail'], default='ignore'),
|
||||
keep_volumes=dict(type='bool', default=True),
|
||||
kill_signal=dict(type='str'),
|
||||
name=dict(type='str', required=True),
|
||||
networks_cli_compatible=dict(type='bool', default=True),
|
||||
output_logs=dict(type='bool', default=False),
|
||||
paused=dict(type='bool'),
|
||||
pull=dict(type='bool', default=False),
|
||||
purge_networks=dict(type='bool', default=False),
|
||||
recreate=dict(type='bool', default=False),
|
||||
removal_wait_timeout=dict(type='float'),
|
||||
restart=dict(type='bool', default=False),
|
||||
state=dict(type='str', default='started', choices=['absent', 'present', 'started', 'stopped']),
|
||||
),
|
||||
required_if=[
|
||||
('state', 'present', ['image'])
|
||||
],
|
||||
)
|
||||
|
||||
def execute():
|
||||
cm = ContainerManager(module, engine_driver, client, active_options)
|
||||
cm.run()
|
||||
module.exit_json(**sanitize_result(cm.results))
|
||||
|
||||
engine_driver.run(execute, client)
|
||||
@ -331,6 +331,49 @@ def convert_duration_to_nanosecond(time_str):
|
||||
return time_in_nanoseconds
|
||||
|
||||
|
||||
def normalize_healthcheck_test(test):
|
||||
if isinstance(test, (tuple, list)):
|
||||
return [str(e) for e in test]
|
||||
return ['CMD-SHELL', str(test)]
|
||||
|
||||
|
||||
def normalize_healthcheck(healthcheck, normalize_test=False):
|
||||
"""
|
||||
Return dictionary of healthcheck parameters.
|
||||
"""
|
||||
result = dict()
|
||||
|
||||
# All supported healthcheck parameters
|
||||
options = ('test', 'interval', 'timeout', 'start_period', 'retries')
|
||||
|
||||
duration_options = ('interval', 'timeout', 'start_period')
|
||||
|
||||
for key in options:
|
||||
if key in healthcheck:
|
||||
value = healthcheck[key]
|
||||
if value is None:
|
||||
# due to recursive argument_spec, all keys are always present
|
||||
# (but have default value None if not specified)
|
||||
continue
|
||||
if key in duration_options:
|
||||
value = convert_duration_to_nanosecond(value)
|
||||
if not value:
|
||||
continue
|
||||
if key == 'retries':
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Cannot parse number of retries for healthcheck. '
|
||||
'Expected an integer, got "{0}".'.format(value)
|
||||
)
|
||||
if key == 'test' and normalize_test:
|
||||
value = normalize_healthcheck_test(value)
|
||||
result[key] = value
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def parse_healthcheck(healthcheck):
|
||||
"""
|
||||
Return dictionary of healthcheck parameters and boolean if
|
||||
@ -339,44 +382,7 @@ def parse_healthcheck(healthcheck):
|
||||
if (not healthcheck) or (not healthcheck.get('test')):
|
||||
return None, None
|
||||
|
||||
result = dict()
|
||||
|
||||
# All supported healthcheck parameters
|
||||
options = dict(
|
||||
test='test',
|
||||
interval='interval',
|
||||
timeout='timeout',
|
||||
start_period='start_period',
|
||||
retries='retries'
|
||||
)
|
||||
|
||||
duration_options = ['interval', 'timeout', 'start_period']
|
||||
|
||||
for (key, value) in options.items():
|
||||
if value in healthcheck:
|
||||
if healthcheck.get(value) is None:
|
||||
# due to recursive argument_spec, all keys are always present
|
||||
# (but have default value None if not specified)
|
||||
continue
|
||||
if value in duration_options:
|
||||
time = convert_duration_to_nanosecond(healthcheck.get(value))
|
||||
if time:
|
||||
result[key] = time
|
||||
elif healthcheck.get(value):
|
||||
result[key] = healthcheck.get(value)
|
||||
if key == 'test':
|
||||
if isinstance(result[key], (tuple, list)):
|
||||
result[key] = [str(e) for e in result[key]]
|
||||
else:
|
||||
result[key] = ['CMD-SHELL', str(result[key])]
|
||||
elif key == 'retries':
|
||||
try:
|
||||
result[key] = int(result[key])
|
||||
except ValueError:
|
||||
raise ValueError(
|
||||
'Cannot parse number of retries for healthcheck. '
|
||||
'Expected an integer, got "{0}".'.format(result[key])
|
||||
)
|
||||
result = normalize_healthcheck(healthcheck, normalize_test=True)
|
||||
|
||||
if result['test'] == ['NONE']:
|
||||
# If the user explicitly disables the healthcheck, return None
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -53,10 +53,9 @@
|
||||
state: absent
|
||||
force: yes
|
||||
with_items: "{{ dnetworks }}"
|
||||
when: docker_py_version is version('1.10.0', '>=')
|
||||
diff: no
|
||||
|
||||
when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.25', '>=')
|
||||
when: docker_api_version is version('1.25', '>=')
|
||||
|
||||
- fail: msg="Too old docker / docker-py version to run all docker_container tests!"
|
||||
when: not(docker_py_version is version('3.5.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
|
||||
when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
|
||||
|
||||
@ -33,7 +33,6 @@
|
||||
type: bind
|
||||
read_only: no
|
||||
register: mounts_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (idempotency)
|
||||
docker_container:
|
||||
@ -50,7 +49,6 @@
|
||||
target: /tmp
|
||||
type: bind
|
||||
register: mounts_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (less mounts)
|
||||
docker_container:
|
||||
@ -63,7 +61,6 @@
|
||||
target: /tmp
|
||||
type: bind
|
||||
register: mounts_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (more mounts)
|
||||
docker_container:
|
||||
@ -81,7 +78,6 @@
|
||||
read_only: yes
|
||||
force_kill: yes
|
||||
register: mounts_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (different modes)
|
||||
docker_container:
|
||||
@ -99,7 +95,6 @@
|
||||
read_only: no
|
||||
force_kill: yes
|
||||
register: mounts_5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (endpoint collision)
|
||||
docker_container:
|
||||
@ -161,13 +156,6 @@
|
||||
- "'The mount point \"/x\" appears twice in the mounts option' == mounts_6.msg"
|
||||
- mounts_7 is changed
|
||||
- mounts_8 is not changed
|
||||
when: docker_py_version is version('2.6.0', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- mounts_1 is failed
|
||||
- "('version is ' ~ docker_py_version ~ ' ') in mounts_1.msg"
|
||||
- "'Minimum version required is 2.6.0 ' in mounts_1.msg"
|
||||
when: docker_py_version is version('2.6.0', '<')
|
||||
|
||||
####################################################################
|
||||
## mounts + volumes ################################################
|
||||
@ -187,7 +175,6 @@
|
||||
volumes:
|
||||
- /tmp:/tmp
|
||||
register: mounts_volumes_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (idempotency)
|
||||
docker_container:
|
||||
@ -203,7 +190,6 @@
|
||||
volumes:
|
||||
- /tmp:/tmp
|
||||
register: mounts_volumes_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (switching)
|
||||
docker_container:
|
||||
@ -220,7 +206,6 @@
|
||||
- /:/whatever:ro
|
||||
force_kill: yes
|
||||
register: mounts_volumes_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (collision, should fail)
|
||||
docker_container:
|
||||
@ -253,13 +238,6 @@
|
||||
- mounts_volumes_3 is changed
|
||||
- mounts_volumes_4 is failed
|
||||
- "'The mount point \"/tmp\" appears both in the volumes and mounts option' in mounts_volumes_4.msg"
|
||||
when: docker_py_version is version('2.6.0', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- mounts_volumes_1 is failed
|
||||
- "('version is ' ~ docker_py_version ~ ' ') in mounts_1.msg"
|
||||
- "'Minimum version required is 2.6.0 ' in mounts_1.msg"
|
||||
when: docker_py_version is version('2.6.0', '<')
|
||||
|
||||
####################################################################
|
||||
## volume_driver ###################################################
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -21,7 +21,6 @@
|
||||
state: started
|
||||
auto_remove: yes
|
||||
register: auto_remove_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: Give container 1 second to be sure it terminated
|
||||
pause:
|
||||
@ -32,19 +31,11 @@
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
register: auto_remove_2
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- auto_remove_1 is 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 ~ ' ') in auto_remove_1.msg"
|
||||
- "'Minimum version required is 2.1.0 ' in auto_remove_1.msg"
|
||||
when: docker_py_version is version('2.1.0', '<')
|
||||
|
||||
####################################################################
|
||||
## blkio_weight ####################################################
|
||||
@ -573,7 +564,6 @@
|
||||
name: "{{ cname }}"
|
||||
cpus: 1
|
||||
state: started
|
||||
ignore_errors: yes
|
||||
register: cpus_1
|
||||
|
||||
- name: cpus (idempotency)
|
||||
@ -583,7 +573,6 @@
|
||||
name: "{{ cname }}"
|
||||
cpus: 1
|
||||
state: started
|
||||
ignore_errors: yes
|
||||
register: cpus_2
|
||||
|
||||
- name: cpus (change)
|
||||
@ -596,7 +585,6 @@
|
||||
force_kill: yes
|
||||
# This will fail if the system the test is run on doesn't have
|
||||
# multiple MEMs available.
|
||||
ignore_errors: yes
|
||||
register: cpus_3
|
||||
|
||||
- name: cleanup
|
||||
@ -611,13 +599,6 @@
|
||||
- cpus_1 is changed
|
||||
- cpus_2 is not changed and cpus_2 is not failed
|
||||
- cpus_3 is failed or cpus_3 is changed
|
||||
when: docker_py_version is version('2.3.0', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- cpus_1 is failed
|
||||
- "('version is ' ~ docker_py_version ~ ' ') in cpus_1.msg"
|
||||
- "'Minimum version required is 2.3.0 ' in cpus_1.msg"
|
||||
when: docker_py_version is version('2.3.0', '<')
|
||||
|
||||
####################################################################
|
||||
## debug ###########################################################
|
||||
@ -741,11 +722,8 @@
|
||||
- detach_cleanup_nonzero.status == 42
|
||||
- "'Output' in detach_cleanup_nonzero.container"
|
||||
- "detach_cleanup_nonzero.container.Output == ''"
|
||||
- assert:
|
||||
that:
|
||||
- "'Cannot retrieve result as auto_remove is enabled' == detach_auto_remove.container.Output"
|
||||
- detach_auto_remove_cleanup is not changed
|
||||
when: docker_py_version is version('2.1.0', '>=')
|
||||
|
||||
####################################################################
|
||||
## devices #########################################################
|
||||
@ -825,7 +803,6 @@
|
||||
- path: /dev/urandom
|
||||
rate: 10K
|
||||
register: device_read_bps_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_bps (idempotency)
|
||||
docker_container:
|
||||
@ -839,7 +816,6 @@
|
||||
- path: /dev/random
|
||||
rate: 20M
|
||||
register: device_read_bps_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_bps (lesser entries)
|
||||
docker_container:
|
||||
@ -851,7 +827,6 @@
|
||||
- path: /dev/random
|
||||
rate: 20M
|
||||
register: device_read_bps_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_bps (changed)
|
||||
docker_container:
|
||||
@ -866,7 +841,6 @@
|
||||
rate: 5K
|
||||
force_kill: yes
|
||||
register: device_read_bps_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -881,13 +855,6 @@
|
||||
- device_read_bps_2 is not changed
|
||||
- device_read_bps_3 is not 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 ~ ' ') in device_read_bps_1.msg"
|
||||
- "'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 ################################################
|
||||
@ -905,7 +872,6 @@
|
||||
- path: /dev/urandom
|
||||
rate: 20
|
||||
register: device_read_iops_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_iops (idempotency)
|
||||
docker_container:
|
||||
@ -919,7 +885,6 @@
|
||||
- path: /dev/random
|
||||
rate: 10
|
||||
register: device_read_iops_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_iops (less)
|
||||
docker_container:
|
||||
@ -931,7 +896,6 @@
|
||||
- path: /dev/random
|
||||
rate: 10
|
||||
register: device_read_iops_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_read_iops (changed)
|
||||
docker_container:
|
||||
@ -946,7 +910,6 @@
|
||||
rate: 50
|
||||
force_kill: yes
|
||||
register: device_read_iops_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -961,13 +924,6 @@
|
||||
- device_read_iops_2 is not changed
|
||||
- device_read_iops_3 is not 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 ~ ' ') in device_read_iops_1.msg"
|
||||
- "'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 ##########################
|
||||
@ -986,7 +942,6 @@
|
||||
- path: /dev/urandom
|
||||
rate: 30
|
||||
register: device_write_limit_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_write_bps and device_write_iops (idempotency)
|
||||
docker_container:
|
||||
@ -1001,7 +956,6 @@
|
||||
- path: /dev/urandom
|
||||
rate: 30
|
||||
register: device_write_limit_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: device_write_bps device_write_iops (changed)
|
||||
docker_container:
|
||||
@ -1017,7 +971,6 @@
|
||||
rate: 100
|
||||
force_kill: yes
|
||||
register: device_write_limit_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -1031,13 +984,6 @@
|
||||
- device_write_limit_1 is changed
|
||||
- device_write_limit_2 is not 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 ~ ' ') in device_write_limit_1.msg"
|
||||
- "'Minimum version required is 1.9.0 ' in device_write_limit_1.msg"
|
||||
when: docker_py_version is version('1.9.0', '<')
|
||||
|
||||
####################################################################
|
||||
## device_requests #################################################
|
||||
@ -1074,14 +1020,13 @@
|
||||
that:
|
||||
- device_requests_1 is changed
|
||||
- device_requests_2 is not changed
|
||||
when: docker_py_version is version('4.3.0', '>=') and docker_api_version is version('1.40', '>=')
|
||||
when: docker_api_version is version('1.40', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- device_requests_1 is failed
|
||||
- |
|
||||
(('version is ' ~ docker_py_version ~ ' ') in device_requests_1.msg and 'Minimum version required is 4.3.0 ' in device_requests_1.msg) or
|
||||
(('API version is ' ~ docker_api_version ~ '.') in device_requests_1.msg and 'Minimum version required is 1.40 ' in device_requests_1.msg)
|
||||
when: docker_py_version is version('4.3.0', '<') or docker_api_version is version('1.40', '<')
|
||||
('API version is ' ~ docker_api_version ~ '.') in device_requests_1.msg and 'Minimum version required is 1.40 ' in device_requests_1.msg
|
||||
when: docker_api_version is version('1.40', '<')
|
||||
|
||||
####################################################################
|
||||
## dns_opts ########################################################
|
||||
@ -1097,7 +1042,6 @@
|
||||
- "timeout:10"
|
||||
- rotate
|
||||
register: dns_opts_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: dns_opts (idempotency)
|
||||
docker_container:
|
||||
@ -1109,7 +1053,6 @@
|
||||
- rotate
|
||||
- "timeout:10"
|
||||
register: dns_opts_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: dns_opts (less resolv.conf options)
|
||||
docker_container:
|
||||
@ -1120,7 +1063,6 @@
|
||||
dns_opts:
|
||||
- "timeout:10"
|
||||
register: dns_opts_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: dns_opts (more resolv.conf options)
|
||||
docker_container:
|
||||
@ -1133,7 +1075,6 @@
|
||||
- no-check-names
|
||||
force_kill: yes
|
||||
register: dns_opts_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -1148,13 +1089,6 @@
|
||||
- dns_opts_2 is not changed
|
||||
- dns_opts_3 is not 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 ~ ' ') in dns_opts_1.msg"
|
||||
- "'Minimum version required is 1.10.0 ' in dns_opts_1.msg"
|
||||
when: docker_py_version is version('1.10.0', '<')
|
||||
|
||||
####################################################################
|
||||
## dns_search_domains ##############################################
|
||||
@ -1854,7 +1788,6 @@
|
||||
retries: 2
|
||||
force_kill: yes
|
||||
register: healthcheck_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (idempotency)
|
||||
docker_container:
|
||||
@ -1872,7 +1805,6 @@
|
||||
retries: 2
|
||||
force_kill: yes
|
||||
register: healthcheck_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (changed)
|
||||
docker_container:
|
||||
@ -1890,7 +1822,6 @@
|
||||
retries: 3
|
||||
force_kill: yes
|
||||
register: healthcheck_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (no change)
|
||||
docker_container:
|
||||
@ -1900,7 +1831,6 @@
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: healthcheck_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (disabled)
|
||||
docker_container:
|
||||
@ -1913,7 +1843,6 @@
|
||||
- NONE
|
||||
force_kill: yes
|
||||
register: healthcheck_5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (disabled, idempotency)
|
||||
docker_container:
|
||||
@ -1926,7 +1855,6 @@
|
||||
- NONE
|
||||
force_kill: yes
|
||||
register: healthcheck_6
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (disabled, idempotency, strict)
|
||||
docker_container:
|
||||
@ -1941,7 +1869,6 @@
|
||||
comparisons:
|
||||
'*': strict
|
||||
register: healthcheck_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (string in healthcheck test, changed)
|
||||
docker_container:
|
||||
@ -1953,7 +1880,6 @@
|
||||
test: "sleep 1"
|
||||
force_kill: yes
|
||||
register: healthcheck_8
|
||||
ignore_errors: yes
|
||||
|
||||
- name: healthcheck (string in healthcheck test, idempotency)
|
||||
docker_container:
|
||||
@ -1965,7 +1891,6 @@
|
||||
test: "sleep 1"
|
||||
force_kill: yes
|
||||
register: healthcheck_9
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -1985,13 +1910,6 @@
|
||||
- healthcheck_7 is not changed
|
||||
- healthcheck_8 is changed
|
||||
- healthcheck_9 is not changed
|
||||
when: docker_py_version is version('2.0.0', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- healthcheck_1 is failed
|
||||
- "('version is ' ~ docker_py_version ~ ' ') in healthcheck_1.msg"
|
||||
- "'Minimum version required is 2.0.0 ' in healthcheck_1.msg"
|
||||
when: docker_py_version is version('2.0.0', '<')
|
||||
|
||||
####################################################################
|
||||
## hostname ########################################################
|
||||
@ -2050,7 +1968,6 @@
|
||||
init: yes
|
||||
state: started
|
||||
register: init_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: init (idempotency)
|
||||
docker_container:
|
||||
@ -2060,7 +1977,6 @@
|
||||
init: yes
|
||||
state: started
|
||||
register: init_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: init (change)
|
||||
docker_container:
|
||||
@ -2071,7 +1987,6 @@
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: init_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -2085,13 +2000,6 @@
|
||||
- init_1 is changed
|
||||
- init_2 is not 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 ~ ' ') in init_1.msg"
|
||||
- "'Minimum version required is 2.2.0 ' in init_1.msg"
|
||||
when: docker_py_version is version('2.2.0', '<')
|
||||
|
||||
####################################################################
|
||||
## interactive #####################################################
|
||||
@ -2462,7 +2370,6 @@
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
ignore_errors: yes
|
||||
|
||||
- assert:
|
||||
that:
|
||||
@ -3188,8 +3095,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
pid_mode: "container:{{ pid_mode_helper.container.Id }}"
|
||||
register: pid_mode_1
|
||||
ignore_errors: yes
|
||||
# docker-py < 2.0 does not support "arbitrary" pid_mode values
|
||||
|
||||
- name: pid_mode (idempotency)
|
||||
docker_container:
|
||||
@ -3199,8 +3104,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
pid_mode: "container:{{ cname_h1 }}"
|
||||
register: pid_mode_2
|
||||
ignore_errors: yes
|
||||
# docker-py < 2.0 does not support "arbitrary" pid_mode values
|
||||
|
||||
- name: pid_mode (change)
|
||||
docker_container:
|
||||
@ -3229,13 +3132,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
- pid_mode_1 is changed
|
||||
- pid_mode_2 is not 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', '<')
|
||||
|
||||
####################################################################
|
||||
## pids_limit ######################################################
|
||||
@ -3249,7 +3145,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
pids_limit: 10
|
||||
register: pids_limit_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: pids_limit (idempotency)
|
||||
docker_container:
|
||||
@ -3259,7 +3154,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
pids_limit: 10
|
||||
register: pids_limit_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: pids_limit (changed)
|
||||
docker_container:
|
||||
@ -3270,7 +3164,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
pids_limit: 20
|
||||
force_kill: yes
|
||||
register: pids_limit_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -3284,13 +3177,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
- pids_limit_1 is changed
|
||||
- pids_limit_2 is not changed
|
||||
- pids_limit_3 is changed
|
||||
when: docker_py_version is version('1.10.0', '>=')
|
||||
- assert:
|
||||
that:
|
||||
- pids_limit_1 is failed
|
||||
- "('version is ' ~ docker_py_version ~ ' ') in pids_limit_1.msg"
|
||||
- "'Minimum version required is 1.10.0 ' in pids_limit_1.msg"
|
||||
when: docker_py_version is version('1.10.0', '<')
|
||||
|
||||
####################################################################
|
||||
## privileged ######################################################
|
||||
@ -3648,7 +3534,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
runtime: runc
|
||||
state: started
|
||||
register: runtime_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: runtime (idempotency)
|
||||
docker_container:
|
||||
@ -3658,7 +3543,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
runtime: runc
|
||||
state: started
|
||||
register: runtime_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -3671,13 +3555,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
that:
|
||||
- runtime_1 is 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 ~ ' ') in runtime_1.msg"
|
||||
- "'Minimum version required is 2.4.0 ' in runtime_1.msg"
|
||||
when: docker_py_version is version('2.4.0', '<')
|
||||
|
||||
####################################################################
|
||||
## security_opts ###################################################
|
||||
@ -3975,7 +3852,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
net.ipv4.icmp_echo_ignore_all: 1
|
||||
net.ipv4.ip_forward: 1
|
||||
register: sysctls_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: sysctls (idempotency)
|
||||
docker_container:
|
||||
@ -3987,7 +3863,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
net.ipv4.ip_forward: 1
|
||||
net.ipv4.icmp_echo_ignore_all: 1
|
||||
register: sysctls_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: sysctls (less sysctls)
|
||||
docker_container:
|
||||
@ -3998,7 +3873,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
sysctls:
|
||||
net.ipv4.icmp_echo_ignore_all: 1
|
||||
register: sysctls_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: sysctls (more sysctls)
|
||||
docker_container:
|
||||
@ -4011,7 +3885,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
net.ipv6.conf.default.accept_redirects: 0
|
||||
force_kill: yes
|
||||
register: sysctls_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -4026,13 +3899,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
- sysctls_2 is not changed
|
||||
- sysctls_3 is not 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 ~ ' ') in sysctls_1.msg"
|
||||
- "'Minimum version required is 1.10.0 ' in sysctls_1.msg"
|
||||
when: docker_py_version is version('1.10.0', '<')
|
||||
|
||||
####################################################################
|
||||
## tmpfs ###########################################################
|
||||
@ -4260,7 +4126,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
userns_mode: host
|
||||
state: started
|
||||
register: userns_mode_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: userns_mode (idempotency)
|
||||
docker_container:
|
||||
@ -4270,7 +4135,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
userns_mode: host
|
||||
state: started
|
||||
register: userns_mode_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: userns_mode (change)
|
||||
docker_container:
|
||||
@ -4281,7 +4145,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: userns_mode_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -4295,13 +4158,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
- userns_mode_1 is changed
|
||||
- userns_mode_2 is not 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 ~ ' ') in userns_mode_1.msg"
|
||||
- "'Minimum version required is 1.10.0 ' in userns_mode_1.msg"
|
||||
when: docker_py_version is version('1.10.0', '<')
|
||||
|
||||
####################################################################
|
||||
## uts #############################################################
|
||||
@ -4315,7 +4171,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
uts: host
|
||||
state: started
|
||||
register: uts_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: uts (idempotency)
|
||||
docker_container:
|
||||
@ -4325,7 +4180,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
uts: host
|
||||
state: started
|
||||
register: uts_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: uts (change)
|
||||
docker_container:
|
||||
@ -4336,7 +4190,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: uts_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
@ -4350,13 +4203,6 @@ avoid such warnings, please quote the value.' in (log_options_2.warnings | defau
|
||||
- uts_1 is changed
|
||||
- uts_2 is not 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 ~ ' ') in uts_1.msg"
|
||||
- "'Minimum version required is 3.5.0 ' in uts_1.msg"
|
||||
when: docker_py_version is version('3.5.0', '<')
|
||||
|
||||
####################################################################
|
||||
## working_dir #####################################################
|
||||
|
||||
@ -0,0 +1,172 @@
|
||||
---
|
||||
- name: Registering container name
|
||||
set_fact:
|
||||
cname: "{{ cname_prefix ~ '-update' }}"
|
||||
- name: Registering container name
|
||||
set_fact:
|
||||
cnames: "{{ cnames + [cname] }}"
|
||||
|
||||
# We do not test cpuset_cpus and cpuset_mems since changing it fails if the system does
|
||||
# not have 'enough' CPUs. We do not test kernel_memory since it is deprecated and fails.
|
||||
|
||||
- name: Create container
|
||||
docker_container:
|
||||
image: "{{ docker_test_image_alpine }}"
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
blkio_weight: 123
|
||||
cpu_period: 90000
|
||||
cpu_quota: 150000
|
||||
cpu_shares: 900
|
||||
memory: 64M
|
||||
memory_reservation: 64M
|
||||
memory_swap: 64M
|
||||
restart_policy: on-failure
|
||||
restart_retries: 5
|
||||
register: create
|
||||
|
||||
- name: Update values
|
||||
docker_container:
|
||||
image: "{{ docker_test_image_alpine }}"
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
blkio_weight: 234
|
||||
cpu_period: 50000
|
||||
cpu_quota: 50000
|
||||
cpu_shares: 1100
|
||||
memory: 48M
|
||||
memory_reservation: 48M
|
||||
memory_swap: unlimited
|
||||
restart_policy: on-failure # only on-failure can have restart_retries, so don't change it here
|
||||
restart_retries: 2
|
||||
register: update
|
||||
diff: yes
|
||||
|
||||
- name: Update values again
|
||||
docker_container:
|
||||
image: "{{ docker_test_image_alpine }}"
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
blkio_weight: 135
|
||||
cpu_period: 30000
|
||||
cpu_quota: 40000
|
||||
cpu_shares: 1000
|
||||
memory: 32M
|
||||
memory_reservation: 30M
|
||||
memory_swap: 128M
|
||||
restart_policy: always
|
||||
restart_retries: 0
|
||||
register: update2
|
||||
diff: yes
|
||||
|
||||
- name: Recreate container
|
||||
docker_container:
|
||||
image: "{{ docker_test_image_alpine }}"
|
||||
command: '/bin/sh -c "sleep 20m"' # this will force re-creation
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
blkio_weight: 234
|
||||
cpu_period: 50000
|
||||
cpu_quota: 50000
|
||||
cpu_shares: 1100
|
||||
memory: 48M
|
||||
memory_reservation: 48M
|
||||
memory_swap: unlimited
|
||||
restart_policy: on-failure
|
||||
restart_retries: 2
|
||||
force_kill: yes
|
||||
register: recreate
|
||||
diff: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- name: Check general things
|
||||
assert:
|
||||
that:
|
||||
- create is changed
|
||||
- update is changed
|
||||
- update2 is changed
|
||||
- recreate is changed
|
||||
|
||||
# Make sure the container was *not* recreated when it should not be
|
||||
- create.container.Id == update.container.Id
|
||||
- create.container.Id == update2.container.Id
|
||||
|
||||
# Make sure that the container was recreated when it should be
|
||||
- create.container.Id != recreate.container.Id
|
||||
|
||||
- name: Check diff for first update
|
||||
assert:
|
||||
that:
|
||||
# blkio_weight sometimes cannot be set, then we end up with 0 instead of the value we had
|
||||
- update.diff.before.blkio_weight == 123 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([]))
|
||||
- update.diff.after.blkio_weight == 234
|
||||
- update.diff.before.cpu_period == 90000
|
||||
- update.diff.after.cpu_period == 50000
|
||||
- update.diff.before.cpu_quota == 150000
|
||||
- update.diff.after.cpu_quota == 50000
|
||||
- update.diff.before.cpu_shares == 900
|
||||
- update.diff.after.cpu_shares == 1100
|
||||
- update.diff.before.memory == 67108864
|
||||
- update.diff.after.memory == 50331648
|
||||
- update.diff.before.memory_reservation == 67108864
|
||||
- update.diff.after.memory_reservation == 50331648
|
||||
- (update.diff.before.memory_swap | default(0)) == 67108864 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- (update.diff.after.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- "'restart_policy' not in update.diff.before"
|
||||
- update.diff.before.restart_retries == 5
|
||||
- update.diff.after.restart_retries == 2
|
||||
|
||||
- name: Check diff for second update
|
||||
assert:
|
||||
that:
|
||||
- update2.diff.before.blkio_weight == 234 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([]))
|
||||
- update2.diff.after.blkio_weight == 135
|
||||
- update2.diff.before.cpu_period == 50000
|
||||
- update2.diff.after.cpu_period == 30000
|
||||
- update2.diff.before.cpu_quota == 50000
|
||||
- update2.diff.after.cpu_quota == 40000
|
||||
- update2.diff.before.cpu_shares == 1100
|
||||
- update2.diff.after.cpu_shares == 1000
|
||||
- update2.diff.before.memory == 50331648
|
||||
- update2.diff.after.memory == 33554432
|
||||
- update2.diff.before.memory_reservation == 50331648
|
||||
- update2.diff.after.memory_reservation == 31457280
|
||||
- (update2.diff.before.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- (update2.diff.after.memory_swap | default(0)) == 134217728 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- update2.diff.before.restart_policy == 'on-failure'
|
||||
- update2.diff.after.restart_policy == 'always'
|
||||
- update2.diff.before.restart_retries == 2
|
||||
- update2.diff.after.restart_retries == 0
|
||||
|
||||
- name: Check diff for recreation
|
||||
assert:
|
||||
that:
|
||||
- recreate.diff.before.blkio_weight == 135 or 'Docker warning: Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.' in (create.warnings | default([]))
|
||||
- recreate.diff.after.blkio_weight == 234
|
||||
- recreate.diff.before.cpu_period == 30000
|
||||
- recreate.diff.after.cpu_period == 50000
|
||||
- recreate.diff.before.cpu_quota == 40000
|
||||
- recreate.diff.after.cpu_quota == 50000
|
||||
- recreate.diff.before.cpu_shares == 1000
|
||||
- recreate.diff.after.cpu_shares == 1100
|
||||
- recreate.diff.before.memory == 33554432
|
||||
- recreate.diff.after.memory == 50331648
|
||||
- recreate.diff.before.memory_reservation == 31457280
|
||||
- recreate.diff.after.memory_reservation == 50331648
|
||||
- (recreate.diff.before.memory_swap | default(0)) == 134217728 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- (recreate.diff.after.memory_swap | default(0)) == -1 or 'Docker warning: Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap.' in (create.warnings | default([]))
|
||||
- recreate.diff.before.restart_policy == 'always'
|
||||
- recreate.diff.after.restart_policy == 'on-failure'
|
||||
- recreate.diff.before.restart_retries == 0
|
||||
- recreate.diff.after.restart_retries == 2
|
||||
- recreate.diff.before.command == ['/bin/sh', '-c', 'sleep 10m']
|
||||
- recreate.diff.after.command == ['/bin/sh', '-c', 'sleep 20m']
|
||||
@ -5,4 +5,6 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||
plugins/modules/current_container_facts.py validate-modules:return-syntax-error
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
|
||||
|
||||
@ -5,4 +5,6 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||
plugins/modules/current_container_facts.py validate-modules:return-syntax-error
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
|
||||
|
||||
@ -1,3 +1,2 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||
plugins/modules/current_container_facts.py validate-modules:return-syntax-error
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
|
||||
@ -1,2 +1 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
|
||||
@ -1,2 +1 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py replace-urlopen
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
|
||||
@ -4,4 +4,6 @@
|
||||
.azure-pipelines/scripts/publish-codecov.py compile-3.5!skip # Uses Python 3.6+ syntax
|
||||
.azure-pipelines/scripts/publish-codecov.py future-import-boilerplate
|
||||
.azure-pipelines/scripts/publish-codecov.py metaclass-boilerplate
|
||||
plugins/modules/docker_container.py use-argspec-type-path # uses colon-separated paths, can't use type=path
|
||||
plugins/module_utils/module_container/module.py compile-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/module_utils/module_container/module.py import-2.6!skip # Uses Python 2.7+ syntax
|
||||
plugins/modules/docker_container.py import-2.6!skip # Import uses Python 2.7+ syntax
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import unittest
|
||||
|
||||
from ansible_collections.community.docker.plugins.modules.docker_container import TaskParameters
|
||||
|
||||
|
||||
class TestTaskParameters(unittest.TestCase):
|
||||
"""Unit tests for TaskParameters."""
|
||||
|
||||
def test_parse_exposed_ports_tcp_udp(self):
|
||||
"""
|
||||
Ensure _parse_exposed_ports does not cancel ports with the same
|
||||
number but different protocol.
|
||||
"""
|
||||
task_params = TaskParameters.__new__(TaskParameters)
|
||||
task_params.exposed_ports = None
|
||||
result = task_params._parse_exposed_ports([80, '443', '443/udp'])
|
||||
self.assertTrue((80, 'tcp') in result)
|
||||
self.assertTrue((443, 'tcp') in result)
|
||||
self.assertTrue((443, 'udp') in result)
|
||||
Loading…
Reference in New Issue
Block a user