[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.
pull/59999/head
Dave Bendit 5 years ago committed by Felix Fontein
parent a7573102bc
commit fc558fb85f

@ -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.
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…
Cancel
Save