From 153e99676073ca35e3ba469a5fa4ae6e11782e03 Mon Sep 17 00:00:00 2001 From: Hannes Ljungberg Date: Sun, 10 Feb 2019 15:17:59 +0100 Subject: [PATCH] docker_swarm_service: Remove defaults (#51216) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Drop descriptions of docker-py constraints <=3.0.0 * Properly indent documentation * Clearer examples * Spelling * Fix example * Revert "Drop descriptions of docker-py constraints <=3.0.0" This reverts commit 8106215af62ae5880c3b748cec562a85f2247bdc. # Conflicts: # lib/ansible/modules/cloud/docker/docker_swarm_service.py * Add min versions for placement preferences * Add endpoint_mode and publish to option_minimal_versions * Restrict update max_failure_ratio and monitor versions * Remove defaults and only pass required arguments * Fix indentation * Fix args documentation * Fix replicas documentation Co-Authored-By: hannseman * Add type to all documented options * Add suboptions for mounts, secrets and configs * Add suboptions to argument spec * Remove redundant validation and casting * Don’t default Spec.EndpointSpec.Mode to vip * Use single quotes as string literal * Catch ImportError explicitly * Move init to top of class * Align closing brackets * Spelling * Import LooseVersion * Documentation fixes * Documentation fix * Documentation fixes * Remove required: false and default=None * Defaults for update_delay and update_parallelism * Set correct key for default * Handle empty idempotency * Clearer image documentation Co-Authored-By: hannseman * Clearer constraint documentation Co-Authored-By: hannseman * Better documentation of corresponding service opts --- .../cloud/docker/docker_swarm_service.py | 1514 +++++++++-------- .../tasks/tests/options.yml | 262 ++- .../docker_swarm_service/vars/main.yml | 48 +- 3 files changed, 1122 insertions(+), 702 deletions(-) diff --git a/lib/ansible/modules/cloud/docker/docker_swarm_service.py b/lib/ansible/modules/cloud/docker/docker_swarm_service.py index 430fae1ecae..687872d1de3 100644 --- a/lib/ansible/modules/cloud/docker/docker_swarm_service.py +++ b/lib/ansible/modules/cloud/docker/docker_swarm_service.py @@ -20,320 +20,376 @@ version_added: "2.7" options: name: required: true - description: - - Service name - image: type: str - required: true description: - - Service image path and tag. - Maps docker service IMAGE parameter. + - Service name. + - Corresponds to the C(--name) option of C(docker service create). + image: + required: true + type: str + description: + - Service image path and tag. + - Corresponds to the C(IMAGE) parameter of C(docker service create). resolve_image: type: bool - required: false default: true description: - If the current image digest should be resolved from registry and updated if changed. version_added: 2.8 state: required: true + type: str default: present description: - - Service state. + - Service state. choices: - - present - - absent + - present + - absent args: - required: false - default: [] description: - - List comprised of the command and the arguments to be run inside - - the container + - List arguments to be passed to the container. + - Corresponds to the C(ARG) parameter of C(docker service create). command: - required: false description: - - Command to execute when the container starts. - A command may be either a string or a list or a list of strings. + - Command to execute when the container starts. + - A command may be either a string or a list or a list of strings. + - Corresponds to the C(COMMAND) parameter of C(docker service create). version_added: 2.8 constraints: - required: false - default: [] - 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. + - List of the service constraints. + - Corresponds to the C(--constraint) option of C(docker service create). + placement_preferences: + type: list + description: + - List of the placement preferences as key value pairs. + - Corresponds to the C(--placement-pref) option of C(docker service create). + - Requires API version >= 1.27. version_added: 2.8 hostname: - required: false - default: "" + type: str description: - - Container hostname - - Maps docker service --hostname option. - - Requires API version >= 1.25 + - Container hostname. + - Corresponds to the C(--hostname) option of C(docker service create). + - Requires API version >= 1.25. tty: - required: false type: bool - default: False description: - - Allocate a pseudo-TTY - - Maps docker service --tty option. - - Requires API version >= 1.25 + - Allocate a pseudo-TTY. + - Corresponds to the C(--tty) option of C(docker service create). + - Requires API version >= 1.25. dns: - required: false - default: [] + type: list description: - - List of custom DNS servers. - - Maps docker service --dns option. - - Requires API version >= 1.25 + - List of custom DNS servers. + - Corresponds to the C(--dns) option of C(docker service create). + - Requires API version >= 1.25. dns_search: - required: false - default: [] + type: list description: - - List of custom DNS search domains. - - Maps docker service --dns-search option. - - Requires API version >= 1.25 + - List of custom DNS search domains. + - Corresponds to the C(--dns-search) option of C(docker service create). + - Requires API version >= 1.25. dns_options: - required: false - default: [] + type: list description: - - List of custom DNS options. - - Maps docker service --dns-option option. - - Requires API version >= 1.25 + - List of custom DNS options. + - Corresponds to the C(--dns-option) option of C(docker service create). + - Requires API version >= 1.25. force_update: - required: false type: bool - default: False + default: false description: - - Force update even if no changes require it. - - Maps to docker service update --force option. - - Requires API version >= 1.25 + - Force update even if no changes require it. + - Corresponds to the C(--force) option of C(docker service update). + - Requires API version >= 1.25. labels: - required: false type: dict description: - - Dictionary of key value pairs. - - Maps docker service --label option. + - Dictionary of key value pairs. + - Corresponds to the C(--label) option of C(docker service create). container_labels: - required: false type: dict description: - - Dictionary of key value pairs. - - Maps docker service --container-label option. + - Dictionary of key value pairs. + - Corresponds to the C(--container-label) option of C(docker service create). endpoint_mode: type: str description: - - Service endpoint mode. - - Maps docker service --endpoint-mode option. + - Service endpoint mode. + - Corresponds to the C(--endpoint-mode) option of C(docker service create). + - Requires API version >= 1.25. choices: - - vip - - dnsrr + - vip + - dnsrr env: - required: false - default: [] + type: list description: - - List of the service environment variables. - - Maps docker service --env option. + - List of the service environment variables. + - Corresponds to the C(--env) option of C(docker service create). log_driver: - required: false - default: json-file + type: str description: - - Configure the logging driver for a service + - Configure the logging driver for a service. + - Corresponds to the C(--log-driver) option of C(docker service create). log_driver_options: - required: false - default: [] + type: dict description: - - Options for service logging driver + - Options for service logging driver. + - Corresponds to the C(--log-opt) option of C(docker service create). limit_cpu: - required: false - default: 0.000 + type: float description: - - Service CPU limit. 0 equals no limit. - - Maps docker service --limit-cpu option. + - Service CPU limit. C(0) equals no limit. + - Corresponds to the C(--limit-cpu) option of C(docker service create). reserve_cpu: - required: false - default: 0.000 + type: float description: - - Service CPU reservation. 0 equals no reservation. - - Maps docker service --reserve-cpu option. + - Service CPU reservation. C(0) equals no reservation. + - Corresponds to the C(--reserve-cpu) option of C(docker service create). limit_memory: - required: false - default: 0 + type: str description: - - "Service memory limit (format: C([])). 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)." - - 0 equals no limit. - - Omitting the unit defaults to bytes. - - Maps docker service --limit-memory option. + - "Service memory limit (format: C([])). 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)." + - C(0) equals no limit. + - Omitting the unit defaults to bytes. + - Corresponds to the C(--limit-memory) option of C(docker service create). reserve_memory: - required: false - default: 0 + type: str description: - - "Service memory reservation (format: C([])). 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)." - - 0 equals no reservation. - - Omitting the unit defaults to bytes. - - Maps docker service --reserve-memory option. + - "Service memory reservation (format: C([])). 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)." + - C(0) equals no reservation. + - Omitting the unit defaults to bytes. + - Corresponds to the C(--reserve-memory) option of C(docker service create). mode: - required: false + type: str default: replicated description: - - Service replication mode. - - Maps docker service --mode option. + - Service replication mode. + - Corresponds to the C(--mode) option of C(docker service create). mounts: - required: false - description: - - List of dictionaries describing the service mounts. - - Every item must be a dictionary exposing the keys source, target, type (defaults to 'bind'), readonly (defaults to false) - - Maps docker service --mount option. - default: [] - secrets: - required: false - description: - - List of dictionaries describing the service secrets. - - Every item must be a dictionary exposing the keys secret_id, secret_name, filename, uid (defaults to 0), gid (defaults to 0), mode (defaults to 0o444) - - Maps docker service --secret option. - - Requires API version >= 1.25 - default: [] - configs: - required: false - description: - - List of dictionaries describing the service configs. - - Every item must be a dictionary exposing the keys config_id, config_name, filename, uid (defaults to 0), gid (defaults to 0), mode (defaults to 0o444) - - Maps docker service --config option. - - Requires API version >= 1.30 - default: null - networks: - required: false - default: [] - description: - - List of the service networks names. - - Maps docker service --network option. - publish: type: list - required: false - default: [] description: - - List of dictionaries describing the service published ports. - - Only used with api_version >= 1.25 + - List of dictionaries describing the service mounts. + - Corresponds to the C(--mount) option of C(docker service create). suboptions: - published_port: - type: int - required: true - description: - - The port to make externally available. - target_port: - type: int - required: true - description: - - The port inside the container to expose. - protocol: - type: str - required: false - default: tcp - description: - - What protocol to use. - choices: - - tcp - - udp + source: + type: str + required: true + description: + - Mount source (e.g. a volume name or a host path). + target: + type: str + required: true + description: + - Container path. + type: + type: str + default: bind + choices: + - bind + - volume + - tmpfs + description: + - The mount type. + readonly: + type: bool + default: false + description: + - Whether the mount should be read-only. + secrets: + type: list + description: + - List of dictionaries describing the service secrets. + - Corresponds to the C(--secret) option of C(docker service create). + - Requires API version >= 1.25. + suboptions: + secret_id: + type: str + required: true + description: + - Secret's ID. + secret_name: + type: str + required: true + description: + - Secret's name as defined at its creation. + filename: + type: str + description: + - Name of the file containing the secret. Defaults to the I(secret_name) if not specified. + uid: + type: int + default: 0 + description: + - UID of the secret file's owner. + gid: + type: int + default: 0 + description: + - GID of the secret file's group. + mode: + type: int + default: 0o444 + description: + - File access mode inside the container. + configs: + type: list + description: + - List of dictionaries describing the service configs. + - Corresponds to the C(--config) option of C(docker service create). + - Requires API version >= 1.30. + suboptions: + config_id: + type: str + required: true + description: + - Config's ID. + config_name: + type: str + required: true + description: + - Config's name as defined at its creation. + filename: + type: str + required: true + description: + - Name of the file containing the config. Defaults to the I(config_name) if not specified. + uid: + type: int + default: 0 + description: + - UID of the config file's owner. + gid: + type: int + default: 0 + description: + - GID of the config file's group. + mode: + type: str + default: "0o444" + description: + - File access mode inside the container. + networks: + type: list + description: + - List of the service networks names. + - Corresponds to the C(--network) option of C(docker service create). + publish: + type: list + description: + - List of dictionaries describing the service published ports. + - Corresponds to the C(--publish) option of C(docker service create). + - Requires API version >= 1.25. + suboptions: + published_port: + type: int + required: true + description: + - The port to make externally available. + target_port: + type: int + required: true + description: + - The port inside the container to expose. + protocol: + type: str + default: tcp + description: + - What protocol to use. + choices: + - tcp + - udp mode: type: str - required: false description: - What publish mode to use. - - Requires API version >= 1.32 and docker python library >= 3.0.0 + - Requires API version >= 1.32. choices: - ingress - host replicas: - required: false + type: int default: -1 description: - - Number of containers instantiated in the service. Valid only if ``mode=='replicated'``. - - If set to -1, and service is not present, service replicas will be set to 1. - - If set to -1, and service is present, service replicas will be unchanged. - - Maps docker service --replicas option. + - Number of containers instantiated in the service. Valid only if I(mode) is C(replicated). + - If set to C(-1), and service is not present, service replicas will be set to C(1). + - If set to C(-1), and service is present, service replicas will be unchanged. + - Corresponds to the C(--replicas) option of C(docker service create). restart_policy: - required: false - default: none + type: str description: - - Restart condition of the service. - - Maps docker service --restart-condition option. + - Restart condition of the service. + - Corresponds to the C(--restart-condition) option of C(docker service create). choices: - - none - - on-failure - - any + - none + - on-failure + - any restart_policy_attempts: - required: false - default: 0 + type: int description: - - Maximum number of service restarts. - - Maps docker service --restart-max-attempts option. + - Maximum number of service restarts. + - Corresponds to the C(--restart-condition) option of C(docker service create). restart_policy_delay: - required: false - default: 0 + type: int description: - - Delay between restarts. - - Maps docker service --restart-delay option. + - Delay between restarts. + - Corresponds to the C(--restart-delay) option of C(docker service create). restart_policy_window: - required: false - default: 0 + type: int description: - - Restart policy evaluation window. - - Maps docker service --restart-window option. + - Restart policy evaluation window. + - Corresponds to the C(--restart-window) option of C(docker service create). update_delay: - required: false + type: int default: 10 description: - - Rolling update delay - - Maps docker service --update-delay option + - Rolling update delay in nanoseconds. + - Corresponds to the C(--update-delay) option of C(docker service create). update_parallelism: - required: false + type: int default: 1 description: - - Rolling update parallelism - - Maps docker service --update-parallelism option + - Rolling update parallelism. + - Corresponds to the C(--update-parallelism) option of C(docker service create). update_failure_action: - required: false - default: continue + type: int description: - - Action to take in case of container failure - - Maps to docker service --update-failure-action option + - Action to take in case of container failure. + - Corresponds to the C(--update-failure-action) option of C(docker service create). choices: - - continue - - pause + - continue + - pause update_monitor: - required: false - default: 5000000000 + type: int description: - - Time to monitor updated tasks for failures, in nanoseconds. - - Maps to docker service --update-monitor option + - Time to monitor updated tasks for failures, in nanoseconds. + - Corresponds to the C(--update-monitor) option of C(docker service create). + - Requires API version >= 1.25. update_max_failure_ratio: - required: false - default: 0.00 + type: float description: - - Fraction of tasks that may fail during an update before the failure action is invoked - - Maps to docker service --update-max-failure-ratio + - Fraction of tasks that may fail during an update before the failure action is invoked. + - Corresponds to the C(--update-max-failure-ratio) option of C(docker service create). + - Requires API version >= 1.25. update_order: - required: false - default: null + type: str description: - - Specifies the order of operations when rolling out an updated task. - - Maps to docker service --update-order - - Requires API version >= 1.29 + - Specifies the order of operations when rolling out an updated task. + - Corresponds to the C(--update-order) option of C(docker service create). + - Requires API version >= 1.29. user: type: str - required: false description: - - Sets the username or UID used for the specified command. - - Before Ansible 2.8, the default value for this option was C(root). - The default has been removed so that the user defined in the image is used if no user is specified here. + - Sets the username or UID used for the specified command. + - Before Ansible 2.8, the default value for this option was C(root). + - The default has been removed so that the user defined in the image is used if no user is specified here. + - Corresponds to the C(--user) option of C(docker service create). extends_documentation_fragment: - docker - docker.docker_py_2_documentation @@ -401,7 +457,7 @@ changes: returned: always description: - List of changed service attributes if a service has been altered, - [] otherwhise + [] otherwise type: list sample: ['container_labels', 'replicas'] rebuilt: @@ -413,119 +469,94 @@ rebuilt: ''' EXAMPLES = ''' -- name: define myservice - docker_swarm_service: - name: myservice - image: "alpine" - args: - - "sleep" - - "3600" - mounts: - - source: /tmp/ - target: /remote_tmp/ - type: bind - env: - - "ENVVAR1=envvar1" - log_driver: fluentd - log_driver_options: - fluentd-address: "127.0.0.1:24224" - fluentd-async-connect: true - tag: "{{.Name}}/{{.ID}}" - restart_policy: any - restart_policy_attempts: 5 - restart_policy_window: 30 - register: dss_out1 -- name: change myservice.env - docker_swarm_service: - name: myservice - image: "alpine" - args: - - "sleep" - - "7200" - mounts: - - source: /tmp/ - target: /remote_tmp/ - type: bind - env: - - "ENVVAR1=envvar1" - restart_policy: any - restart_policy_attempts: 5 - restart_policy_window: 30 - register: dss_out2 -- name: test for changed myservice facts - fail: - msg: unchanged service - when: "{{ dss_out1 == dss_out2 }}" -- name: change myservice.image - docker_swarm_service: - name: myservice - image: "alpine:edge" - args: - - "sleep" - - "7200" - mounts: - - source: /tmp/ - target: /remote_tmp/ - type: bind - env: - - "ENVVAR1=envvar1" - restart_policy: any - restart_policy_attempts: 5 - restart_policy_window: 30 - register: dss_out3 -- name: test for changed myservice facts - fail: - msg: unchanged service - when: "{{ dss_out2 == dss_out3 }}" -- name: remove mount - docker_swarm_service: - name: myservice - image: "alpine:edge" - args: - - "sleep" - - "7200" - env: - - "ENVVAR1=envvar1" - restart_policy: any - restart_policy_attempts: 5 - restart_policy_window: 30 - register: dss_out4 -- name: test for changed myservice facts - fail: - msg: unchanged service - when: "{{ dss_out3 == dss_out4 }}" -- name: keep service as it is - docker_swarm_service: - name: myservice - image: "alpine:edge" - args: - - "sleep" - - "7200" - env: - - "ENVVAR1=envvar1" - restart_policy: any - restart_policy_attempts: 5 - restart_policy_window: 30 - register: dss_out5 -- name: test for changed service facts - fail: - msg: changed service - when: "{{ dss_out5 != dss_out5 }}" -- name: remove myservice - 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" +- name: Set arguments + docker_swarm_service: + name: myservice + image: alpine + args: + - "sleep" + - "3600" + +- name: Set a bind mount + docker_swarm_service: + name: myservice + image: alpine + mounts: + - source: /tmp/ + target: /remote_tmp/ + type: bind + +- name: Set environment variables + docker_swarm_service: + name: myservice + image: alpine + env: + - "ENVVAR1=envvar1" + - "ENVVAR2=envvar2" + +- name: Set fluentd logging + docker_swarm_service: + name: myservice + image: alpine + log_driver: fluentd + log_driver_options: + fluentd-address: "127.0.0.1:24224" + fluentd-async-connect: true + tag: myservice + +- name: Set restart policies + docker_swarm_service: + name: myservice + image: alpine + restart_policy: any + restart_policy_attempts: 5 + restart_policy_delay: 5 + restart_policy_window: 30 + +- name: Set placement preferences + docker_swarm_service: + name: myservice + image: alpine:edge + placement_preferences: + - spread: "node.labels.mylabel" + +- name: Set configs + docker_swarm_service: + name: myservice + image: alpine:edge + configs: + - config_id: myconfig_id + config_name: myconfig_name + filename: "/tmp/config.txt" + +- name: Set networks + docker_swarm_service: + name: myservice + image: alpine:edge + networks: + - mynetwork + +- name: Set secrets + docker_swarm_service: + name: myservice + image: alpine:edge + secrets: + - secret_id: mysecret_id + secret_name: mysecret_name + filename: "/run/secrets/secret.txt" + +- name: Remove service + docker_swarm_service: + name: myservice + state: absent ''' import time import shlex import operator + +from distutils.version import LooseVersion + from ansible.module_utils.docker.common import ( AnsibleDockerClient, DifferenceTracker, @@ -536,11 +567,10 @@ from ansible.module_utils.six import string_types from ansible.module_utils._text import to_text try: - from distutils.version import LooseVersion from docker import types from docker.utils import parse_repository_tag from docker.errors import APIError, DockerException -except Exception: +except ImportError: # missing docker-py handled in ansible.module_utils.docker.common pass @@ -550,32 +580,31 @@ class DockerService(DockerBaseClass): super(DockerService, self).__init__() self.image = "" self.command = None - self.args = [] + self.args = None self.endpoint_mode = None - self.dns = [] - self.hostname = "" - self.tty = False - self.dns_search = [] - self.dns_options = [] - self.env = [] + self.dns = None + self.hostname = None + self.tty = None + self.dns_search = None + self.dns_options = None + self.env = None self.force_update = None - self.log_driver = "json-file" - self.log_driver_options = {} - self.labels = {} - self.container_labels = {} - self.limit_cpu = 0.000 - self.limit_memory = 0 - self.reserve_cpu = 0.000 - self.reserve_memory = 0 + self.log_driver = None + self.log_driver_options = None + self.labels = None + self.container_labels = None + self.limit_cpu = None + self.limit_memory = None + self.reserve_cpu = None + self.reserve_memory = None self.mode = "replicated" self.user = None - self.mounts = [] - self.configs = [] - self.secrets = [] - self.constraints = [] - self.networks = [] - self.publish = [] - self.constraints = [] + self.mounts = None + self.configs = None + self.secrets = None + self.constraints = None + self.networks = None + self.publish = None self.placement_preferences = None self.replicas = -1 self.service_id = False @@ -585,10 +614,10 @@ class DockerService(DockerBaseClass): self.restart_policy_delay = None self.restart_policy_window = None self.update_delay = None - self.update_parallelism = 1 - self.update_failure_action = "continue" - self.update_monitor = 5000000000 - self.update_max_failure_ratio = 0.00 + self.update_parallelism = None + self.update_failure_action = None + self.update_monitor = None + self.update_max_failure_ratio = None self.update_order = None def get_facts(self): @@ -629,7 +658,8 @@ class DockerService(DockerBaseClass): 'update_failure_action': self.update_failure_action, 'update_monitor': self.update_monitor, 'update_max_failure_ratio': self.update_max_failure_ratio, - 'update_order': self.update_order} + 'update_order': self.update_order + } @staticmethod def from_ansible_params(ap, old_service, image_digest): @@ -710,54 +740,51 @@ class DockerService(DockerBaseClass): try: setattr(s, param_name, human_to_bytes(ap[param_name])) except ValueError as exc: - raise Exception("Failed to convert %s to bytes: %s" % (param_name, exc)) + raise Exception('Failed to convert %s to bytes: %s' % (param_name, exc)) - s.publish = [] - for param_p in ap['publish']: - service_p = {} - service_p['protocol'] = param_p['protocol'] - service_p['mode'] = param_p['mode'] - service_p['published_port'] = int(param_p['published_port']) - service_p['target_port'] = int(param_p['target_port']) - if service_p['protocol'] not in ['tcp', 'udp']: - raise ValueError("got publish.protocol '%s', valid values:'tcp', 'udp'" % - service_p['protocol']) - if service_p['mode'] not in [None, 'ingress', 'host']: - raise ValueError("got publish.mode '%s', valid values:'ingress', 'host'" % - service_p['mode']) - s.publish.append(service_p) - s.mounts = [] - for param_m in ap['mounts']: - service_m = {} - service_m['readonly'] = bool(param_m.get('readonly', False)) - service_m['type'] = param_m.get('type', 'bind') - service_m['source'] = param_m['source'] - service_m['target'] = param_m['target'] - s.mounts.append(service_m) + if ap['publish'] is not None: + s.publish = [] + for param_p in ap['publish']: + service_p = {} + service_p['protocol'] = param_p['protocol'] + service_p['mode'] = param_p['mode'] + service_p['published_port'] = param_p['published_port'] + service_p['target_port'] = param_p['target_port'] + s.publish.append(service_p) + + if ap['mounts'] is not None: + s.mounts = [] + for param_m in ap['mounts']: + service_m = {} + service_m['readonly'] = param_m['readonly'] + service_m['type'] = param_m['type'] + service_m['source'] = param_m['source'] + service_m['target'] = param_m['target'] + s.mounts.append(service_m) - s.configs = None if ap['configs'] is not None: s.configs = [] for param_m in ap['configs']: service_c = {} service_c['config_id'] = param_m['config_id'] - service_c['config_name'] = str(param_m['config_name']) - service_c['filename'] = param_m.get('filename', service_c['config_name']) - service_c['uid'] = int(param_m.get('uid', "0")) - service_c['gid'] = int(param_m.get('gid', "0")) - service_c['mode'] = param_m.get('mode', 0o444) + service_c['config_name'] = param_m['config_name'] + service_c['filename'] = param_m['filename'] or service_c['config_name'] + service_c['uid'] = param_m['uid'] + service_c['gid'] = param_m['gid'] + service_c['mode'] = param_m['mode'] s.configs.append(service_c) - s.secrets = [] - for param_m in ap['secrets']: - service_s = {} - service_s['secret_id'] = param_m['secret_id'] - service_s['secret_name'] = str(param_m['secret_name']) - service_s['filename'] = param_m.get('filename', service_s['secret_name']) - service_s['uid'] = int(param_m.get('uid', "0")) - service_s['gid'] = int(param_m.get('gid', "0")) - service_s['mode'] = param_m.get('mode', 0o444) - s.secrets.append(service_s) + if ap['secrets'] is not None: + s.secrets = [] + for param_m in ap['secrets']: + service_s = {} + service_s['secret_id'] = param_m['secret_id'] + service_s['secret_name'] = param_m['secret_name'] + service_s['filename'] = param_m['filename'] or service_s['secret_name'] + service_s['uid'] = param_m['uid'] + service_s['gid'] = param_m['gid'] + service_s['mode'] = param_m['mode'] + s.secrets.append(service_s) return s def compare(self, os): @@ -766,65 +793,65 @@ class DockerService(DockerBaseClass): force_update = False if self.endpoint_mode is not None and self.endpoint_mode != os.endpoint_mode: differences.add('endpoint_mode', parameter=self.endpoint_mode, active=os.endpoint_mode) - if self.env != os.env: + if self.env is not None and self.env != (os.env or []): differences.add('env', parameter=self.env, active=os.env) - if self.log_driver != os.log_driver: + if self.log_driver is not None and self.log_driver != os.log_driver: differences.add('log_driver', parameter=self.log_driver, active=os.log_driver) - if self.log_driver_options != os.log_driver_options: + if self.log_driver_options is not None and self.log_driver_options != (os.log_driver_options or {}): differences.add('log_opt', parameter=self.log_driver_options, active=os.log_driver_options) if self.mode != os.mode: needs_rebuild = True differences.add('mode', parameter=self.mode, active=os.mode) - if self.mounts != os.mounts: + if self.mounts is not None and self.mounts != (os.mounts or []): differences.add('mounts', parameter=self.mounts, active=os.mounts) - if self.configs is not None and self.configs != os.configs: + if self.configs is not None and self.configs != (os.configs or []): differences.add('configs', parameter=self.configs, active=os.configs) - if self.secrets != os.secrets: + if self.secrets is not None and self.secrets != (os.secrets or []): differences.add('secrets', parameter=self.secrets, active=os.secrets) - if self.networks != os.networks: + if self.networks is not None and self.networks != (os.networks or []): differences.add('networks', parameter=self.networks, active=os.networks) needs_rebuild = True if self.replicas != os.replicas: differences.add('replicas', parameter=self.replicas, active=os.replicas) - if self.command is not None and self.command != os.command: + if self.command is not None and self.command != (os.command or []): differences.add('command', parameter=self.command, active=os.command) - if self.args != os.args: + if self.args is not None and self.args != (os.args or []): differences.add('args', parameter=self.args, active=os.args) - if self.constraints != os.constraints: + if self.constraints is not None and self.constraints != (os.constraints or []): differences.add('constraints', parameter=self.constraints, active=os.constraints) - if self.placement_preferences is not None and self.placement_preferences != os.placement_preferences: + if self.placement_preferences is not None and self.placement_preferences != (os.placement_preferences or []): differences.add('placement_preferences', parameter=self.placement_preferences, active=os.placement_preferences) - if self.labels != os.labels: + if self.labels is not None and self.labels != (os.labels or {}): differences.add('labels', parameter=self.labels, active=os.labels) - if self.limit_cpu != os.limit_cpu: + if self.limit_cpu is not None and self.limit_cpu != os.limit_cpu: differences.add('limit_cpu', parameter=self.limit_cpu, active=os.limit_cpu) - if self.limit_memory != os.limit_memory: + if self.limit_memory is not None and self.limit_memory != os.limit_memory: differences.add('limit_memory', parameter=self.limit_memory, active=os.limit_memory) - if self.reserve_cpu != os.reserve_cpu: + if self.reserve_cpu is not None and self.reserve_cpu != os.reserve_cpu: differences.add('reserve_cpu', parameter=self.reserve_cpu, active=os.reserve_cpu) - if self.reserve_memory != os.reserve_memory: + if self.reserve_memory is not None and self.reserve_memory != os.reserve_memory: differences.add('reserve_memory', parameter=self.reserve_memory, active=os.reserve_memory) - if self.container_labels != os.container_labels: + if self.container_labels is not None and self.container_labels != (os.container_labels or {}): differences.add('container_labels', parameter=self.container_labels, active=os.container_labels) if self.has_publish_changed(os.publish): differences.add('publish', parameter=self.publish, active=os.publish) - if self.restart_policy != os.restart_policy: + if self.restart_policy is not None and self.restart_policy != os.restart_policy: differences.add('restart_policy', parameter=self.restart_policy, active=os.restart_policy) - if self.restart_policy_attempts != os.restart_policy_attempts: + if self.restart_policy_attempts is not None and self.restart_policy_attempts != os.restart_policy_attempts: differences.add('restart_policy_attempts', parameter=self.restart_policy_attempts, active=os.restart_policy_attempts) - if self.restart_policy_delay != os.restart_policy_delay: + if self.restart_policy_delay is not None and self.restart_policy_delay != os.restart_policy_delay: differences.add('restart_policy_delay', parameter=self.restart_policy_delay, active=os.restart_policy_delay) - if self.restart_policy_window != os.restart_policy_window: + if self.restart_policy_window is not None and self.restart_policy_window != os.restart_policy_window: differences.add('restart_policy_window', parameter=self.restart_policy_window, active=os.restart_policy_window) - if self.update_delay != os.update_delay: + if self.update_delay is not None and self.update_delay != os.update_delay: differences.add('update_delay', parameter=self.update_delay, active=os.update_delay) - if self.update_parallelism != os.update_parallelism: + if self.update_parallelism is not None and self.update_parallelism != os.update_parallelism: differences.add('update_parallelism', parameter=self.update_parallelism, active=os.update_parallelism) - if self.update_failure_action != os.update_failure_action: + if self.update_failure_action is not None and self.update_failure_action != os.update_failure_action: differences.add('update_failure_action', parameter=self.update_failure_action, active=os.update_failure_action) - if self.update_monitor != os.update_monitor: + if self.update_monitor is not None and self.update_monitor != os.update_monitor: differences.add('update_monitor', parameter=self.update_monitor, active=os.update_monitor) - if self.update_max_failure_ratio != os.update_max_failure_ratio: + if self.update_max_failure_ratio is not None and self.update_max_failure_ratio != os.update_max_failure_ratio: differences.add('update_max_failure_ratio', parameter=self.update_max_failure_ratio, active=os.update_max_failure_ratio) if self.update_order is not None and self.update_order != os.update_order: differences.add('update_order', parameter=self.update_order, active=os.update_order) @@ -833,21 +860,24 @@ class DockerService(DockerBaseClass): differences.add('image', parameter=self.image, active=change) if self.user and self.user != os.user: differences.add('user', parameter=self.user, active=os.user) - if self.dns != os.dns: + if self.dns is not None and self.dns != (os.dns or []): differences.add('dns', parameter=self.dns, active=os.dns) - if self.dns_search != os.dns_search: + if self.dns_search is not None and self.dns_search != (os.dns_search or []): differences.add('dns_search', parameter=self.dns_search, active=os.dns_search) - if self.dns_options != os.dns_options: + if self.dns_options is not None and self.dns_options != (os.dns_options or []): differences.add('dns_options', parameter=self.dns_options, active=os.dns_options) - if self.hostname != os.hostname: + if self.hostname is not None and self.hostname != os.hostname: differences.add('hostname', parameter=self.hostname, active=os.hostname) - if self.tty != os.tty: + if self.tty is not None and self.tty != os.tty: differences.add('tty', parameter=self.tty, active=os.tty) if self.force_update: force_update = True return not differences.empty or force_update, differences, needs_rebuild, force_update def has_publish_changed(self, old_publish): + if self.publish is None: + return False + old_publish = old_publish or [] if len(self.publish) != len(old_publish): return True publish_sorter = operator.itemgetter('published_port', 'target_port', 'protocol') @@ -882,20 +912,25 @@ class DockerService(DockerBaseClass): 'configs': self.configs, 'secrets': self.secrets, 'networks': self.networks, - 'replicas': self.replicas}) + 'replicas': self.replicas + }) - def generate_docker_py_service_description(self, name, docker_networks): - 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']) - ) + def build_container_spec(self): + mounts = None + 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'] + ) + ) configs = None - if self.configs: + if self.configs is not None: configs = [] for config_config in self.configs: configs.append( @@ -909,116 +944,207 @@ class DockerService(DockerBaseClass): ) ) - secrets = [] - for secret_config in self.secrets: - secrets.append( - types.SecretReference( - secret_id=secret_config['secret_id'], - secret_name=secret_config['secret_name'], - filename=secret_config.get('filename'), - uid=secret_config.get('uid'), - gid=secret_config.get('gid'), - mode=secret_config.get('mode') + secrets = None + if self.secrets is not None: + secrets = [] + for secret_config in self.secrets: + secrets.append( + types.SecretReference( + secret_id=secret_config['secret_id'], + secret_name=secret_config['secret_name'], + filename=secret_config.get('filename'), + uid=secret_config.get('uid'), + gid=secret_config.get('gid'), + mode=secret_config.get('mode') + ) ) - ) - dns_config = types.DNSConfig( - nameservers=self.dns, - search=self.dns_search, - options=self.dns_options - ) + dns_config_args = {} + if self.dns is not None: + dns_config_args['nameservers'] = self.dns + if self.dns_search is not None: + dns_config_args['search'] = self.dns_search + if self.dns_options is not None: + dns_config_args['options'] = self.dns_options + dns_config = types.DNSConfig(**dns_config_args) if dns_config_args else None - cspec = types.ContainerSpec( - image=self.image, - command=self.command, - args=self.args, - hostname=self.hostname, - env=self.env, - user=self.user, - labels=self.container_labels, - mounts=mounts, - secrets=secrets, - tty=self.tty, - dns_config=dns_config, - configs=configs - ) + container_spec_args = {} + if self.command is not None: + container_spec_args['command'] = self.command + if self.args is not None: + container_spec_args['args'] = self.args + if self.env is not None: + container_spec_args['env'] = self.env + if self.user is not None: + container_spec_args['user'] = self.user + if self.container_labels is not None: + container_spec_args['labels'] = self.container_labels + if self.hostname is not None: + container_spec_args['hostname'] = self.hostname + if self.tty is not None: + container_spec_args['tty'] = self.tty + if secrets is not None: + container_spec_args['secrets'] = secrets + if self.mounts is not None: + container_spec_args['mounts'] = mounts + if dns_config is not None: + container_spec_args['dns_config'] = dns_config + if configs is not None: + container_spec_args['configs'] = configs - log_driver = types.DriverConfig(name=self.log_driver, options=self.log_driver_options) + return types.ContainerSpec(self.image, **container_spec_args) - placement = types.Placement( - constraints=self.constraints, - preferences=[ - {key.title(): {"SpreadDescriptor": value}} + def build_placement(self): + placement_args = {} + if self.constraints is not None: + placement_args['constraints'] = self.constraints + if self.placement_preferences is not None: + placement_args['preferences'] = [ + {key.title(): {'SpreadDescriptor': value}} for preference in self.placement_preferences for key, value in preference.items() - ] if self.placement_preferences else None, - ) + ] + return types.Placement(**placement_args) if placement_args else None - restart_policy = types.RestartPolicy( - condition=self.restart_policy, - delay=self.restart_policy_delay, - max_attempts=self.restart_policy_attempts, - window=self.restart_policy_window) + def build_update_config(self): + update_config_args = {} + if self.update_parallelism is not None: + update_config_args['parallelism'] = self.update_parallelism + if self.update_delay is not None: + update_config_args['delay'] = self.update_delay + if self.update_failure_action is not None: + update_config_args['failure_action'] = self.update_failure_action + if self.update_monitor is not None: + update_config_args['monitor'] = self.update_monitor + if self.update_max_failure_ratio is not None: + update_config_args['max_failure_ratio'] = self.update_max_failure_ratio + if self.update_order is not None: + update_config_args['order'] = self.update_order + return types.UpdateConfig(**update_config_args) if update_config_args else None - resources = types.Resources( - cpu_limit=int(self.limit_cpu * 1000000000.0), - mem_limit=self.limit_memory, - cpu_reservation=int(self.reserve_cpu * 1000000000.0), - mem_reservation=self.reserve_memory - ) + def build_log_driver(self): + log_driver_args = {} + if self.log_driver is not None: + log_driver_args['name'] = self.log_driver + if self.log_driver_options is not None: + log_driver_args['options'] = self.log_driver_options + return types.DriverConfig(**log_driver_args) if log_driver_args else None - update_policy = types.UpdateConfig( - parallelism=self.update_parallelism, - delay=self.update_delay, - failure_action=self.update_failure_action, - monitor=self.update_monitor, - max_failure_ratio=self.update_max_failure_ratio, - order=self.update_order - ) + def build_restart_policy(self): + restart_policy_args = {} + if self.restart_policy is not None: + restart_policy_args['condition'] = self.restart_policy + if self.restart_policy_delay is not None: + restart_policy_args['delay'] = self.restart_policy_delay + if self.restart_policy_attempts is not None: + restart_policy_args['max_attempts'] = self.restart_policy_attempts + if self.restart_policy_window is not None: + restart_policy_args['window'] = self.restart_policy_window + return types.RestartPolicy(**restart_policy_args) if restart_policy_args else None - task_template = types.TaskTemplate( - container_spec=cspec, - log_driver=log_driver, - restart_policy=restart_policy, - placement=placement, - resources=resources, - force_update=self.force_update) + def build_resources(self): + resources_args = {} + if self.limit_cpu is not None: + resources_args['cpu_limit'] = int(self.limit_cpu * 1000000000.0) + if self.limit_memory is not None: + resources_args['mem_limit'] = self.limit_memory + if self.reserve_cpu is not None: + resources_args['cpu_reservation'] = int(self.reserve_cpu * 1000000000.0) + if self.reserve_memory is not None: + resources_args['mem_reservation'] = self.reserve_memory + return types.Resources(**resources_args) if resources_args else None + def build_task_template(self, container_spec, placement=None): + log_driver = self.build_log_driver() + restart_policy = self.build_restart_policy() + resources = self.build_resources() + + task_template_args = {} + if placement is not None: + task_template_args['placement'] = placement + if log_driver is not None: + task_template_args['log_driver'] = log_driver + if restart_policy is not None: + task_template_args['restart_policy'] = restart_policy + if resources is not None: + task_template_args['resources'] = resources + if self.force_update: + task_template_args['force_update'] = self.force_update + return types.TaskTemplate(container_spec=container_spec, **task_template_args) + + def build_service_mode(self): if self.mode == 'global': self.replicas = None + return types.ServiceMode(self.mode, replicas=self.replicas) - mode = types.ServiceMode(self.mode, replicas=self.replicas) + def build_networks(self, docker_networks): + networks = None + if self.networks is not None: + networks = [] + for network_name in self.networks: + network_id = None + try: + network_id = list( + filter(lambda n: n['name'] == network_name, docker_networks) + )[0]['id'] + except (IndexError, KeyError): + pass + if network_id: + networks.append({'Target': network_id}) + else: + raise Exception('no docker networks named: %s' % network_name) + return networks - networks = [] - for network_name in self.networks: - network_id = None - try: - network_id = list(filter(lambda n: n['name'] == network_name, docker_networks))[0]['id'] - except Exception: - pass - if network_id: - networks.append({'Target': network_id}) - else: - raise Exception("no docker networks named: %s" % network_name) + def build_endpoint_spec(self): + endpoint_spec_args = {} + if self.publish is not None: + ports = {} + for port in self.publish: + if port.get('mode'): + ports[int(port['published_port'])] = ( + int(port['target_port']), + port['protocol'], + port['mode'], + ) + else: + ports[int(port['published_port'])] = ( + int(port['target_port']), + port['protocol'], + ) + endpoint_spec_args['ports'] = ports + if self.endpoint_mode is not None: + endpoint_spec_args['mode'] = self.endpoint_mode + return types.EndpointSpec(**endpoint_spec_args) if endpoint_spec_args else None - ports = {} - for port in self.publish: - if port.get('mode'): - ports[int(port['published_port'])] = (int(port['target_port']), port['protocol'], port['mode']) - else: - ports[int(port['published_port'])] = (int(port['target_port']), port['protocol']) - endpoint_spec = types.EndpointSpec(mode=self.endpoint_mode, ports=ports) - return update_policy, task_template, networks, endpoint_spec, mode, self.labels + def build_docker_service(self, docker_networks): + container_spec = self.build_container_spec() + placement = self.build_placement() + task_template = self.build_task_template(container_spec, placement) - # def fail(self, msg): - # self.parameters.client.module.fail_json(msg=msg) - # - # @property - # def exists(self): - # return True if self.service else False + update_config = self.build_update_config() + service_mode = self.build_service_mode() + networks = self.build_networks(docker_networks) + endpoint_spec = self.build_endpoint_spec() + + service = {'task_template': task_template, 'mode': service_mode} + if update_config: + service['update_config'] = update_config + if networks: + service['networks'] = networks + if endpoint_spec: + service['endpoint_spec'] = endpoint_spec + if self.labels: + service['labels'] = self.labels + return service -class DockerServiceManager(): +class DockerServiceManager(object): + + def __init__(self, client): + self.client = client + self.retries = 2 + self.diff_tracker = None + def get_networks_names_ids(self): return [{'name': n['Name'], 'id': n['Id']} for n in self.client.networks()] @@ -1037,50 +1163,47 @@ class DockerServiceManager(): return None raw_data = raw_data[0] - networks_names_ids = self.get_networks_names_ids() ds = DockerService() task_template_data = raw_data['Spec']['TaskTemplate'] - update_config_data = raw_data['Spec']['UpdateConfig'] - ds.image = task_template_data['ContainerSpec']['Image'] - ds.user = task_template_data['ContainerSpec'].get('User', None) - ds.env = task_template_data['ContainerSpec'].get('Env', []) + ds.user = task_template_data['ContainerSpec'].get('User') + ds.env = task_template_data['ContainerSpec'].get('Env') ds.command = task_template_data['ContainerSpec'].get('Command') - ds.args = task_template_data['ContainerSpec'].get('Args', []) - ds.update_delay = update_config_data['Delay'] - ds.update_parallelism = update_config_data['Parallelism'] - ds.update_failure_action = update_config_data['FailureAction'] - ds.update_monitor = update_config_data['Monitor'] - ds.update_max_failure_ratio = update_config_data['MaxFailureRatio'] + ds.args = task_template_data['ContainerSpec'].get('Args') - if 'Order' in update_config_data: - ds.update_order = update_config_data['Order'] + update_config_data = raw_data['Spec'].get('UpdateConfig') + if update_config_data: + ds.update_delay = update_config_data.get('Delay') + ds.update_parallelism = update_config_data.get('Parallelism') + ds.update_failure_action = update_config_data.get('FailureAction') + ds.update_monitor = update_config_data.get('Monitor') + ds.update_max_failure_ratio = update_config_data.get('MaxFailureRatio') + ds.update_order = update_config_data.get('Order') - dns_config = task_template_data['ContainerSpec'].get('DNSConfig', None) + dns_config = task_template_data['ContainerSpec'].get('DNSConfig') if dns_config: - if 'Nameservers' in dns_config.keys(): - ds.dns = dns_config['Nameservers'] - if 'Search' in dns_config.keys(): - ds.dns_search = dns_config['Search'] - if 'Options' in dns_config.keys(): - ds.dns_options = dns_config['Options'] - ds.hostname = task_template_data['ContainerSpec'].get('Hostname', '') - ds.tty = task_template_data['ContainerSpec'].get('TTY', False) - if 'Placement' in task_template_data.keys(): - placement = task_template_data['Placement'] - ds.constraints = placement.get('Constraints', []) + ds.dns = dns_config.get('Nameservers') + ds.dns_search = dns_config.get('Search') + ds.dns_options = dns_config.get('Options') + + ds.hostname = task_template_data['ContainerSpec'].get('Hostname') + ds.tty = task_template_data['ContainerSpec'].get('TTY') + + placement = task_template_data.get('Placement') + if placement: + ds.constraints = placement.get('Constraints') placement_preferences = [] for preference in placement.get('Preferences', []): placement_preferences.append( dict( - (key.lower(), value["SpreadDescriptor"]) + (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) + restart_policy_data = task_template_data.get('RestartPolicy') if restart_policy_data: ds.restart_policy = restart_policy_data.get('Condition') ds.restart_policy_delay = restart_policy_data.get('Delay') @@ -1090,31 +1213,42 @@ class DockerServiceManager(): raw_data_endpoint_spec = raw_data['Spec'].get('EndpointSpec') if raw_data_endpoint_spec: ds.endpoint_mode = raw_data_endpoint_spec.get('Mode') - for port in raw_data_endpoint_spec.get('Ports', []): - ds.publish.append({ - 'protocol': port['Protocol'], - 'mode': port.get('PublishMode', None), - 'published_port': int(port['PublishedPort']), - 'target_port': int(port['TargetPort'])}) + raw_data_ports = raw_data_endpoint_spec.get('Ports') + if raw_data_ports: + ds.publish = [] + for port in raw_data_ports: + ds.publish.append({ + 'protocol': port['Protocol'], + 'mode': port.get('PublishMode', None), + 'published_port': int(port['PublishedPort']), + 'target_port': int(port['TargetPort']) + }) - if 'Resources' in task_template_data.keys(): - if 'Limits' in task_template_data['Resources'].keys(): - if 'NanoCPUs' in task_template_data['Resources']['Limits'].keys(): - ds.limit_cpu = float(task_template_data['Resources']['Limits']['NanoCPUs']) / 1000000000 - if 'MemoryBytes' in task_template_data['Resources']['Limits'].keys(): - ds.limit_memory = int(task_template_data['Resources']['Limits']['MemoryBytes']) - if 'Reservations' in task_template_data['Resources'].keys(): - if 'NanoCPUs' in task_template_data['Resources']['Reservations'].keys(): - ds.reserve_cpu = float(task_template_data['Resources']['Reservations']['NanoCPUs']) / 1000000000 - if 'MemoryBytes' in task_template_data['Resources']['Reservations'].keys(): - ds.reserve_memory = int( - task_template_data['Resources']['Reservations']['MemoryBytes']) + raw_data_limits = task_template_data.get('Resources', {}).get('Limits') + if raw_data_limits: + raw_cpu_limits = raw_data_limits.get('NanoCPUs') + if raw_cpu_limits: + ds.limit_cpu = float(raw_cpu_limits) / 1000000000 + + raw_memory_limits = raw_data_limits.get('MemoryBytes') + if raw_memory_limits: + ds.limit_memory = int(raw_memory_limits) + + raw_data_reservations = task_template_data.get('Resources', {}).get('Reservations') + if raw_data_reservations: + raw_cpu_reservations = raw_data_reservations.get('NanoCPUs') + if raw_cpu_reservations: + ds.reserve_cpu = float(raw_cpu_reservations) / 1000000000 + + raw_memory_reservations = raw_data_reservations.get('MemoryBytes') + if raw_memory_reservations: + ds.reserve_memory = int(raw_memory_reservations) + + ds.labels = raw_data['Spec'].get('Labels') + ds.log_driver = task_template_data.get('LogDriver', {}).get('Name') + ds.log_driver_options = task_template_data.get('LogDriver', {}).get('Options') + ds.container_labels = task_template_data['ContainerSpec'].get('Labels') - ds.labels = raw_data['Spec'].get('Labels', {}) - if 'LogDriver' in task_template_data.keys(): - ds.log_driver = task_template_data['LogDriver'].get('Name', 'json-file') - ds.log_driver_options = task_template_data['LogDriver'].get('Options', {}) - ds.container_labels = task_template_data['ContainerSpec'].get('Labels', {}) mode = raw_data['Spec']['Mode'] if 'Replicated' in mode.keys(): ds.mode = to_text('replicated', encoding='utf-8') @@ -1122,68 +1256,73 @@ class DockerServiceManager(): elif 'Global' in mode.keys(): ds.mode = 'global' else: - raise Exception("Unknown service mode: %s" % mode) - for mount_data in raw_data['Spec']['TaskTemplate']['ContainerSpec'].get('Mounts', []): - ds.mounts.append({ - 'source': mount_data['Source'], - 'type': mount_data['Type'], - 'target': mount_data['Target'], - 'readonly': mount_data.get('ReadOnly', False)}) - for config_data in raw_data['Spec']['TaskTemplate']['ContainerSpec'].get('Configs', []): - ds.configs.append({ - 'config_id': config_data['ConfigID'], - 'config_name': config_data['ConfigName'], - 'filename': config_data['File'].get('Name'), - 'uid': int(config_data['File'].get('UID')), - 'gid': int(config_data['File'].get('GID')), - 'mode': config_data['File'].get('Mode') - }) - for secret_data in raw_data['Spec']['TaskTemplate']['ContainerSpec'].get('Secrets', []): - ds.secrets.append({ - 'secret_id': secret_data['SecretID'], - 'secret_name': secret_data['SecretName'], - 'filename': secret_data['File'].get('Name'), - 'uid': int(secret_data['File'].get('UID')), - 'gid': int(secret_data['File'].get('GID')), - 'mode': secret_data['File'].get('Mode') - }) + raise Exception('Unknown service mode: %s' % mode) + + raw_data_mounts = task_template_data['ContainerSpec'].get('Mounts') + if raw_data_mounts: + ds.mounts = [] + for mount_data in raw_data_mounts: + ds.mounts.append({ + 'source': mount_data['Source'], + 'type': mount_data['Type'], + 'target': mount_data['Target'], + 'readonly': mount_data.get('ReadOnly', False) + }) + + raw_data_configs = task_template_data['ContainerSpec'].get('Configs') + if raw_data_configs: + ds.configs = [] + for config_data in raw_data_configs: + ds.configs.append({ + 'config_id': config_data['ConfigID'], + 'config_name': config_data['ConfigName'], + 'filename': config_data['File'].get('Name'), + 'uid': int(config_data['File'].get('UID')), + 'gid': int(config_data['File'].get('GID')), + 'mode': config_data['File'].get('Mode') + }) + + raw_data_secrets = task_template_data['ContainerSpec'].get('Secrets') + if raw_data_secrets: + ds.secrets = [] + for secret_data in raw_data_secrets: + ds.secrets.append({ + 'secret_id': secret_data['SecretID'], + 'secret_name': secret_data['SecretName'], + 'filename': secret_data['File'].get('Name'), + 'uid': int(secret_data['File'].get('UID')), + 'gid': int(secret_data['File'].get('GID')), + 'mode': secret_data['File'].get('Mode') + }) + networks_names_ids = self.get_networks_names_ids() - for raw_network_data in raw_data['Spec']['TaskTemplate'].get('Networks', raw_data['Spec'].get('Networks', [])): - network_name = [network_name_id['name'] for network_name_id in networks_names_ids if - network_name_id['id'] == raw_network_data['Target']] - if len(network_name) == 0: - ds.networks.append(raw_network_data['Target']) - else: - ds.networks.append(network_name[0]) + raw_networks_data = task_template_data.get('Networks', raw_data['Spec'].get('Networks')) + if raw_networks_data: + ds.networks = [] + for network_data in raw_networks_data: + network_name = [network_name_id['name'] for network_name_id in networks_names_ids if + network_name_id['id'] == network_data['Target']] + if len(network_name) == 0: + ds.networks.append(network_data['Target']) + else: + ds.networks.append(network_name[0]) + ds.service_version = raw_data['Version']['Index'] ds.service_id = raw_data['ID'] return ds def update_service(self, name, old_service, new_service): - update_policy, task_template, networks, endpoint_spec, mode, labels = new_service.generate_docker_py_service_description( - name, self.get_networks_names_ids()) + service_data = new_service.build_docker_service(self.get_networks_names_ids()) self.client.update_service( old_service.service_id, old_service.service_version, name=name, - endpoint_spec=endpoint_spec, - networks=networks, - mode=mode, - update_config=update_policy, - task_template=task_template, - labels=labels) + **service_data + ) def create_service(self, name, service): - update_policy, task_template, networks, endpoint_spec, mode, labels = service.generate_docker_py_service_description( - name, self.get_networks_names_ids()) - self.client.create_service( - name=name, - endpoint_spec=endpoint_spec, - mode=mode, - networks=networks, - update_config=update_policy, - task_template=task_template, - labels=labels) + service_data = service.build_docker_service(self.get_networks_names_ids()) + self.client.create_service(name=name, **service_data) def remove_service(self, name): self.client.remove_service(name) @@ -1204,11 +1343,6 @@ class DockerServiceManager(): digest = distribution_data['Descriptor']['digest'] return '%s@%s' % (name, digest) - def __init__(self, client): - self.client = client - self.retries = 2 - self.diff_tracker = None - def run(self): self.diff_tracker = DifferenceTracker() module = self.client.module @@ -1226,7 +1360,7 @@ class DockerServiceManager(): current_service = self.get_service(module.params['name']) except Exception as e: return module.fail_json( - msg="Error looking for service named %s: %s" % + msg='Error looking for service named %s: %s' % (module.params['name'], e)) try: new_service = DockerService.from_ansible_params( @@ -1236,7 +1370,7 @@ class DockerServiceManager(): ) except Exception as e: return module.fail_json( - msg="Error parsing module parameters: %s" % e) + msg='Error parsing module parameters: %s' % e) changed = False msg = 'noop' @@ -1257,23 +1391,29 @@ class DockerServiceManager(): if need_rebuild: if not module.check_mode: self.remove_service(module.params['name']) - self.create_service(module.params['name'], - new_service) + self.create_service( + module.params['name'], + new_service + ) msg = 'Service rebuilt' rebuilt = True else: if not module.check_mode: - self.update_service(module.params['name'], - current_service, - new_service) + self.update_service( + module.params['name'], + current_service, + new_service + ) msg = 'Service updated' rebuilt = False else: if force_update: if not module.check_mode: - self.update_service(module.params['name'], - current_service, - new_service) + self.update_service( + module.params['name'], + current_service, + new_service + ) msg = 'Service forcefully updated' rebuilt = False changed = True @@ -1285,8 +1425,7 @@ class DockerServiceManager(): msg = 'Service absent' else: if not module.check_mode: - service_id = self.create_service(module.params['name'], - new_service) + self.create_service(module.params['name'], new_service) msg = 'Service created' changed = True facts = new_service.get_facts() @@ -1318,62 +1457,91 @@ def main(): argument_spec = dict( name=dict(required=True), image=dict(type='str'), - state=dict(default="present", choices=['present', 'absent']), - mounts=dict(default=[], type='list'), - configs=dict(default=None, type='list'), - secrets=dict(default=[], type='list'), - networks=dict(default=[], type='list'), - command=dict(default=None, type='raw'), - args=dict(default=[], type='list'), - env=dict(default=[], type='list'), + state=dict(default='present', choices=['present', 'absent']), + mounts=dict(type='list', elements='dict', options=dict( + source=dict(type='str', required=True), + target=dict(type='str', required=True), + type=dict( + default='bind', + type='str', + choices=['bind', 'volume', 'tmpfs'] + ), + readonly=dict(default=False, type='bool'), + )), + configs=dict(type='list', elements='dict', options=dict( + config_id=dict(type='str', required=True), + config_name=dict(type='str', required=True), + filename=dict(type='str'), + uid=dict(default=0, type='int'), + gid=dict(default=0, type='int'), + mode=dict(default=0o444, type='int'), + )), + secrets=dict(type='list', elements='dict', options=dict( + secret_id=dict(type='str', required=True), + secret_name=dict(type='str', required=True), + filename=dict(type='str'), + uid=dict(default=0, type='int'), + gid=dict(default=0, type='int'), + mode=dict(default=0o444, type='int'), + )), + networks=dict(type='list'), + command=dict(type='raw'), + args=dict(type='list'), + env=dict(type='list'), force_update=dict(default=False, type='bool'), - log_driver=dict(default="json-file", type='str'), - log_driver_options=dict(default={}, type='dict'), - publish=dict(default=[], type='list', elements='dict', options=dict( + log_driver=dict(type='str'), + log_driver_options=dict(type='dict'), + publish=dict(type='list', elements='dict', options=dict( published_port=dict(type='int', required=True), target_port=dict(type='int', required=True), - protocol=dict(default='tcp', type='str', required=False, choices=('tcp', 'udp')), - mode=dict(type='str', required=False, choices=('ingress', 'host')), + protocol=dict(default='tcp', type='str', choices=('tcp', 'udp')), + mode=dict(type='str', 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'), - dns_options=dict(default=[], type='list'), - hostname=dict(default="", type='str'), - labels=dict(default={}, type='dict'), - container_labels=dict(default={}, type='dict'), - mode=dict(default="replicated"), + constraints=dict(type='list'), + placement_preferences=dict(type='list'), + tty=dict(type='bool'), + dns=dict(type='list'), + dns_search=dict(type='list'), + dns_options=dict(type='list'), + hostname=dict(type='str'), + labels=dict(type='dict'), + container_labels=dict(type='dict'), + mode=dict(default='replicated', type='str'), replicas=dict(default=-1, type='int'), - endpoint_mode=dict(default=None, choices=['vip', 'dnsrr']), - restart_policy=dict(default='none', choices=['none', 'on-failure', 'any']), - limit_cpu=dict(default=0, type='float'), - limit_memory=dict(default=0, type='str'), - reserve_cpu=dict(default=0, type='float'), - reserve_memory=dict(default=0, type='str'), + endpoint_mode=dict(choices=['vip', 'dnsrr']), + limit_cpu=dict(type='float'), + limit_memory=dict(type='str'), + reserve_cpu=dict(type='float'), + reserve_memory=dict(type='str'), resolve_image=dict(default=True, type='bool'), - restart_policy_delay=dict(default=0, type='int'), - restart_policy_attempts=dict(default=0, type='int'), - restart_policy_window=dict(default=0, type='int'), + restart_policy=dict(choices=['none', 'on-failure', 'any']), + restart_policy_delay=dict(type='int'), + restart_policy_attempts=dict(type='int'), + restart_policy_window=dict(type='int'), update_delay=dict(default=10, type='int'), update_parallelism=dict(default=1, type='int'), - update_failure_action=dict(default='continue', choices=['continue', 'pause']), - update_monitor=dict(default=5000000000, type='int'), - update_max_failure_ratio=dict(default=0, type='float'), - update_order=dict(default=None, type='str'), - user=dict(type='str')) + update_failure_action=dict(choices=['continue', 'pause']), + update_monitor=dict(type='int'), + update_max_failure_ratio=dict(type='float'), + update_order=dict(type='str'), + user=dict(type='str') + ) option_minimal_versions = dict( dns=dict(docker_py_version='2.6.0', docker_api_version='1.25'), dns_options=dict(docker_py_version='2.6.0', docker_api_version='1.25'), dns_search=dict(docker_py_version='2.6.0', docker_api_version='1.25'), + endpoint_mode=dict(docker_py_version='3.0.0', docker_api_version='1.25'), force_update=dict(docker_py_version='2.1.0', docker_api_version='1.25'), hostname=dict(docker_py_version='2.2.0', docker_api_version='1.25'), tty=dict(docker_py_version='2.4.0', docker_api_version='1.25'), secrets=dict(docker_py_version='2.1.0', docker_api_version='1.25'), configs=dict(docker_py_version='2.6.0', docker_api_version='1.30'), + update_max_failure_ratio=dict(docker_py_version='2.1.0', docker_api_version='1.25'), + update_monitor=dict(docker_py_version='2.1.0', docker_api_version='1.25'), update_order=dict(docker_py_version='2.7.0', docker_api_version='1.29'), + placement_preferences=dict(docker_py_version='2.4.0', docker_api_version='1.27'), + publish=dict(docker_py_version='3.0.0', docker_api_version='1.25'), # specials publish_mode=dict( docker_py_version='3.0.0', 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 c0a547a9a71..239746074c6 100644 --- a/test/integration/targets/docker_swarm_service/tasks/tests/options.yml +++ b/test/integration/targets/docker_swarm_service/tasks/tests/options.yml @@ -94,6 +94,20 @@ - "3400" register: args_3 +- name: args (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + args: [] + register: args_4 + +- name: args (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + args: [] + register: args_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -105,6 +119,8 @@ - args_1 is changed - args_2 is not changed - args_3 is changed + - args_4 is changed + - args_5 is not changed #################################################################### ## configs ######################################################### @@ -154,6 +170,14 @@ configs: [] register: configs_4 +- name: configs (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + configs: [] + register: configs_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -166,6 +190,7 @@ - configs_2 is not changed - configs_3 is changed - configs_4 is changed + - configs_5 is not changed when: docker_api_version is version('1.30', '>=') - assert: that: @@ -195,7 +220,7 @@ - "node.role == manager" register: constraints_2 -- name: constraints (idempotency) +- name: constraints (change) docker_swarm_service: name: "{{ service_name }}" image: alpine:3.8 @@ -204,6 +229,22 @@ - "node.role == worker" register: constraints_3 +- name: constraints (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + constraints: [] + register: constraints_4 + +- name: constraints (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + constraints: [] + register: constraints_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -215,6 +256,8 @@ - constraints_1 is changed - constraints_2 is not changed - constraints_3 is changed + - constraints_4 is changed + - constraints_5 is not changed #################################################################### ## command ######################################################### @@ -251,12 +294,26 @@ - "sleep 10m" register: command_4 +- name: command (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: [] + register: command_5 + +- name: command (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: [] + register: command_6 + - name: command (string failure) docker_swarm_service: name: "{{ service_name }}" image: alpine:3.8 command: yes - register: command_5 + register: command_7 ignore_errors: yes - name: command (list failure) @@ -266,7 +323,7 @@ command: - "/bin/sh" - yes - register: command_6 + register: command_8 ignore_errors: yes - name: cleanup @@ -281,8 +338,10 @@ - command_2 is not changed - command_3 is changed - command_4 is not changed - - command_5 is failed - - command_6 is failed + - command_5 is changed + - command_6 is not changed + - command_7 is failed + - command_8 is failed #################################################################### ## container_labels ################################################ @@ -318,6 +377,22 @@ test_2: "3" register: container_labels_3 +- name: container_labels (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + container_labels: {} + register: container_labels_4 + +- name: container_labels (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + container_labels: {} + register: container_labels_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -329,6 +404,8 @@ - container_labels_1 is changed - container_labels_2 is not changed - container_labels_3 is changed + - container_labels_4 is changed + - container_labels_5 is not changed #################################################################### ## dns ############################################################# @@ -374,6 +451,22 @@ - 9.9.9.9 register: dns_4 +- name: dns_servers (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns: [] + register: dns_5 + +- name: dns_servers (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns: [] + register: dns_6 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -386,6 +479,8 @@ - dns_2 is not changed - dns_3 is changed - dns_4 is changed + - dns_5 is changed + - dns_6 is not changed when: docker_api_version is version('1.25', '>=') - assert: that: @@ -430,6 +525,24 @@ register: dns_options_3 ignore_errors: yes +- name: dns_options (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns_options: [] + register: dns_options_4 + ignore_errors: yes + +- name: dns_options (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns_options: [] + register: dns_options_5 + ignore_errors: yes + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -441,6 +554,8 @@ - dns_options_1 is changed - dns_options_2 is not changed - dns_options_3 is changed + - dns_options_4 is changed + - dns_options_5 is not changed when: docker_api_version is version('1.25', '>=') - assert: that: @@ -492,6 +607,22 @@ - example.com register: dns_search_4 +- name: dns_search (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns_search: [] + register: dns_search_5 + +- name: dns_search (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + dns_search: [] + register: dns_search_6 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -504,6 +635,8 @@ - dns_search_2 is not changed - dns_search_3 is changed - dns_search_4 is changed + - dns_search_5 is changed + - dns_search_6 is not changed when: docker_api_version is version('1.25', '>=') - assert: that: @@ -585,6 +718,22 @@ - "TEST2=val3" register: env_3 +- name: env (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + env: [] + register: env_4 + +- name: env (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + env: [] + register: env_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -596,6 +745,8 @@ - env_1 is changed - env_2 is not changed - env_3 is changed + - env_4 is changed + - env_5 is not changed ################################################################### ## force_update ################################################### @@ -757,6 +908,22 @@ test_3: "3" register: labels_3 +- name: labels (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + labels: {} + register: labels_4 + +- name: labels (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + labels: {} + register: labels_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -768,6 +935,8 @@ - labels_1 is changed - labels_2 is not changed - labels_3 is changed + - labels_4 is changed + - labels_5 is not changed ################################################################### ## limit_cpu ###################################################### @@ -927,6 +1096,24 @@ max-file: "1" register: log_driver_options_3 +- name: log_driver_options (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + log_driver: json-file + log_driver_options: {} + register: log_driver_options_4 + +- name: log_driver_options (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + log_driver: json-file + log_driver_options: {} + register: log_driver_options_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -938,6 +1125,8 @@ - log_driver_options_1 is changed - log_driver_options_2 is not changed - log_driver_options_3 is changed + - log_driver_options_4 is changed + - log_driver_options_5 is not changed ################################################################### ## mode ########################################################### @@ -1030,6 +1219,14 @@ mounts: [] register: mounts_4 +- name: mounts (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + mounts: [] + register: mounts_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -1042,6 +1239,7 @@ - mounts_2 is not changed - mounts_3 is changed - mounts_4 is changed + - mounts_5 is not changed #################################################################### ## networks ######################################################## @@ -1073,6 +1271,14 @@ networks: [] register: networks_3 +- name: networks (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + networks: [] + register: networks_4 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -1084,6 +1290,7 @@ - networks_1 is changed - networks_2 is not changed - networks_3 is changed + - networks_4 is not changed #################################################################### ## placement_preferences ########################################### @@ -1116,6 +1323,22 @@ - spread: "node.labels.test2" register: placement_preferences_3 +- name: placement_preferences (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + placement_preferences: [] + register: placement_preferences_4 + +- name: placement_preferences (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + placement_preferences: [] + register: placement_preferences_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -1127,6 +1350,8 @@ - placement_preferences_1 is changed - placement_preferences_2 is not changed - placement_preferences_3 is changed + - placement_preferences_4 is changed + - placement_preferences_5 is not changed #################################################################### ## publish ######################################################### @@ -1205,6 +1430,22 @@ mode: host register: publish_5 +- name: publish (empty) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + publish: [] + register: publish_6 + +- name: publish (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + publish: [] + register: publish_7 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -1218,6 +1459,8 @@ - publish_3 is changed - publish_4 is changed - publish_5 is not changed + - publish_6 is changed + - publish_7 is not changed when: docker_api_version is version('1.25', '>=') - assert: that: @@ -1593,6 +1836,14 @@ secrets: [] register: secrets_4 +- name: secrets (empty idempotency) + docker_swarm_service: + name: "{{ service_name }}" + image: alpine:3.8 + command: '/bin/sh -v -c "sleep 10m"' + secrets: [] + register: secrets_5 + - name: cleanup docker_swarm_service: name: "{{ service_name }}" @@ -1605,6 +1856,7 @@ - secrets_2 is not changed - secrets_3 is changed - secrets_4 is changed + - secrets_5 is not changed when: docker_api_version is version('1.25', '>=') - assert: that: diff --git a/test/integration/targets/docker_swarm_service/vars/main.yml b/test/integration/targets/docker_swarm_service/vars/main.yml index 28a5d0485e3..57fd89073b0 100644 --- a/test/integration/targets/docker_swarm_service/vars/main.yml +++ b/test/integration/targets/docker_swarm_service/vars/main.yml @@ -3,40 +3,40 @@ service_expected_output: args: [sleep, '1800'] configs: null - constraints: [] - container_labels: {} + constraints: null + container_labels: null command: null - dns: [] - dns_options: [] - dns_search: [] + dns: null + dns_options: null + dns_search: null endpoint_mode: vip - env: [] + env: null force_update: null - hostname: '' + hostname: null image: busybox - labels: {} - limit_cpu: 0.0 - limit_memory: 0 - log_driver: json-file - log_driver_options: {} + labels: null + limit_cpu: null + limit_memory: null + log_driver: null + log_driver_options: null mode: global - mounts: [] - networks: [] + mounts: null + networks: null placement_preferences: null publish: - {mode: null, protocol: tcp, published_port: 60001, target_port: 60001} - {mode: null, protocol: udp, published_port: 60001, target_port: 60001} replicas: null - reserve_cpu: 0.0 - reserve_memory: 0 - restart_policy: none - restart_policy_attempts: 0 - restart_policy_delay: 0 - restart_policy_window: 0 - tty: false + reserve_cpu: null + reserve_memory: null + restart_policy: null + restart_policy_attempts: null + restart_policy_delay: null + restart_policy_window: null + tty: null update_delay: 10 - update_failure_action: continue - update_max_failure_ratio: 0.0 - update_monitor: 5000000000 + update_failure_action: null + update_max_failure_ratio: null + update_monitor: null update_order: null update_parallelism: 1