diff --git a/changelogs/fragments/51082-docker_swarm_service-placement-preferences-option.yml b/changelogs/fragments/51082-docker_swarm_service-placement-preferences-option.yml new file mode 100644 index 00000000000..5dc7dc32df9 --- /dev/null +++ b/changelogs/fragments/51082-docker_swarm_service-placement-preferences-option.yml @@ -0,0 +1,2 @@ +minor_changes: + - "docker_swarm_service - Added support for ``placement_preferences`` parameter." diff --git a/lib/ansible/modules/cloud/docker/docker_swarm_service.py b/lib/ansible/modules/cloud/docker/docker_swarm_service.py index bcd5ac51569..be4886f8561 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm_service.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm_service.py @@ -47,6 +47,13 @@ options: description: - List of the service constraints. - Maps docker service --constraint option. + placement_preferences: + required: false + type: list + description: + - List of the placement preferences as key value pairs. + - Maps docker service C(--placement-pref) option. + version_added: 2.8 hostname: required: false default: "" @@ -495,6 +502,12 @@ EXAMPLES = ''' docker_swarm_service: name: myservice state: absent +- name: set placement preferences + docker_swarm_service: + name: myservice + image: alpine:edge + placement_preferences: + - spread: "node.labels.mylabel" ''' import time @@ -518,7 +531,6 @@ except Exception: class DockerService(DockerBaseClass): def __init__(self): super(DockerService, self).__init__() - self.constraints = [] self.image = "" self.args = [] self.endpoint_mode = "vip" @@ -545,6 +557,8 @@ class DockerService(DockerBaseClass): self.constraints = [] self.networks = [] self.publish = [] + self.constraints = [] + self.placement_preferences = None self.replicas = -1 self.service_id = False self.service_version = False @@ -577,6 +591,7 @@ class DockerService(DockerBaseClass): 'log_driver_options': self.log_driver_options, 'publish': self.publish, 'constraints': self.constraints, + 'placement_preferences': self.placement_preferences, 'labels': self.labels, 'container_labels': self.container_labels, 'mode': self.mode, @@ -601,6 +616,7 @@ class DockerService(DockerBaseClass): def from_ansible_params(ap, old_service): s = DockerService() s.constraints = ap['constraints'] + s.placement_preferences = ap['placement_preferences'] s.image = ap['image'] s.args = ap['args'] s.endpoint_mode = ap['endpoint_mode'] @@ -726,6 +742,8 @@ class DockerService(DockerBaseClass): differences.add('args', parameter=self.args, active=os.args) if self.constraints != os.constraints: differences.add('constraints', parameter=self.constraints, active=os.constraints) + if self.placement_preferences is not None and self.placement_preferences != os.placement_preferences: + differences.add('placement_preferences', parameter=self.placement_preferences, active=os.placement_preferences) if self.labels != os.labels: differences.add('labels', parameter=self.labels, active=os.labels) if self.limit_cpu != os.limit_cpu: @@ -864,7 +882,14 @@ class DockerService(DockerBaseClass): log_driver = types.DriverConfig(name=self.log_driver, options=self.log_driver_options) - placement = types.Placement(constraints=self.constraints) + placement = types.Placement( + constraints=self.constraints, + preferences=[ + {key.title(): {"SpreadDescriptor": value}} + for preference in self.placement_preferences + for key, value in preference.items() + ] if self.placement_preferences else None, + ) restart_policy = types.RestartPolicy( condition=self.restart_policy, @@ -979,7 +1004,17 @@ class DockerServiceManager(): ds.hostname = task_template_data['ContainerSpec'].get('Hostname', '') ds.tty = task_template_data['ContainerSpec'].get('TTY', False) if 'Placement' in task_template_data.keys(): - ds.constraints = task_template_data['Placement'].get('Constraints', []) + placement = task_template_data['Placement'] + ds.constraints = placement.get('Constraints', []) + placement_preferences = [] + for preference in placement.get('Preferences', []): + placement_preferences.append( + dict( + (key.lower(), value["SpreadDescriptor"]) + for key, value in preference.items() + ) + ) + ds.placement_preferences = placement_preferences or None restart_policy_data = task_template_data.get('RestartPolicy', None) if restart_policy_data: @@ -1193,6 +1228,7 @@ def main(): mode=dict(type='str', required=False, choices=('ingress', 'host')), )), constraints=dict(default=[], type='list'), + placement_preferences=dict(default=None, type='list'), tty=dict(default=False, type='bool'), dns=dict(default=[], type='list'), dns_search=dict(default=[], type='list'), diff --git a/test/integration/targets/docker_swarm_service/tasks/tests/options.yml b/test/integration/targets/docker_swarm_service/tasks/tests/options.yml index 3cfd2dbac0a..cf563c05318 100644 --- a/test/integration/targets/docker_swarm_service/tasks/tests/options.yml +++ b/test/integration/targets/docker_swarm_service/tasks/tests/options.yml @@ -950,6 +950,46 @@ - networks_2 is not changed - networks_3 is changed +#################################################################### +## placement_preferences ########################################### +#################################################################### + +- name: placement_preferences + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + placement_preferences: + - spread: "node.labels.test" + register: placement_preferences_1 + +- name: placement_preferences (idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + placement_preferences: + - spread: "node.labels.test" + register: placement_preferences_2 + +- name: placement_preferences (change) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + placement_preferences: + - spread: "node.labels.test2" + register: placement_preferences_3 + +- name: cleanup + docker_swarm_service: + name: "{{ service_name }}" + state: absent + diff: no + +- assert: + that: + - placement_preferences_1 is changed + - placement_preferences_2 is not changed + - placement_preferences_3 is changed + #################################################################### ## publish ######################################################### #################################################################### diff --git a/test/integration/targets/docker_swarm_service/vars/main.yml b/test/integration/targets/docker_swarm_service/vars/main.yml index ca8abea7d6b..abc5c63a5fa 100644 --- a/test/integration/targets/docker_swarm_service/vars/main.yml +++ b/test/integration/targets/docker_swarm_service/vars/main.yml @@ -21,6 +21,7 @@ service_expected_output: mode: global mounts: [] networks: [] + placement_preferences: null publish: - {mode: null, protocol: tcp, published_port: 60001, target_port: 60001} - {mode: null, protocol: udp, published_port: 60001, target_port: 60001}