[docker_container] Adding support for mounts
option (#49808)
* [WIP][docker_container] Adding support for `mounts` option Fixes #42054 * Adjusting to current standards. * Add changelog. * Adjust types. * Cleanup. * Add idempotency checks for mounts. * Improve diff for mounts. * Linting. * Python 2.6 compatibility. * Fix error message formatting. * Move mounts and volumes tests into own file. * Add set of mount tests. * Golang's omitempty for bool omits false values. * Simplify sanity checks. Correct order of volume_options sanitization and usage. * Fix key. * Fix check. * Add tests where both volumes and mounts show up. * Add collision test.
This commit is contained in:
parent
a7573102bc
commit
fc558fb85f
5 changed files with 612 additions and 199 deletions
changelogs/fragments
lib/ansible
test/integration/targets/docker_container/tasks/tests
2
changelogs/fragments/49808-docker_container-mounts.yml
Normal file
2
changelogs/fragments/49808-docker_container-mounts.yml
Normal file
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "docker_container - add ``mounts`` option."
|
|
@ -1005,3 +1005,10 @@ def parse_healthcheck(healthcheck):
|
|||
return None, True
|
||||
|
||||
return result, False
|
||||
|
||||
|
||||
def omit_none_from_dict(d):
|
||||
"""
|
||||
Return a copy of the dictionary with all keys with value None omitted.
|
||||
"""
|
||||
return dict((k, v) for (k, v) in d.items() if v is not None)
|
||||
|
|
|
@ -375,6 +375,86 @@ options:
|
|||
- Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
|
||||
- If not set, the value will be remain the same if container exists and will be inherited from the host machine if it is (re-)created.
|
||||
type: int
|
||||
mounts:
|
||||
version_added: "2.9"
|
||||
type: list
|
||||
description:
|
||||
- 'Specification for mounts to be added to the container. More powerful alternative to I(volumes).'
|
||||
suboptions:
|
||||
target:
|
||||
description:
|
||||
- Path inside the container.
|
||||
type: str
|
||||
required: true
|
||||
source:
|
||||
description:
|
||||
- Mount source (e.g. a volume name or a host path).
|
||||
type: str
|
||||
type:
|
||||
description:
|
||||
- The mount type.
|
||||
- Note that C(npipe) is only supported by Docker for Windows.
|
||||
type: str
|
||||
choices:
|
||||
- 'bind'
|
||||
- 'volume'
|
||||
- 'tmpfs'
|
||||
- 'npipe'
|
||||
default: volume
|
||||
read_only:
|
||||
description:
|
||||
- 'Whether the mount should be read-only.'
|
||||
type: bool
|
||||
consistency:
|
||||
description:
|
||||
- 'The consistency requirement for the mount.'
|
||||
type: str
|
||||
choices:
|
||||
- 'default'
|
||||
- 'consistent'
|
||||
- 'cached'
|
||||
- 'delegated'
|
||||
propagation:
|
||||
description:
|
||||
- Propagation mode. Only valid for the C(bind) type.
|
||||
type: str
|
||||
choices:
|
||||
- 'private'
|
||||
- 'rprivate'
|
||||
- 'shared'
|
||||
- 'rshared'
|
||||
- 'slave'
|
||||
- 'rslave'
|
||||
no_copy:
|
||||
description:
|
||||
- False if the volume should be populated with the data from the target. Only valid for the C(volume) type.
|
||||
- The default value is C(false).
|
||||
type: bool
|
||||
labels:
|
||||
description:
|
||||
- User-defined name and labels for the volume. Only valid for the C(volume) type.
|
||||
type: dict
|
||||
volume_driver:
|
||||
description:
|
||||
- Specify the volume driver. Only valid for the C(volume) type.
|
||||
- See L(here,https://docs.docker.com/storage/volumes/#use-a-volume-driver) for details.
|
||||
type: str
|
||||
volume_options:
|
||||
description:
|
||||
- Dictionary of options specific to the chosen volume_driver. See L(here,https://docs.docker.com/storage/volumes/#use-a-volume-driver)
|
||||
for details.
|
||||
type: dict
|
||||
tmpfs_size:
|
||||
description:
|
||||
- "The size for the tmpfs mount in bytes. Format: <number>[<unit>]"
|
||||
- "Number is a positive integer. Unit can be one of C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
|
||||
C(T) (tebibyte), or C(P) (pebibyte)"
|
||||
- "Omitting the unit defaults to bytes."
|
||||
type: str
|
||||
tmpfs_mode:
|
||||
description:
|
||||
- The permission mode for the tmpfs mount.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Assign a name to a new container or match an existing container.
|
||||
|
@ -953,6 +1033,8 @@ from ansible.module_utils.docker.common import (
|
|||
compare_generic,
|
||||
is_image_name_id,
|
||||
sanitize_result,
|
||||
clean_dict_booleans_for_docker_api,
|
||||
omit_none_from_dict,
|
||||
parse_healthcheck,
|
||||
DOCKER_COMMON_ARGS,
|
||||
RequestException,
|
||||
|
@ -964,6 +1046,7 @@ try:
|
|||
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 import types as docker_types
|
||||
else:
|
||||
from docker.utils.types import Ulimit, LogConfig
|
||||
from docker.errors import DockerException, APIError, NotFound
|
||||
|
@ -1091,6 +1174,7 @@ class TaskParameters(DockerBaseClass):
|
|||
self.memory_reservation = None
|
||||
self.memory_swap = None
|
||||
self.memory_swappiness = None
|
||||
self.mounts = None
|
||||
self.name = None
|
||||
self.network_mode = None
|
||||
self.userns_mode = None
|
||||
|
@ -1204,6 +1288,8 @@ class TaskParameters(DockerBaseClass):
|
|||
if isinstance(self.command, list):
|
||||
self.command = ' '.join([str(x) for x in self.command])
|
||||
|
||||
self.mounts_opt, self.expected_mounts = self._process_mounts()
|
||||
|
||||
for param_name in ["device_read_bps", "device_write_bps"]:
|
||||
if client.module.params.get(param_name):
|
||||
self._process_rate_bps(option=param_name)
|
||||
|
@ -1381,6 +1467,7 @@ class TaskParameters(DockerBaseClass):
|
|||
device_read_iops='device_read_iops',
|
||||
device_write_iops='device_write_iops',
|
||||
pids_limit='pids_limit',
|
||||
mounts='mounts',
|
||||
)
|
||||
|
||||
if self.client.docker_py_version >= LooseVersion('1.9') and self.client.docker_api_version >= LooseVersion('1.22'):
|
||||
|
@ -1403,6 +1490,9 @@ class TaskParameters(DockerBaseClass):
|
|||
params['restart_policy'] = dict(Name=self.restart_policy,
|
||||
MaximumRetryCount=self.restart_retries)
|
||||
|
||||
if 'mounts' in params:
|
||||
params['mounts'] = self.mounts_opt
|
||||
|
||||
return self.client.create_host_config(**params)
|
||||
|
||||
@property
|
||||
|
@ -1418,7 +1508,7 @@ class TaskParameters(DockerBaseClass):
|
|||
network.get('Options', {}).get('com.docker.network.bridge.host_binding_ipv4'):
|
||||
ip = network['Options']['com.docker.network.bridge.host_binding_ipv4']
|
||||
break
|
||||
except NotFound as e:
|
||||
except NotFound as dummy:
|
||||
self.client.fail(
|
||||
"Cannot inspect the network '{0}' to determine the default IP: {1}".format(net['name'], e),
|
||||
exception=traceback.format_exc()
|
||||
|
@ -1487,12 +1577,12 @@ class TaskParameters(DockerBaseClass):
|
|||
for vol in volumes:
|
||||
host = None
|
||||
if ':' in vol:
|
||||
if len(vol.split(':')) == 3:
|
||||
host, container, mode = vol.split(':')
|
||||
parts = vol.split(':')
|
||||
if len(parts) == 3:
|
||||
host, container, mode = parts
|
||||
if not is_volume_permissions(mode):
|
||||
self.fail('Found invalid volumes mode: {0}'.format(mode))
|
||||
if len(vol.split(':')) == 2:
|
||||
parts = vol.split(':')
|
||||
elif len(parts) == 2:
|
||||
if not is_volume_permissions(parts[1]):
|
||||
host, container, mode = (vol.split(':') + ['rw'])
|
||||
if host is not None:
|
||||
|
@ -1659,6 +1749,58 @@ class TaskParameters(DockerBaseClass):
|
|||
self.fail("Error getting network id for %s - %s" % (network_name, str(exc)))
|
||||
return network_id
|
||||
|
||||
def _process_mounts(self):
|
||||
if self.mounts is None:
|
||||
return None, None
|
||||
mounts_list = []
|
||||
mounts_expected = []
|
||||
for mount in self.mounts:
|
||||
target = mount['target']
|
||||
type = mount['type']
|
||||
mount_dict = dict(mount)
|
||||
# Sanity checks (so we don't wait for docker-py to barf on input)
|
||||
if mount_dict.get('source') is None and type != 'tmpfs':
|
||||
self.client.fail('source must be specified for mount "{0}" of type "{1}"'.format(target, type))
|
||||
mount_option_types = dict(
|
||||
volume_driver='volume',
|
||||
volume_options='volume',
|
||||
propagation='bind',
|
||||
no_copy='volume',
|
||||
labels='volume',
|
||||
tmpfs_size='tmpfs',
|
||||
tmpfs_mode='tmpfs',
|
||||
)
|
||||
for option, req_type in mount_option_types.items():
|
||||
if mount_dict.get(option) is not None and type != req_type:
|
||||
self.client.fail('{0} cannot be specified for mount "{1}" of type "{2}" (needs type "{3}")'.format(option, target, type, req_type))
|
||||
# Handle volume_driver and volume_options
|
||||
volume_driver = mount_dict.pop('volume_driver')
|
||||
volume_options = mount_dict.pop('volume_options')
|
||||
if volume_driver:
|
||||
if volume_options:
|
||||
volume_options = clean_dict_booleans_for_docker_api(volume_options)
|
||||
mount_dict['driver_config'] = docker_types.DriverConfig(name=volume_driver, options=volume_options)
|
||||
if mount_dict['labels']:
|
||||
mount_dict['labels'] = clean_dict_booleans_for_docker_api(mount_dict['labels'])
|
||||
if mount_dict.get('tmpfs_size') is not None:
|
||||
try:
|
||||
mount_dict['tmpfs_size'] = human_to_bytes(mount_dict['tmpfs_size'])
|
||||
except ValueError as exc:
|
||||
self.fail('Failed to convert tmpfs_size of mount "{0}" to bytes: {1}'.format(target, exc))
|
||||
if mount_dict.get('tmpfs_mode') is not None:
|
||||
try:
|
||||
mount_dict['tmpfs_mode'] = int(mount_dict['tmpfs_mode'], 8)
|
||||
except Exception as dummy:
|
||||
self.client.fail('tmp_fs mode of mount "{0}" is not an octal string!'.format(target))
|
||||
# Fill expected mount dict
|
||||
mount_expected = dict(mount)
|
||||
mount_expected['tmpfs_size'] = mount_dict['tmpfs_size']
|
||||
mount_expected['tmpfs_mode'] = mount_dict['tmpfs_mode']
|
||||
# Add result to lists
|
||||
mounts_list.append(docker_types.Mount(**mount_dict))
|
||||
mounts_expected.append(omit_none_from_dict(mount_expected))
|
||||
return mounts_list, mounts_expected
|
||||
|
||||
def _process_rate_bps(self, option):
|
||||
"""
|
||||
Format device_read_bps and device_write_bps option
|
||||
|
@ -1735,6 +1877,7 @@ class Container(DockerBaseClass):
|
|||
self.parameters_map['expected_cmd'] = 'command'
|
||||
self.parameters_map['expected_devices'] = 'devices'
|
||||
self.parameters_map['expected_healthcheck'] = 'healthcheck'
|
||||
self.parameters_map['expected_mounts'] = 'mounts'
|
||||
|
||||
def fail(self, msg):
|
||||
self.parameters.client.fail(msg)
|
||||
|
@ -1762,6 +1905,28 @@ class Container(DockerBaseClass):
|
|||
'''
|
||||
return compare_generic(a, b, compare['comparison'], compare['type'])
|
||||
|
||||
def _decode_mounts(self, mounts):
|
||||
if not mounts:
|
||||
return mounts
|
||||
result = []
|
||||
empty_dict = dict()
|
||||
for mount in mounts:
|
||||
res = dict()
|
||||
res['type'] = mount.get('Type')
|
||||
res['source'] = mount.get('Source')
|
||||
res['target'] = mount.get('Target')
|
||||
res['read_only'] = mount.get('ReadOnly', False) # golang's omitempty for bool returns None for False
|
||||
res['consistency'] = mount.get('Consistency')
|
||||
res['propagation'] = mount.get('BindOptions', empty_dict).get('Propagation')
|
||||
res['no_copy'] = mount.get('VolumeOptions', empty_dict).get('NoCopy', False)
|
||||
res['labels'] = mount.get('VolumeOptions', empty_dict).get('Labels', empty_dict)
|
||||
res['volume_driver'] = mount.get('VolumeOptions', empty_dict).get('DriverConfig', empty_dict).get('Name')
|
||||
res['volume_options'] = mount.get('VolumeOptions', empty_dict).get('DriverConfig', empty_dict).get('Options', empty_dict)
|
||||
res['tmpfs_size'] = mount.get('TmpfsOptions', empty_dict).get('SizeBytes')
|
||||
res['tmpfs_mode'] = mount.get('TmpfsOptions', empty_dict).get('Mode')
|
||||
result.append(res)
|
||||
return result
|
||||
|
||||
def has_different_configuration(self, image):
|
||||
'''
|
||||
Diff parameters vs existing container config. Returns tuple: (True | False, List of differences)
|
||||
|
@ -1860,6 +2025,11 @@ class Container(DockerBaseClass):
|
|||
device_read_iops=host_config.get('BlkioDeviceReadIOps'),
|
||||
device_write_iops=host_config.get('BlkioDeviceWriteIOps'),
|
||||
pids_limit=host_config.get('PidsLimit'),
|
||||
# According to https://github.com/moby/moby/, support for HostConfig.Mounts
|
||||
# has been included at least since v17.03.0-ce, which has API version 1.26.
|
||||
# The previous tag, v1.9.1, has API version 1.21 and does not have
|
||||
# HostConfig.Mounts. I have no idea what about API 1.25...
|
||||
expected_mounts=self._decode_mounts(host_config.get('Mounts')),
|
||||
)
|
||||
# Options which don't make sense without their accompanying option
|
||||
if self.parameters.restart_policy:
|
||||
|
@ -1920,11 +2090,16 @@ class Container(DockerBaseClass):
|
|||
c = sorted(c)
|
||||
elif compare['type'] == 'set(dict)':
|
||||
# Since the order does not matter, sort so that the diff output is better.
|
||||
# We sort the list of dictionaries by using the sorted items of a dict as its key.
|
||||
def sort_key_fn(x):
|
||||
# For selected values, use one entry as key
|
||||
if key == 'expected_mounts':
|
||||
return x['target']
|
||||
# We sort the list of dictionaries by using the sorted items of a dict as its key.
|
||||
return sorted((a, str(b)) for a, b in x.items())
|
||||
if p is not None:
|
||||
p = sorted(p, key=lambda x: sorted(x.items()))
|
||||
p = sorted(p, key=sort_key_fn)
|
||||
if c is not None:
|
||||
c = sorted(c, key=lambda x: sorted(x.items()))
|
||||
c = sorted(c, key=sort_key_fn)
|
||||
differences.add(key, parameter=p, active=c)
|
||||
|
||||
has_differences = not differences.empty
|
||||
|
@ -2729,6 +2904,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
|
|||
env='set',
|
||||
entrypoint='list',
|
||||
etc_hosts='set',
|
||||
mounts='set(dict)',
|
||||
networks='set(dict)',
|
||||
ulimits='set(dict)',
|
||||
device_read_bps='set(dict)',
|
||||
|
@ -2874,6 +3050,7 @@ class AnsibleDockerClientContainer(AnsibleDockerClient):
|
|||
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'),
|
||||
pids_limit=dict(docker_py_version='1.10.0', docker_api_version='1.23'),
|
||||
mounts=dict(docker_py_version='2.6.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'),
|
||||
|
@ -2962,6 +3139,20 @@ def main():
|
|||
memory_reservation=dict(type='str'),
|
||||
memory_swap=dict(type='str'),
|
||||
memory_swappiness=dict(type='int'),
|
||||
mounts=dict(type='list', elements='dict', options=dict(
|
||||
target=dict(type='str', required=True),
|
||||
source=dict(type='str'),
|
||||
type=dict(type='str', choices=['bind', 'volume', 'tmpfs', 'npipe'], default='volume'),
|
||||
read_only=dict(type='bool'),
|
||||
consistency=dict(type='str', choices=['default', 'consistent', 'cached', 'delegated']),
|
||||
propagation=dict(type='str', choices=['private', 'rprivate', 'shared', 'rshared', 'slave', 'rslave']),
|
||||
no_copy=dict(type='bool'),
|
||||
labels=dict(type='dict'),
|
||||
volume_driver=dict(type='str'),
|
||||
volume_options=dict(type='dict'),
|
||||
tmpfs_size=dict(type='str'),
|
||||
tmpfs_mode=dict(type='str'),
|
||||
)),
|
||||
name=dict(type='str', required=True),
|
||||
network_mode=dict(type='str'),
|
||||
networks=dict(type='list', elements='dict', options=dict(
|
||||
|
|
|
@ -0,0 +1,404 @@
|
|||
---
|
||||
- name: Registering container name
|
||||
set_fact:
|
||||
cname: "{{ cname_prefix ~ '-mounts' }}"
|
||||
cname_h1: "{{ cname_prefix ~ '-mounts-h1' }}"
|
||||
cname_h2: "{{ cname_prefix ~ '-mounts-h2' }}"
|
||||
- name: Registering container name
|
||||
set_fact:
|
||||
cnames: "{{ cnames + [cname, cname_h1, cname_h2] }}"
|
||||
|
||||
####################################################################
|
||||
## keep_volumes ####################################################
|
||||
####################################################################
|
||||
|
||||
# TODO: - keep_volumes
|
||||
|
||||
####################################################################
|
||||
## mounts ##########################################################
|
||||
####################################################################
|
||||
|
||||
- name: mounts
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
- source: /
|
||||
target: /whatever
|
||||
type: bind
|
||||
read_only: no
|
||||
register: mounts_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /
|
||||
target: /whatever
|
||||
type: bind
|
||||
read_only: no
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
register: mounts_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (less mounts)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
register: mounts_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (more mounts)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
- source: /tmp
|
||||
target: /somewhereelse
|
||||
type: bind
|
||||
read_only: yes
|
||||
force_kill: yes
|
||||
register: mounts_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts (different modes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
- source: /tmp
|
||||
target: /somewhereelse
|
||||
type: bind
|
||||
read_only: no
|
||||
force_kill: yes
|
||||
register: mounts_5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- mounts_1 is changed
|
||||
- mounts_2 is not changed
|
||||
- mounts_3 is not changed
|
||||
- mounts_4 is changed
|
||||
- mounts_5 is 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 ################################################
|
||||
####################################################################
|
||||
|
||||
- name: mounts + volumes
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /
|
||||
target: /whatever
|
||||
type: bind
|
||||
read_only: yes
|
||||
volumes:
|
||||
- /tmp:/tmp
|
||||
register: mounts_volumes_1
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /
|
||||
target: /whatever
|
||||
type: bind
|
||||
read_only: yes
|
||||
volumes:
|
||||
- /tmp:/tmp
|
||||
register: mounts_volumes_2
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (switching)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
read_only: no
|
||||
volumes:
|
||||
- /:/whatever:ro
|
||||
force_kill: yes
|
||||
register: mounts_volumes_3
|
||||
ignore_errors: yes
|
||||
|
||||
- name: mounts + volumes (collision, should fail)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
mounts:
|
||||
- source: /tmp
|
||||
target: /tmp
|
||||
type: bind
|
||||
read_only: no
|
||||
volumes:
|
||||
- /tmp:/tmp
|
||||
force_kill: yes
|
||||
register: mounts_volumes_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- mounts_volumes_1 is changed
|
||||
- mounts_volumes_2 is not changed
|
||||
- mounts_volumes_3 is changed
|
||||
- mounts_volumes_4 is failed
|
||||
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 ###################################################
|
||||
####################################################################
|
||||
|
||||
- name: volume_driver
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: local
|
||||
state: started
|
||||
register: volume_driver_1
|
||||
|
||||
- name: volume_driver (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: local
|
||||
state: started
|
||||
register: volume_driver_2
|
||||
|
||||
- name: volume_driver (change)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: /
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: volume_driver_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volume_driver_1 is changed
|
||||
- volume_driver_2 is not changed
|
||||
- volume_driver_3 is changed
|
||||
|
||||
####################################################################
|
||||
## volumes #########################################################
|
||||
####################################################################
|
||||
|
||||
- name: volumes
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/:/whatever:rw,z"
|
||||
register: volumes_1
|
||||
|
||||
- name: volumes (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/:/whatever:rw,z"
|
||||
- "/tmp:/tmp"
|
||||
register: volumes_2
|
||||
|
||||
- name: volumes (less volumes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
register: volumes_3
|
||||
|
||||
- name: volumes (more volumes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/tmp:/somewhereelse:ro,Z"
|
||||
force_kill: yes
|
||||
register: volumes_4
|
||||
|
||||
- name: volumes (different modes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/tmp:/somewhereelse:ro"
|
||||
force_kill: yes
|
||||
register: volumes_5
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volumes_1 is changed
|
||||
- volumes_2 is not changed
|
||||
- volumes_3 is not changed
|
||||
- volumes_4 is changed
|
||||
- volumes_5 is changed
|
||||
|
||||
####################################################################
|
||||
## volumes_from ####################################################
|
||||
####################################################################
|
||||
|
||||
- name: start helpers
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ container_name }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "{{ '/tmp:/tmp' if container_name == cname_h1 else '/:/whatever:ro' }}"
|
||||
loop:
|
||||
- "{{ cname_h1 }}"
|
||||
- "{{ cname_h2 }}"
|
||||
loop_control:
|
||||
loop_var: container_name
|
||||
|
||||
- name: volumes_from
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h1 }}"
|
||||
register: volumes_from_1
|
||||
|
||||
- name: volumes_from (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h1 }}"
|
||||
register: volumes_from_2
|
||||
|
||||
- name: volumes_from (change)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h2 }}"
|
||||
force_kill: yes
|
||||
register: volumes_from_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ container_name }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
loop:
|
||||
- "{{ cname }}"
|
||||
- "{{ cname_h1 }}"
|
||||
- "{{ cname_h2 }}"
|
||||
loop_control:
|
||||
loop_var: container_name
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volumes_from_1 is changed
|
||||
- volumes_from_2 is not changed
|
||||
- volumes_from_3 is changed
|
||||
|
||||
####################################################################
|
||||
####################################################################
|
||||
####################################################################
|
|
@ -3644,197 +3644,6 @@ avoid such warnings, please quote the value.' in log_options_2.warnings"
|
|||
- "'Minimum version required is 3.5.0 ' in uts_1.msg"
|
||||
when: docker_py_version is version('3.5.0', '<')
|
||||
|
||||
####################################################################
|
||||
## keep_volumes ####################################################
|
||||
####################################################################
|
||||
|
||||
# TODO: - keep_volumes
|
||||
|
||||
####################################################################
|
||||
## volume_driver ###################################################
|
||||
####################################################################
|
||||
|
||||
- name: volume_driver
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: local
|
||||
state: started
|
||||
register: volume_driver_1
|
||||
|
||||
- name: volume_driver (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: local
|
||||
state: started
|
||||
register: volume_driver_2
|
||||
|
||||
- name: volume_driver (change)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
volume_driver: /
|
||||
state: started
|
||||
force_kill: yes
|
||||
register: volume_driver_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volume_driver_1 is changed
|
||||
- volume_driver_2 is not changed
|
||||
- volume_driver_3 is changed
|
||||
|
||||
####################################################################
|
||||
## volumes #########################################################
|
||||
####################################################################
|
||||
|
||||
- name: volumes
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/:/whatever:rw,z"
|
||||
register: volumes_1
|
||||
|
||||
- name: volumes (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/:/whatever:rw,z"
|
||||
- "/tmp:/tmp"
|
||||
register: volumes_2
|
||||
|
||||
- name: volumes (less volumes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
register: volumes_3
|
||||
|
||||
- name: volumes (more volumes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/tmp:/somewhereelse:ro,Z"
|
||||
force_kill: yes
|
||||
register: volumes_4
|
||||
|
||||
- name: volumes (different modes)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "/tmp:/tmp"
|
||||
- "/tmp:/somewhereelse:ro"
|
||||
force_kill: yes
|
||||
register: volumes_5
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ cname }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volumes_1 is changed
|
||||
- volumes_2 is not changed
|
||||
- volumes_3 is not changed
|
||||
- volumes_4 is changed
|
||||
- volumes_5 is changed
|
||||
|
||||
####################################################################
|
||||
## volumes_from ####################################################
|
||||
####################################################################
|
||||
|
||||
- name: start helpers
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ container_name }}"
|
||||
state: started
|
||||
volumes:
|
||||
- "{{ '/tmp:/tmp' if container_name == cname_h1 else '/:/whatever:ro' }}"
|
||||
loop:
|
||||
- "{{ cname_h1 }}"
|
||||
- "{{ cname_h2 }}"
|
||||
loop_control:
|
||||
loop_var: container_name
|
||||
|
||||
- name: volumes_from
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h1 }}"
|
||||
register: volumes_from_1
|
||||
|
||||
- name: volumes_from (idempotency)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h1 }}"
|
||||
register: volumes_from_2
|
||||
|
||||
- name: volumes_from (change)
|
||||
docker_container:
|
||||
image: alpine:3.8
|
||||
command: '/bin/sh -c "sleep 10m"'
|
||||
name: "{{ cname }}"
|
||||
state: started
|
||||
volumes_from: "{{ cname_h2 }}"
|
||||
force_kill: yes
|
||||
register: volumes_from_3
|
||||
|
||||
- name: cleanup
|
||||
docker_container:
|
||||
name: "{{ container_name }}"
|
||||
state: absent
|
||||
force_kill: yes
|
||||
loop:
|
||||
- "{{ cname }}"
|
||||
- "{{ cname_h1 }}"
|
||||
- "{{ cname_h2 }}"
|
||||
loop_control:
|
||||
loop_var: container_name
|
||||
diff: no
|
||||
|
||||
- assert:
|
||||
that:
|
||||
- volumes_from_1 is changed
|
||||
- volumes_from_2 is not changed
|
||||
- volumes_from_3 is changed
|
||||
|
||||
####################################################################
|
||||
## working_dir #####################################################
|
||||
####################################################################
|
||||
|
|
Loading…
Reference in a new issue