docker_swarm_service: Sort lists when checking for changes (#63887)

* docker_swarm_service: Sort lists when checking for changes

When two lists are checked for changes in this module, the lists are 
reported changed when the order of the items is different. This PR 
resolves this issue.

* docker_swarm_service: Minor typo fix

* docker_swarm_service: Another minor typo

* docker_swarm_service: Should use sorted(), not sort()

* docker_swarm_service: Sort lists of dictionaries

* docker_swarm_service: Fix style issues in tests

* docker_swarm_service: Updates to integration tests

* docker_swarm_service: Casting string types within lists when comparing

* docker_swarm_service: Special handling of unordered networks with ordered aliases

* docker_swarm_service: Sorting network lists

* docker_swarm_serivce: Better unit test code coverage for lists and networks

* docker_swarm_service: Fixed coding style for sanity tests

* docker_swarm_service: More coding style fixes

* docker_swarm_service: Ignoring test for Python < 3

* docker_swarm_service: Update to version info check for backwards compatibility

* docker_swarm_service: Added change fragment #63887

* docker_swarm_service: Better handling of missing sort key for dictionary of lists

* docker_swarm_service: Preventing sorts from modifying in-place

Co-Authored-By: Felix Fontein <felix@fontein.de>

* docker_swarm_service: Removed spurious import in test

* docker_swarm_service: Preventing sorts from modifying more data in-place

Co-Authored-By: Felix Fontein <felix@fontein.de>
This commit is contained in:
Michael Cassaniti 2019-11-24 06:31:35 +11:00 committed by Felix Fontein
parent 136dc27572
commit a096cd08c5
9 changed files with 468 additions and 133 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "docker_swarm_service - Sort lists when checking for changes."

View file

@ -1244,19 +1244,59 @@ def has_dict_changed(new_dict, old_dict):
return False return False
def has_list_changed(new_list, old_list): def has_list_changed(new_list, old_list, sort_lists=True, sort_key=None):
""" """
Check two lists has differences. Check two lists have differences. Sort lists by default.
""" """
def sort_list(unsorted_list):
"""
Sort a given list.
The list may contain dictionaries, so use the sort key to handle them.
"""
if unsorted_list and isinstance(unsorted_list[0], dict):
if not sort_key:
raise Exception(
'A sort key was not specified when sorting list'
)
else:
return sorted(unsorted_list, key=lambda k: k[sort_key])
# Either the list is empty or does not contain dictionaries
try:
return sorted(unsorted_list)
except TypeError:
return unsorted_list
if new_list is None: if new_list is None:
return False return False
old_list = old_list or [] old_list = old_list or []
if len(new_list) != len(old_list): if len(new_list) != len(old_list):
return True return True
for new_item, old_item in zip(new_list, old_list):
if sort_lists:
zip_data = zip(sort_list(new_list), sort_list(old_list))
else:
zip_data = zip(new_list, old_list)
for new_item, old_item in zip_data:
is_same_type = type(new_item) == type(old_item) is_same_type = type(new_item) == type(old_item)
if not is_same_type: if not is_same_type:
return True if isinstance(new_item, string_types) and isinstance(old_item, string_types):
# Even though the types are different between these items,
# they are both strings. Try matching on the same string type.
try:
new_item_type = type(new_item)
old_item_casted = new_item_type(old_item)
if new_item != old_item_casted:
return True
else:
continue
except UnicodeEncodeError:
# Fallback to assuming the strings are different
return True
else:
return True
if isinstance(new_item, dict): if isinstance(new_item, dict):
if has_dict_changed(new_item, old_item): if has_dict_changed(new_item, old_item):
return True return True
@ -1266,6 +1306,35 @@ def has_list_changed(new_list, old_list):
return False return False
def have_networks_changed(new_networks, old_networks):
"""Special case list checking for networks to sort aliases"""
if new_networks is None:
return False
old_networks = old_networks or []
if len(new_networks) != len(old_networks):
return True
zip_data = zip(
sorted(new_networks, key=lambda k: k['id']),
sorted(old_networks, key=lambda k: k['id'])
)
for new_item, old_item in zip_data:
new_item = dict(new_item)
old_item = dict(old_item)
# Sort the aliases
if 'aliases' in new_item:
new_item['aliases'] = sorted(new_item['aliases'] or [])
if 'aliases' in old_item:
old_item['aliases'] = sorted(old_item['aliases'] or [])
if has_dict_changed(new_item, old_item):
return True
return False
class DockerService(DockerBaseClass): class DockerService(DockerBaseClass):
def __init__(self, docker_api_version, docker_py_version): def __init__(self, docker_api_version, docker_py_version):
super(DockerService, self).__init__() super(DockerService, self).__init__()
@ -1761,7 +1830,7 @@ class DockerService(DockerBaseClass):
force_update = False force_update = False
if self.endpoint_mode is not None and self.endpoint_mode != os.endpoint_mode: 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) differences.add('endpoint_mode', parameter=self.endpoint_mode, active=os.endpoint_mode)
if self.env is not None and self.env != (os.env or []): if has_list_changed(self.env, os.env):
differences.add('env', parameter=self.env, active=os.env) differences.add('env', parameter=self.env, active=os.env)
if self.log_driver is not None and 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) differences.add('log_driver', parameter=self.log_driver, active=os.log_driver)
@ -1770,26 +1839,26 @@ class DockerService(DockerBaseClass):
if self.mode != os.mode: if self.mode != os.mode:
needs_rebuild = True needs_rebuild = True
differences.add('mode', parameter=self.mode, active=os.mode) differences.add('mode', parameter=self.mode, active=os.mode)
if has_list_changed(self.mounts, os.mounts): if has_list_changed(self.mounts, os.mounts, sort_key='target'):
differences.add('mounts', parameter=self.mounts, active=os.mounts) differences.add('mounts', parameter=self.mounts, active=os.mounts)
if has_list_changed(self.configs, os.configs): if has_list_changed(self.configs, os.configs, sort_key='config_name'):
differences.add('configs', parameter=self.configs, active=os.configs) differences.add('configs', parameter=self.configs, active=os.configs)
if has_list_changed(self.secrets, os.secrets): if has_list_changed(self.secrets, os.secrets, sort_key='secret_name'):
differences.add('secrets', parameter=self.secrets, active=os.secrets) differences.add('secrets', parameter=self.secrets, active=os.secrets)
if has_list_changed(self.networks, os.networks): if have_networks_changed(self.networks, os.networks):
differences.add('networks', parameter=self.networks, active=os.networks) differences.add('networks', parameter=self.networks, active=os.networks)
needs_rebuild = not self.can_update_networks needs_rebuild = not self.can_update_networks
if self.replicas != os.replicas: if self.replicas != os.replicas:
differences.add('replicas', parameter=self.replicas, active=os.replicas) differences.add('replicas', parameter=self.replicas, active=os.replicas)
if self.command is not None and self.command != (os.command or []): if has_list_changed(self.command, os.command, sort_lists=False):
differences.add('command', parameter=self.command, active=os.command) differences.add('command', parameter=self.command, active=os.command)
if self.args is not None and self.args != (os.args or []): if has_list_changed(self.args, os.args, sort_lists=False):
differences.add('args', parameter=self.args, active=os.args) differences.add('args', parameter=self.args, active=os.args)
if self.constraints is not None and self.constraints != (os.constraints or []): if has_list_changed(self.constraints, os.constraints):
differences.add('constraints', parameter=self.constraints, active=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 or []): if has_list_changed(self.placement_preferences, os.placement_preferences, sort_lists=False):
differences.add('placement_preferences', parameter=self.placement_preferences, active=os.placement_preferences) differences.add('placement_preferences', parameter=self.placement_preferences, active=os.placement_preferences)
if self.groups is not None and self.groups != (os.groups or []): if has_list_changed(self.groups, os.groups):
differences.add('groups', parameter=self.groups, active=os.groups) differences.add('groups', parameter=self.groups, active=os.groups)
if self.labels is not None and self.labels != (os.labels or {}): if self.labels is not None and self.labels != (os.labels or {}):
differences.add('labels', parameter=self.labels, active=os.labels) differences.add('labels', parameter=self.labels, active=os.labels)
@ -1838,11 +1907,11 @@ class DockerService(DockerBaseClass):
differences.add('image', parameter=self.image, active=change) differences.add('image', parameter=self.image, active=change)
if self.user and self.user != os.user: if self.user and self.user != os.user:
differences.add('user', parameter=self.user, active=os.user) differences.add('user', parameter=self.user, active=os.user)
if self.dns is not None and self.dns != (os.dns or []): if has_list_changed(self.dns, os.dns, sort_lists=False):
differences.add('dns', parameter=self.dns, active=os.dns) differences.add('dns', parameter=self.dns, active=os.dns)
if self.dns_search is not None and self.dns_search != (os.dns_search or []): if has_list_changed(self.dns_search, os.dns_search, sort_lists=False):
differences.add('dns_search', parameter=self.dns_search, active=os.dns_search) differences.add('dns_search', parameter=self.dns_search, active=os.dns_search)
if self.dns_options is not None and self.dns_options != (os.dns_options or []): if has_list_changed(self.dns_options, os.dns_options):
differences.add('dns_options', parameter=self.dns_options, active=os.dns_options) differences.add('dns_options', parameter=self.dns_options, active=os.dns_options)
if self.has_healthcheck_changed(os): if self.has_healthcheck_changed(os):
differences.add('healthcheck', parameter=self.healthcheck, active=os.healthcheck) differences.add('healthcheck', parameter=self.healthcheck, active=os.healthcheck)

