diff --git a/changelogs/fragments/49808-docker_container-mounts.yml b/changelogs/fragments/49808-docker_container-mounts.yml new file mode 100644 index 00000000000..22a25536823 --- /dev/null +++ b/changelogs/fragments/49808-docker_container-mounts.yml @@ -0,0 +1,2 @@ +minor_changes: +- "docker_container - add ``mounts`` option." diff --git a/lib/ansible/module_utils/docker/common.py b/lib/ansible/module_utils/docker/common.py index 932af58f51b..ccf7e5e5dba 100644 --- a/lib/ansible/module_utils/docker/common.py +++ b/lib/ansible/module_utils/docker/common.py @@ -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) diff --git a/lib/ansible/modules/cloud/docker/docker_container.py b/lib/ansible/modules/cloud/docker/docker_container.py index c5ded7c0188..c2869e4f8f6 100644 --- a/lib/ansible/modules/cloud/docker/docker_container.py +++ b/lib/ansible/modules/cloud/docker/docker_container.py @@ -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 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( diff --git a/test/integration/targets/docker_container/tasks/tests/mounts-volumes.yml b/test/integration/targets/docker_container/tasks/tests/mounts-volumes.yml new file mode 100644 index 00000000000..218caad65f8 --- /dev/null +++ b/test/integration/targets/docker_container/tasks/tests/mounts-volumes.yml @@ -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 + +#################################################################### +#################################################################### +#################################################################### diff --git a/test/integration/targets/docker_container/tasks/tests/options.yml b/test/integration/targets/docker_container/tasks/tests/options.yml index e594f46cf95..638286b0c68 100644 --- a/test/integration/targets/docker_container/tasks/tests/options.yml +++ b/test/integration/targets/docker_container/tasks/tests/options.yml @@ -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 ##################################################### ####################################################################