docker_swarm_service: Extend mount options (#53559)

* Add mount options

* Remove mount readonly default

* Fix driver_config test

* Add documentation

* Add changelog fragment

* Properly indent tmpfs_ options

* Use correct service suffix for mount tests

* Check for None value on tmpfs usage check

* Document change of mounts.readonly return key

* Use correct change log type

* Really use correct change log type

* Revert changing mount.readonly to read_only
This commit is contained in:
Hannes Ljungberg 2019-03-09 21:35:57 +01:00 committed by Sloane Hertel
parent c75da35595
commit 57f706e5a0
4 changed files with 672 additions and 100 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "docker_swarm_service - Extended ``mounts`` options. It now also accepts ``labels``, ``propagation``, ``no_copy``, ``driver_config``, ``tmpfs_size``, ``tmpfs_mode``."

View file

@ -299,7 +299,59 @@ options:
description:
- Whether the mount should be read-only.
type: bool
default: no
labels:
description:
- Volume labels to apply.
type: dict
version_added: "2.8"
propagation:
description:
- The propagation mode to use.
- Can only be used when I(mode) is C(bind).
type: str
choices:
- shared
- slave
- private
- rshared
- rslave
- rprivate
version_added: "2.8"
no_copy:
description:
- Disable copying of data from a container when a volume is created.
- Can only be used when I(mode) is C(volume).
type: bool
version_added: "2.8"
driver_config:
description:
- Volume driver configuration.
- Can only be used when I(mode) is C(volume).
suboptions:
name:
description:
- Name of the volume-driver plugin to use for the volume.
type: str
options:
description:
- Options as key-value pairs to pass to the driver for this volume.
type: dict
type: dict
version_added: "2.8"
tmpfs_size:
description:
- "Size of the tmpfs mount (format: C(<number>[<unit>])). Number is a positive integer.
Unit can be C(B) (byte), C(K) (kibibyte, 1024B), C(M) (mebibyte), C(G) (gibibyte),
C(T) (tebibyte), or C(P) (pebibyte)."
- Can only be used when I(mode) is C(tmpfs).
type: str
version_added: "2.8"
tmpfs_mode:
description:
- File mode of the tmpfs in octal.
- Can only be used when I(mode) is C(tmpfs).
type: int
version_added: "2.8"
name:
description:
- Service name.
@ -743,7 +795,13 @@ swarm_service:
"readonly": false,
"source": "/tmp/",
"target": "/remote_tmp/",
"type": "bind"
"type": "bind",
"labels": null,
"propagation": null,
"no_copy": null,
"driver_config": null,
"tmpfs_size": null,
"tmpfs_mode": null
}
],
"networks": null,
@ -1410,6 +1468,21 @@ class DockerService(DockerBaseClass):
service_m['type'] = param_m['type']
service_m['source'] = param_m['source']
service_m['target'] = param_m['target']
service_m['labels'] = param_m['labels']
service_m['no_copy'] = param_m['no_copy']
service_m['propagation'] = param_m['propagation']
service_m['driver_config'] = param_m['driver_config']
service_m['tmpfs_mode'] = param_m['tmpfs_mode']
tmpfs_size = param_m['tmpfs_size']
if tmpfs_size is not None:
try:
tmpfs_size = human_to_bytes(tmpfs_size)
except ValueError as exc:
raise ValueError(
'Failed to convert tmpfs_size to bytes: %s' % exc
)
service_m['tmpfs_size'] = tmpfs_size
s.mounts.append(service_m)
if ap['configs'] is not None:
@ -1592,14 +1665,25 @@ class DockerService(DockerBaseClass):
if self.mounts is not None:
mounts = []
for mount_config in self.mounts:
mounts.append(
types.Mount(
target=mount_config['target'],
source=mount_config['source'],
type=mount_config['type'],
read_only=mount_config['readonly']
)
)
mount_options = {
'target': 'target',
'source': 'source',
'type': 'type',
'readonly': 'read_only',
'propagation': 'propagation',
'labels': 'labels',
'no_copy': 'no_copy',
'driver_config': 'driver_config',
'tmpfs_size': 'tmpfs_size',
'tmpfs_mode': 'tmpfs_mode'
}
mount_args = {}
for option, mount_arg in mount_options.items():
value = mount_config.get(option)
if value is not None:
mount_args[mount_arg] = value
mounts.append(types.Mount(**mount_args))
configs = None
if self.configs is not None:
@ -1962,11 +2046,24 @@ class DockerServiceManager(object):
if raw_data_mounts:
ds.mounts = []
for mount_data in raw_data_mounts:
bind_options = mount_data.get('BindOptions', {})
volume_options = mount_data.get('VolumeOptions', {})
tmpfs_options = mount_data.get('TmpfsOptions', {})
driver_config = volume_options.get('DriverConfig', {})
driver_config = dict(
(key.lower(), value) for key, value in driver_config.items()
) or None
ds.mounts.append({
'source': mount_data['Source'],
'type': mount_data['Type'],
'target': mount_data['Target'],
'readonly': mount_data.get('ReadOnly', False)
'readonly': mount_data.get('ReadOnly'),
'propagation': bind_options.get('Propagation'),
'no_copy': volume_options.get('NoCopy'),
'labels': volume_options.get('Labels'),
'driver_config': driver_config,
'tmpfs_mode': tmpfs_options.get('Mode'),
'tmpfs_size': tmpfs_options.get('SizeBytes'),
})
raw_data_configs = task_template_data['ContainerSpec'].get('Configs')
@ -2173,6 +2270,17 @@ def _detect_healthcheck_start_period(client):
return False
def _detect_mount_tmpfs_usage(client):
for mount in client.module.params['mounts'] or []:
if mount.get('type') == 'tmpfs':
return True
if mount.get('tmpfs_size') is not None:
return True
if mount.get('tmpfs_mode') is not None:
return True
return False
def main():
argument_spec = dict(
name=dict(type='str', required=True),
@ -2184,9 +2292,28 @@ def main():
type=dict(
type='str',
default='bind',
choices=['bind', 'volume', 'tmpfs']
choices=['bind', 'volume', 'tmpfs'],
),
readonly=dict(type='bool', default=False),
readonly=dict(type='bool'),
labels=dict(type='dict'),
propagation=dict(
type='str',
choices=[
'shared',
'slave',
'private',
'rshared',
'rslave',
'rprivate'
]
),
no_copy=dict(type='bool'),
driver_config=dict(type='dict', options=dict(
name=dict(type='str'),
options=dict(type='dict')
)),
tmpfs_size=dict(type='str'),
tmpfs_mode=dict(type='int')
)),
configs=dict(type='list', elements='dict', options=dict(
config_id=dict(type='str', required=True),
@ -2378,6 +2505,11 @@ def main():
) is not None,
usage_msg='set placement.constraints'
),
mounts_tmpfs=dict(
docker_py_version='2.6.0',
detect_usage=_detect_mount_tmpfs_usage,
usage_msg='set mounts.tmpfs'
),
)
required_if = [

View file

@ -0,0 +1,525 @@
- name: Registering service name
set_fact:
service_name: "{{ name_prefix ~ '-mounts' }}"
volume_name_1: "{{ name_prefix ~ '-volume-1' }}"
volume_name_2: "{{ name_prefix ~ '-volume-2' }}"
- name: Registering service name
set_fact:
service_names: "{{ service_names }} + [service_name]"
volume_names: "{{ volume_names }} + [volume_name_1, volume_name_2]"
- docker_volume:
name: "{{ volume_name }}"
state: present
loop:
- "{{ volume_name_1 }}"
- "{{ volume_name_2 }}"
loop_control:
loop_var: volume_name
####################################################################
## mounts ##########################################################
####################################################################
- name: mounts
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
register: mounts_1
- name: mounts (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
register: mounts_2
- name: mounts (add)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
- source: "/tmp/"
target: "/tmp/{{ volume_name_2 }}"
type: "bind"
register: mounts_3
- name: mounts (empty)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts: []
register: mounts_4
- name: mounts (empty idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts: []
register: mounts_5
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_1 is changed
- mounts_2 is not changed
- mounts_3 is changed
- mounts_4 is changed
- mounts_5 is not changed
####################################################################
## mounts.readonly #################################################
####################################################################
- name: mounts.readonly
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
readonly: true
register: mounts_readonly_1
- name: mounts.readonly (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
readonly: true
register: mounts_readonly_2
- name: mounts.readonly (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
readonly: false
register: mounts_readonly_3
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_readonly_1 is changed
- mounts_readonly_2 is not changed
- mounts_readonly_3 is changed
####################################################################
## mounts.propagation ##############################################
####################################################################
- name: mounts.propagation
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "/tmp"
target: "/tmp/{{ volume_name_1 }}"
type: "bind"
propagation: "slave"
register: mounts_propagation_1
- name: mounts.propagation (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "/tmp"
target: "/tmp/{{ volume_name_1 }}"
type: "bind"
propagation: "slave"
register: mounts_propagation_2
- name: mounts.propagation (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "/tmp"
target: "/tmp/{{ volume_name_1 }}"
type: "bind"
propagation: "rprivate"
register: mounts_propagation_3
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_propagation_1 is changed
- mounts_propagation_2 is not changed
- mounts_propagation_3 is changed
####################################################################
## mounts.labels ##################################################
####################################################################
- name: mounts.labels
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
labels:
mylabel: hello-world
my-other-label: hello-mars
register: mounts_labels_1
- name: mounts.labels (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
labels:
mylabel: hello-world
my-other-label: hello-mars
register: mounts_labels_2
- name: mounts.labels (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
labels:
mylabel: hello-world
register: mounts_labels_3
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_labels_1 is changed
- mounts_labels_2 is not changed
- mounts_labels_3 is changed
####################################################################
## mounts.no_copy ##################################################
####################################################################
- name: mounts.no_copy
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
no_copy: true
register: mounts_no_copy_1
- name: mounts.no_copy (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
no_copy: true
register: mounts_no_copy_2
- name: mounts.no_copy (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
no_copy: false
register: mounts_no_copy_3
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_no_copy_1 is changed
- mounts_no_copy_2 is not changed
- mounts_no_copy_3 is changed
####################################################################
## mounts.driver_config ############################################
####################################################################
- name: mounts.driver_config
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
driver_config:
name: "nfs"
options:
addr: "127.0.0.1"
register: mounts_driver_config_1
- name: mounts.driver_config
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
driver_config:
name: "nfs"
options:
addr: "127.0.0.1"
register: mounts_driver_config_2
- name: mounts.driver_config
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
driver_config:
name: "local"
register: mounts_driver_config_3
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_driver_config_1 is changed
- mounts_driver_config_2 is not changed
- mounts_driver_config_3 is changed
####################################################################
## mounts.tmpfs_size ###############################################
####################################################################
- name: mounts.tmpfs_size
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_size: "50M"
register: mounts_tmpfs_size_1
ignore_errors: yes
- name: mounts.tmpfs_size (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_size: "50M"
register: mounts_tmpfs_size_2
ignore_errors: yes
- name: mounts.tmpfs_size (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_size: "25M"
register: mounts_tmpfs_size_3
ignore_errors: yes
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_tmpfs_size_1 is changed
- mounts_tmpfs_size_2 is not changed
- mounts_tmpfs_size_3 is changed
when: docker_py_version is version('2.6.0', '>=')
- assert:
that:
- mounts_tmpfs_size_1 is failed
- "'Minimum version required' in mounts_tmpfs_size_1.msg"
when: docker_py_version is version('2.6.0', '<')
####################################################################
## mounts.tmpfs_mode ###############################################
####################################################################
- name: mounts.tmpfs_mode
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_mode: 0444
register: mounts_tmpfs_mode_1
ignore_errors: yes
- name: mounts.tmpfs_mode (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_mode: 0444
register: mounts_tmpfs_mode_2
ignore_errors: yes
- name: mounts.tmpfs_mode (change)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "tmpfs"
tmpfs_mode: 0777
register: mounts_tmpfs_mode_3
ignore_errors: yes
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_tmpfs_mode_1 is changed
- mounts_tmpfs_mode_2 is not changed
- mounts_tmpfs_mode_3 is changed
when: docker_py_version is version('2.6.0', '>=')
- assert:
that:
- mounts_tmpfs_size_1 is failed
- "'Minimum version required' in mounts_tmpfs_size_1.msg"
when: docker_py_version is version('2.6.0', '<')
####################################################################
####################################################################
####################################################################
- name: Delete volumes
docker_volume:
name: "{{ volume_name }}"
state: absent
loop:
- "{{ volume_name_1 }}"
- "{{ volume_name_2 }}"
loop_control:
loop_var: volume_name
ignore_errors: yes

View file

@ -7,8 +7,6 @@
network_name_2: "{{ name_prefix ~ '-network-2' }}"
config_name_1: "{{ name_prefix ~ '-configs-1' }}"
config_name_2: "{{ name_prefix ~ '-configs-2' }}"
volume_name_1: "{{ name_prefix ~ '-volume-1' }}"
volume_name_2: "{{ name_prefix ~ '-volume-2' }}"
secret_name_1: "{{ name_prefix ~ '-secret-1' }}"
secret_name_2: "{{ name_prefix ~ '-secret-2' }}"
@ -17,7 +15,6 @@
service_names: "{{ service_names }} + [service_name]"
network_names: "{{ network_names }} + [network_name_1, network_name_2]"
config_names: "{{ config_names }} + [config_name_1, config_name_2]"
volume_names: "{{ volume_names }} + [volume_name_1, volume_name_2]"
secret_names: "{{ secret_names }} + [secret_name_1, secret_name_2]"
- docker_config:
@ -58,15 +55,6 @@
loop_control:
loop_var: network_name
- docker_volume:
name: "{{ volume_name }}"
state: present
loop:
- "{{ volume_name_1 }}"
- "{{ volume_name_2 }}"
loop_control:
loop_var: volume_name
####################################################################
## args ############################################################
####################################################################
@ -1408,81 +1396,6 @@
- mode_2 is not changed
- mode_3 is changed
####################################################################
## mounts ##########################################################
####################################################################
- name: mounts
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
register: mounts_1
- name: mounts (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
register: mounts_2
- name: mounts (add)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts:
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
- source: "{{ volume_name_2 }}"
target: "/tmp/{{ volume_name_2 }}"
type: "volume"
register: mounts_3
- name: mounts (empty)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts: []
register: mounts_4
- name: mounts (empty idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
mounts: []
register: mounts_5
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- mounts_1 is changed
- mounts_2 is not changed
- mounts_3 is changed
- mounts_4 is changed
- mounts_5 is not changed
####################################################################
## networks ########################################################
####################################################################