View file

@ -97,6 +97,20 @@
register: configs_5 register: configs_5
ignore_errors: yes ignore_errors: yes
- name: configs (add idempotency no id and re-ordered)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
configs:
- config_name: "{{ config_name_2 }}"
filename: "/tmp/{{ config_name_2 }}.txt"
- config_name: "{{ config_name_1 }}"
filename: "/tmp/{{ config_name_1 }}.txt"
register: configs_6
ignore_errors: yes
- name: configs (empty) - name: configs (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -104,7 +118,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
configs: [] configs: []
register: configs_6 register: configs_7
ignore_errors: yes ignore_errors: yes
- name: configs (empty idempotency) - name: configs (empty idempotency)
@ -114,7 +128,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
configs: [] configs: []
register: configs_7 register: configs_8
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -130,8 +144,9 @@
- configs_3 is changed - configs_3 is changed
- configs_4 is not changed - configs_4 is not changed
- configs_5 is not changed - configs_5 is not changed
- configs_6 is changed - configs_6 is not changed
- configs_7 is not changed - configs_7 is changed
- configs_8 is not changed
when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=') when: docker_api_version is version('1.30', '>=') and docker_py_version is version('2.6.0', '>=')
- assert: - assert:

View file

@ -61,6 +61,21 @@
type: "bind" type: "bind"
register: mounts_3 register: mounts_3
- name: mounts (order 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_2 }}"
type: "bind"
- source: "{{ volume_name_1 }}"
target: "/tmp/{{ volume_name_1 }}"
type: "volume"
register: mounts_4
- name: mounts (empty) - name: mounts (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -68,7 +83,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
mounts: [] mounts: []
register: mounts_4 register: mounts_5
- name: mounts (empty idempotency) - name: mounts (empty idempotency)
docker_swarm_service: docker_swarm_service:
@ -77,7 +92,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
mounts: [] mounts: []
register: mounts_5 register: mounts_6
- name: cleanup - name: cleanup
docker_swarm_service: docker_swarm_service:
@ -90,8 +105,9 @@
- mounts_1 is changed - mounts_1 is changed
- mounts_2 is not changed - mounts_2 is not changed
- mounts_3 is changed - mounts_3 is changed
- mounts_4 is changed - mounts_4 is not changed
- mounts_5 is not changed - mounts_5 is changed
- mounts_6 is not changed
#################################################################### ####################################################################
## mounts.readonly ################################################# ## mounts.readonly #################################################

View file

@ -99,7 +99,7 @@
- "{{ network_name_2 }}" - "{{ network_name_2 }}"
register: networks_7 register: networks_7
- name: networks (change mixed order) - name: networks (order idempotency)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
image: alpine:3.8 image: alpine:3.8
@ -110,17 +110,6 @@
- name: "{{ network_name_1 }}" - name: "{{ network_name_1 }}"
register: networks_8 register: networks_8
- name: networks (change mixed order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_2 }}"
- name: "{{ network_name_1 }}"
register: networks_9
- name: networks (change less) - name: networks (change less)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -129,7 +118,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: networks:
- "{{ network_name_2 }}" - "{{ network_name_2 }}"
register: networks_10 register: networks_9
- name: networks (change less idempotency) - name: networks (change less idempotency)
docker_swarm_service: docker_swarm_service:
@ -139,7 +128,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: networks:
- "{{ network_name_2 }}" - "{{ network_name_2 }}"
register: networks_11 register: networks_10
- name: networks (empty) - name: networks (empty)
docker_swarm_service: docker_swarm_service:
@ -148,7 +137,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: [] networks: []
register: networks_12 register: networks_11
- name: networks (empty idempotency) - name: networks (empty idempotency)
docker_swarm_service: docker_swarm_service:
@ -157,7 +146,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: [] networks: []
register: networks_13 register: networks_12
- name: networks (unknown network) - name: networks (unknown network)
docker_swarm_service: docker_swarm_service:
@ -167,7 +156,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: networks:
- "idonotexist" - "idonotexist"
register: networks_14 register: networks_13
ignore_errors: yes ignore_errors: yes
- name: networks (missing dict key name) - name: networks (missing dict key name)
@ -178,7 +167,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: networks:
- foo: "bar" - foo: "bar"
register: networks_15 register: networks_14
ignore_errors: yes ignore_errors: yes
- name: networks (invalid list type) - name: networks (invalid list type)
@ -189,7 +178,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
networks: networks:
- [1, 2, 3] - [1, 2, 3]
register: networks_16 register: networks_15
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -207,18 +196,17 @@
- networks_5 is not changed - networks_5 is not changed
- networks_6 is not changed - networks_6 is not changed
- networks_7 is not changed - networks_7 is not changed
- networks_8 is changed - networks_8 is not changed
- networks_9 is not changed - networks_9 is changed
- networks_10 is changed - networks_10 is not changed
- networks_11 is not changed - networks_11 is changed
- networks_12 is changed - networks_12 is not changed
- networks_13 is not changed - networks_13 is failed
- '"Could not find a network named: ''idonotexist''" in networks_13.msg'
- networks_14 is failed - networks_14 is failed
- '"Could not find a network named: ''idonotexist''" in networks_14.msg' - "'\"name\" is required when networks are passed as dictionaries.' in networks_14.msg"
- networks_15 is failed - networks_15 is failed
- "'\"name\" is required when networks are passed as dictionaries.' in networks_15.msg" - "'Only a list of strings or dictionaries are allowed to be passed as networks' in networks_15.msg"
- networks_16 is failed
- "'Only a list of strings or dictionaries are allowed to be passed as networks' in networks_16.msg"
- assert: - assert:
that: that:
@ -262,6 +250,19 @@
- "alias2" - "alias2"
register: networks_aliases_2 register: networks_aliases_2
- name: networks.aliases (order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- name: "{{ network_name_1 }}"
aliases:
- "alias2"
- "alias1"
register: networks_aliases_3
- name: networks.aliases (change) - name: networks.aliases (change)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -272,7 +273,7 @@
- name: "{{ network_name_1 }}" - name: "{{ network_name_1 }}"
aliases: aliases:
- "alias1" - "alias1"
register: networks_aliases_3 register: networks_aliases_4
- name: networks.aliases (empty) - name: networks.aliases (empty)
docker_swarm_service: docker_swarm_service:
@ -283,7 +284,7 @@
networks: networks:
- name: "{{ network_name_1 }}" - name: "{{ network_name_1 }}"
aliases: [] aliases: []
register: networks_aliases_4 register: networks_aliases_5
- name: networks.aliases (empty idempotency) - name: networks.aliases (empty idempotency)
docker_swarm_service: docker_swarm_service:
@ -294,7 +295,7 @@
networks: networks:
- name: "{{ network_name_1 }}" - name: "{{ network_name_1 }}"
aliases: [] aliases: []
register: networks_aliases_5 register: networks_aliases_6
- name: networks.aliases (invalid type) - name: networks.aliases (invalid type)
docker_swarm_service: docker_swarm_service:
@ -306,7 +307,7 @@
- name: "{{ network_name_1 }}" - name: "{{ network_name_1 }}"
aliases: aliases:
- [1, 2, 3] - [1, 2, 3]
register: networks_aliases_6 register: networks_aliases_7
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -319,11 +320,12 @@
that: that:
- networks_aliases_1 is changed - networks_aliases_1 is changed
- networks_aliases_2 is not changed - networks_aliases_2 is not changed
- networks_aliases_3 is changed - networks_aliases_3 is not changed
- networks_aliases_4 is changed - networks_aliases_4 is changed
- networks_aliases_5 is not changed - networks_aliases_5 is changed
- networks_aliases_6 is failed - networks_aliases_6 is not changed
- "'Only strings are allowed as network aliases' in networks_aliases_6.msg" - networks_aliases_7 is failed
- "'Only strings are allowed as network aliases' in networks_aliases_7.msg"
#################################################################### ####################################################################
## networks.options ################################################ ## networks.options ################################################

View file

@ -366,6 +366,18 @@
register: dns_options_3 register: dns_options_3
ignore_errors: yes ignore_errors: yes
- name: dns_options (order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
dns_options:
- no-check-names
- "timeout:10"
register: dns_options_4
ignore_errors: yes
- name: dns_options (empty) - name: dns_options (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -373,7 +385,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
dns_options: [] dns_options: []
register: dns_options_4 register: dns_options_5
ignore_errors: yes ignore_errors: yes
- name: dns_options (empty idempotency) - name: dns_options (empty idempotency)
@ -383,7 +395,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
dns_options: [] dns_options: []
register: dns_options_5 register: dns_options_6
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -397,8 +409,9 @@
- dns_options_1 is changed - dns_options_1 is changed
- dns_options_2 is not changed - dns_options_2 is not changed
- dns_options_3 is changed - dns_options_3 is changed
- dns_options_4 is changed - dns_options_4 is not changed
- dns_options_5 is not changed - dns_options_5 is changed
- dns_options_6 is not changed
when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.6.0', '>=') when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.6.0', '>=')
- assert: - assert:
that: that:
@ -588,16 +601,18 @@
- "TEST2=val3" - "TEST2=val3"
register: env_3 register: env_3
- name: env (empty) - name: env (order idempotency)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
image: alpine:3.8 image: alpine:3.8
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
env: [] env:
- "TEST2=val3"
- "TEST1=val1"
register: env_4 register: env_4
- name: env (empty idempotency) - name: env (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
image: alpine:3.8 image: alpine:3.8
@ -606,6 +621,15 @@
env: [] env: []
register: env_5 register: env_5
- name: env (empty idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
env: []
register: env_6
- name: env (fail unwrapped values) - name: env (fail unwrapped values)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -613,7 +637,7 @@
resolve_image: no resolve_image: no
env: env:
TEST1: true TEST1: true
register: env_6 register: env_7
ignore_errors: yes ignore_errors: yes
- name: env (fail invalid formatted string) - name: env (fail invalid formatted string)
@ -624,7 +648,7 @@
env: env:
- "TEST1=val3" - "TEST1=val3"
- "TEST2" - "TEST2"
register: env_7 register: env_8
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -638,10 +662,11 @@
- env_1 is changed - env_1 is changed
- env_2 is not changed - env_2 is not changed
- env_3 is changed - env_3 is changed
- env_4 is changed - env_4 is not changed
- env_5 is not changed - env_5 is changed
- env_6 is failed - env_6 is not changed
- env_7 is failed - env_7 is failed
- env_8 is failed
#################################################################### ####################################################################
## env_files ####################################################### ## env_files #######################################################
@ -802,6 +827,18 @@
register: groups_2 register: groups_2
ignore_errors: yes ignore_errors: yes
- name: groups (order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
groups:
- "5678"
- "1234"
register: groups_3
ignore_errors: yes
- name: groups (change) - name: groups (change)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -810,7 +847,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
groups: groups:
- "1234" - "1234"
register: groups_3 register: groups_4
ignore_errors: yes ignore_errors: yes
- name: groups (empty) - name: groups (empty)
@ -820,7 +857,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
groups: [] groups: []
register: groups_4 register: groups_5
ignore_errors: yes ignore_errors: yes
- name: groups (empty idempotency) - name: groups (empty idempotency)
@ -830,7 +867,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
groups: [] groups: []
register: groups_5 register: groups_6
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -843,9 +880,10 @@
that: that:
- groups_1 is changed - groups_1 is changed
- groups_2 is not changed - groups_2 is not changed
- groups_3 is changed - groups_3 is not changed
- groups_4 is changed - groups_4 is changed
- groups_5 is not changed - groups_5 is changed
- groups_6 is not changed
when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.6.0', '>=') when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.6.0', '>=')
- assert: - assert:
that: that:

View file

@ -142,6 +142,32 @@
register: constraints_3 register: constraints_3
ignore_errors: yes ignore_errors: yes
- name: placement.constraints (add)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
placement:
constraints:
- "node.role == worker"
- "node.label != non_existent_label"
register: constraints_4
ignore_errors: yes
- name: placement.constraints (order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
placement:
constraints:
- "node.label != non_existent_label"
- "node.role == worker"
register: constraints_5
ignore_errors: yes
- name: placement.constraints (empty) - name: placement.constraints (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -150,7 +176,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
placement: placement:
constraints: [] constraints: []
register: constraints_4 register: constraints_6
ignore_errors: yes ignore_errors: yes
- name: placement.constraints (empty idempotency) - name: placement.constraints (empty idempotency)
@ -161,7 +187,7 @@
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
placement: placement:
constraints: [] constraints: []
register: constraints_5 register: constraints_7
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -178,6 +204,8 @@
- constraints_3 is changed - constraints_3 is changed
- constraints_4 is changed - constraints_4 is changed
- constraints_5 is not changed - constraints_5 is not changed
- constraints_6 is changed
- constraints_7 is not changed
when: docker_api_version is version('1.27', '>=') and docker_py_version is version('2.4.0', '>=') when: docker_api_version is version('1.27', '>=') and docker_py_version is version('2.4.0', '>=')
- assert: - assert:
that: that:

View file

@ -97,6 +97,20 @@
register: secrets_5 register: secrets_5
ignore_errors: yes ignore_errors: yes
- name: secrets (order idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
secrets:
- secret_name: "{{ secret_name_2 }}"
filename: "/run/secrets/{{ secret_name_2 }}.txt"
- secret_name: "{{ secret_name_1 }}"
filename: "/run/secrets/{{ secret_name_1 }}.txt"
register: secrets_6
ignore_errors: yes
- name: secrets (empty) - name: secrets (empty)
docker_swarm_service: docker_swarm_service:
name: "{{ service_name }}" name: "{{ service_name }}"
@ -104,7 +118,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
secrets: [] secrets: []
register: secrets_6 register: secrets_7
ignore_errors: yes ignore_errors: yes
- name: secrets (empty idempotency) - name: secrets (empty idempotency)
@ -114,7 +128,7 @@
resolve_image: no resolve_image: no
command: '/bin/sh -v -c "sleep 10m"' command: '/bin/sh -v -c "sleep 10m"'
secrets: [] secrets: []
register: secrets_7 register: secrets_8
ignore_errors: yes ignore_errors: yes
- name: cleanup - name: cleanup
@ -130,8 +144,9 @@
- secrets_3 is changed - secrets_3 is changed
- secrets_4 is not changed - secrets_4 is not changed
- secrets_5 is not changed - secrets_5 is not changed
- secrets_6 is changed - secrets_6 is not changed
- secrets_7 is not changed - secrets_7 is changed
- secrets_8 is not changed
when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.4.0', '>=') when: docker_api_version is version('1.25', '>=') and docker_py_version is version('2.4.0', '>=')
- assert: - assert:
that: that:

View file

@ -169,60 +169,87 @@ def test_has_dict_changed(docker_swarm_service):
def test_has_list_changed(docker_swarm_service): def test_has_list_changed(docker_swarm_service):
# List comparisons without dictionaries
# I could improve the indenting, but pycodestyle wants this instead
assert not docker_swarm_service.has_list_changed(None, None)
assert not docker_swarm_service.has_list_changed(None, [])
assert not docker_swarm_service.has_list_changed(None, [1, 2])
assert not docker_swarm_service.has_list_changed([], None)
assert not docker_swarm_service.has_list_changed([], [])
assert docker_swarm_service.has_list_changed([], [1, 2])
assert docker_swarm_service.has_list_changed([1, 2], None)
assert docker_swarm_service.has_list_changed([1, 2], [])
assert docker_swarm_service.has_list_changed([1, 2, 3], [1, 2])
assert docker_swarm_service.has_list_changed([1, 2], [1, 2, 3])
# Check list sorting
assert not docker_swarm_service.has_list_changed([1, 2], [2, 1])
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[ [1, 2],
{"a": 1}, [2, 1],
{"b": 1} sort_lists=False
], )
[
{"a": 1} # Check type matching
] assert docker_swarm_service.has_list_changed([None, 1], [2, 1])
assert docker_swarm_service.has_list_changed([2, 1], [None, 1])
assert docker_swarm_service.has_list_changed(
"command --with args",
['command', '--with', 'args']
) )
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[ ['sleep', '3400'],
{"a": 1}, [u'sleep', u'3600'],
], sort_lists=False
[
{"a": 1},
{"b": 1},
]
) )
# List comparisons with dictionaries
assert not docker_swarm_service.has_list_changed( assert not docker_swarm_service.has_list_changed(
[ [{'a': 1}],
{"a": 1}, [{'a': 1}],
{"b": 1}, sort_key='a'
],
[
{"a": 1},
{"b": 1}
]
) )
assert not docker_swarm_service.has_list_changed( assert not docker_swarm_service.has_list_changed(
None, [{'a': 1}, {'a': 2}],
[ [{'a': 1}, {'a': 2}],
{"b": 1}, sort_key='a'
{"a": 1} )
]
with pytest.raises(Exception):
docker_swarm_service.has_list_changed(
[{'a': 1}, {'a': 2}],
[{'a': 1}, {'a': 2}]
)
# List sort checking with sort key
assert not docker_swarm_service.has_list_changed(
[{'a': 1}, {'a': 2}],
[{'a': 2}, {'a': 1}],
sort_key='a'
) )
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[], [{'a': 1}, {'a': 2}],
[ [{'a': 2}, {'a': 1}],
{"b": 1}, sort_lists=False
{"a": 1}
]
) )
assert not docker_swarm_service.has_list_changed(
None, assert docker_swarm_service.has_list_changed(
None [{'a': 1}, {'a': 2}, {'a': 3}],
[{'a': 2}, {'a': 1}],
sort_key='a'
) )
assert not docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[], [{'a': 1}, {'a': 2}],
None [{'a': 1}, {'a': 2}, {'a': 3}],
) sort_lists=False
assert not docker_swarm_service.has_list_changed(
None,
[]
) )
# Additional dictionary elements
assert not docker_swarm_service.has_list_changed( assert not docker_swarm_service.has_list_changed(
[ [
{"src": 1, "dst": 2}, {"src": 1, "dst": 2},
@ -231,7 +258,8 @@ def test_has_list_changed(docker_swarm_service):
[ [
{"src": 1, "dst": 2, "protocol": "tcp"}, {"src": 1, "dst": 2, "protocol": "tcp"},
{"src": 1, "dst": 2, "protocol": "udp"}, {"src": 1, "dst": 2, "protocol": "udp"},
] ],
sort_key='dst'
) )
assert not docker_swarm_service.has_list_changed( assert not docker_swarm_service.has_list_changed(
[ [
@ -241,7 +269,8 @@ def test_has_list_changed(docker_swarm_service):
[ [
{"src": 1, "dst": 2, "protocol": "udp"}, {"src": 1, "dst": 2, "protocol": "udp"},
{"src": 1, "dst": 3, "protocol": "tcp"}, {"src": 1, "dst": 3, "protocol": "tcp"},
] ],
sort_key='dst'
) )
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[ [
@ -253,7 +282,8 @@ def test_has_list_changed(docker_swarm_service):
{"src": 1, "dst": 3, "protocol": "udp"}, {"src": 1, "dst": 3, "protocol": "udp"},
{"src": 1, "dst": 2, "protocol": "tcp"}, {"src": 1, "dst": 2, "protocol": "tcp"},
{"src": 3, "dst": 4, "protocol": "tcp"}, {"src": 3, "dst": 4, "protocol": "tcp"},
] ],
sort_key='dst'
) )
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[ [
@ -263,7 +293,8 @@ def test_has_list_changed(docker_swarm_service):
[ [
{"src": 1, "dst": 2, "protocol": "tcp"}, {"src": 1, "dst": 2, "protocol": "tcp"},
{"src": 1, "dst": 2, "protocol": "udp"}, {"src": 1, "dst": 2, "protocol": "udp"},
] ],
sort_key='dst'
) )
assert docker_swarm_service.has_list_changed( assert docker_swarm_service.has_list_changed(
[ [
@ -273,11 +304,130 @@ def test_has_list_changed(docker_swarm_service):
[ [
{"src": 1, "dst": 2, "protocol": "udp"}, {"src": 1, "dst": 2, "protocol": "udp"},
{"src": 1, "dst": 2, "protocol": "tcp"}, {"src": 1, "dst": 2, "protocol": "tcp"},
] ],
sort_key='dst'
) )
assert not docker_swarm_service.has_list_changed( assert not docker_swarm_service.has_list_changed(
[{'id': '123', 'aliases': []}], [{'id': '123', 'aliases': []}],
[{'id': '123'}] [{'id': '123'}],
sort_key='id'
)
def test_have_networks_changed(docker_swarm_service):
assert not docker_swarm_service.have_networks_changed(
None,
None
)
assert not docker_swarm_service.have_networks_changed(
[],
None
)
assert not docker_swarm_service.have_networks_changed(
[{'id': 1}],
[{'id': 1}]
)
assert docker_swarm_service.have_networks_changed(
[{'id': 1}],
[{'id': 1}, {'id': 2}]
)
assert not docker_swarm_service.have_networks_changed(
[{'id': 1}, {'id': 2}],
[{'id': 1}, {'id': 2}]
)
assert not docker_swarm_service.have_networks_changed(
[{'id': 1}, {'id': 2}],
[{'id': 2}, {'id': 1}]
)
assert not docker_swarm_service.have_networks_changed(
[
{'id': 1},
{'id': 2, 'aliases': []}
],
[
{'id': 1},
{'id': 2}
]
)
assert docker_swarm_service.have_networks_changed(
[
{'id': 1},
{'id': 2, 'aliases': ['alias1']}
],
[
{'id': 1},
{'id': 2}
]
)
assert docker_swarm_service.have_networks_changed(
[
{'id': 1},
{'id': 2, 'aliases': ['alias1', 'alias2']}
],
[
{'id': 1},
{'id': 2, 'aliases': ['alias1']}
]
)
assert not docker_swarm_service.have_networks_changed(
[
{'id': 1},
{'id': 2, 'aliases': ['alias1', 'alias2']}
],
[
{'id': 1},
{'id': 2, 'aliases': ['alias1', 'alias2']}
]
)
assert not docker_swarm_service.have_networks_changed(
[
{'id': 1},
{'id': 2, 'aliases': ['alias1', 'alias2']}
],
[
{'id': 1},
{'id': 2, 'aliases': ['alias2', 'alias1']}
]
)
assert not docker_swarm_service.have_networks_changed(
[
{'id': 1, 'options': {}},
{'id': 2, 'aliases': ['alias1', 'alias2']}],
[
{'id': 1},
{'id': 2, 'aliases': ['alias2', 'alias1']}
]
)
assert not docker_swarm_service.have_networks_changed(
[
{'id': 1, 'options': {'option1': 'value1'}},
{'id': 2, 'aliases': ['alias1', 'alias2']}],
[
{'id': 1, 'options': {'option1': 'value1'}},
{'id': 2, 'aliases': ['alias2', 'alias1']}
]
)
assert docker_swarm_service.have_networks_changed(
[
{'id': 1, 'options': {'option1': 'value1'}},
{'id': 2, 'aliases': ['alias1', 'alias2']}],
[
{'id': 1, 'options': {'option1': 'value2'}},
{'id': 2, 'aliases': ['alias2', 'alias1']}
]
) )