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:
parent
136dc27572
commit
a096cd08c5
9 changed files with 468 additions and 133 deletions
|
@ -0,0 +1,2 @@
|
|||
minor_changes:
|
||||
- "docker_swarm_service - Sort lists when checking for changes."
|
|
@ -1244,19 +1244,59 @@ def has_dict_changed(new_dict, old_dict):
|
|||
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:
|
||||
return False
|
||||
old_list = old_list or []
|
||||
if len(new_list) != len(old_list):
|
||||
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)
|
||||
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 has_dict_changed(new_item, old_item):
|
||||
return True
|
||||
|
@ -1266,6 +1306,35 @@ def has_list_changed(new_list, old_list):
|
|||
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):
|
||||
def __init__(self, docker_api_version, docker_py_version):
|
||||
super(DockerService, self).__init__()
|
||||
|
@ -1761,7 +1830,7 @@ 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 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)
|
||||
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)
|
||||
|
@ -1770,26 +1839,26 @@ class DockerService(DockerBaseClass):
|
|||
if self.mode != os.mode:
|
||||
needs_rebuild = True
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
needs_rebuild = not self.can_update_networks
|
||||
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 or []):
|
||||
if has_list_changed(self.command, os.command, sort_lists=False):
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
if self.labels is not None and self.labels != (os.labels or {}):
|
||||
differences.add('labels', parameter=self.labels, active=os.labels)
|
||||
|
@ -1838,11 +1907,11 @@ 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 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)
|
||||
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)
|
||||
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)
|
||||
if self.has_healthcheck_changed(os):
|
||||
differences.add('healthcheck', parameter=self.healthcheck, active=os.healthcheck)
|
||||
|
|
|
@ -97,6 +97,20 @@
|
|||
register: configs_5
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -104,7 +118,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
configs: []
|
||||
register: configs_6
|
||||
register: configs_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: configs (empty idempotency)
|
||||
|
@ -114,7 +128,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
configs: []
|
||||
register: configs_7
|
||||
register: configs_8
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -130,8 +144,9 @@
|
|||
- configs_3 is changed
|
||||
- configs_4 is not changed
|
||||
- configs_5 is not changed
|
||||
- configs_6 is changed
|
||||
- configs_7 is not changed
|
||||
- configs_6 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', '>=')
|
||||
|
||||
- assert:
|
||||
|
|
|
@ -61,6 +61,21 @@
|
|||
type: "bind"
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -68,7 +83,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
mounts: []
|
||||
register: mounts_4
|
||||
register: mounts_5
|
||||
|
||||
- name: mounts (empty idempotency)
|
||||
docker_swarm_service:
|
||||
|
@ -77,7 +92,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
mounts: []
|
||||
register: mounts_5
|
||||
register: mounts_6
|
||||
|
||||
- name: cleanup
|
||||
docker_swarm_service:
|
||||
|
@ -90,8 +105,9 @@
|
|||
- mounts_1 is changed
|
||||
- mounts_2 is not changed
|
||||
- mounts_3 is changed
|
||||
- mounts_4 is changed
|
||||
- mounts_5 is not changed
|
||||
- mounts_4 is not changed
|
||||
- mounts_5 is changed
|
||||
- mounts_6 is not changed
|
||||
|
||||
####################################################################
|
||||
## mounts.readonly #################################################
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
- "{{ network_name_2 }}"
|
||||
register: networks_7
|
||||
|
||||
- name: networks (change mixed order)
|
||||
- name: networks (order idempotency)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
|
@ -110,17 +110,6 @@
|
|||
- name: "{{ network_name_1 }}"
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -129,7 +118,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks:
|
||||
- "{{ network_name_2 }}"
|
||||
register: networks_10
|
||||
register: networks_9
|
||||
|
||||
- name: networks (change less idempotency)
|
||||
docker_swarm_service:
|
||||
|
@ -139,7 +128,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks:
|
||||
- "{{ network_name_2 }}"
|
||||
register: networks_11
|
||||
register: networks_10
|
||||
|
||||
- name: networks (empty)
|
||||
docker_swarm_service:
|
||||
|
@ -148,7 +137,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks: []
|
||||
register: networks_12
|
||||
register: networks_11
|
||||
|
||||
- name: networks (empty idempotency)
|
||||
docker_swarm_service:
|
||||
|
@ -157,7 +146,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks: []
|
||||
register: networks_13
|
||||
register: networks_12
|
||||
|
||||
- name: networks (unknown network)
|
||||
docker_swarm_service:
|
||||
|
@ -167,7 +156,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks:
|
||||
- "idonotexist"
|
||||
register: networks_14
|
||||
register: networks_13
|
||||
ignore_errors: yes
|
||||
|
||||
- name: networks (missing dict key name)
|
||||
|
@ -178,7 +167,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks:
|
||||
- foo: "bar"
|
||||
register: networks_15
|
||||
register: networks_14
|
||||
ignore_errors: yes
|
||||
|
||||
- name: networks (invalid list type)
|
||||
|
@ -189,7 +178,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
networks:
|
||||
- [1, 2, 3]
|
||||
register: networks_16
|
||||
register: networks_15
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -207,18 +196,17 @@
|
|||
- networks_5 is not changed
|
||||
- networks_6 is not changed
|
||||
- networks_7 is not changed
|
||||
- networks_8 is changed
|
||||
- networks_9 is not changed
|
||||
- networks_10 is changed
|
||||
- networks_11 is not changed
|
||||
- networks_12 is changed
|
||||
- networks_13 is not changed
|
||||
- networks_8 is not changed
|
||||
- networks_9 is changed
|
||||
- networks_10 is not changed
|
||||
- networks_11 is changed
|
||||
- networks_12 is not changed
|
||||
- networks_13 is failed
|
||||
- '"Could not find a network named: ''idonotexist''" in networks_13.msg'
|
||||
- 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
|
||||
- "'\"name\" is required when networks are passed as dictionaries.' 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"
|
||||
- "'Only a list of strings or dictionaries are allowed to be passed as networks' in networks_15.msg"
|
||||
|
||||
- assert:
|
||||
that:
|
||||
|
@ -262,6 +250,19 @@
|
|||
- "alias2"
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -272,7 +273,7 @@
|
|||
- name: "{{ network_name_1 }}"
|
||||
aliases:
|
||||
- "alias1"
|
||||
register: networks_aliases_3
|
||||
register: networks_aliases_4
|
||||
|
||||
- name: networks.aliases (empty)
|
||||
docker_swarm_service:
|
||||
|
@ -283,7 +284,7 @@
|
|||
networks:
|
||||
- name: "{{ network_name_1 }}"
|
||||
aliases: []
|
||||
register: networks_aliases_4
|
||||
register: networks_aliases_5
|
||||
|
||||
- name: networks.aliases (empty idempotency)
|
||||
docker_swarm_service:
|
||||
|
@ -294,7 +295,7 @@
|
|||
networks:
|
||||
- name: "{{ network_name_1 }}"
|
||||
aliases: []
|
||||
register: networks_aliases_5
|
||||
register: networks_aliases_6
|
||||
|
||||
- name: networks.aliases (invalid type)
|
||||
docker_swarm_service:
|
||||
|
@ -306,7 +307,7 @@
|
|||
- name: "{{ network_name_1 }}"
|
||||
aliases:
|
||||
- [1, 2, 3]
|
||||
register: networks_aliases_6
|
||||
register: networks_aliases_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -319,11 +320,12 @@
|
|||
that:
|
||||
- networks_aliases_1 is 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_5 is not changed
|
||||
- networks_aliases_6 is failed
|
||||
- "'Only strings are allowed as network aliases' in networks_aliases_6.msg"
|
||||
- networks_aliases_5 is changed
|
||||
- networks_aliases_6 is not changed
|
||||
- networks_aliases_7 is failed
|
||||
- "'Only strings are allowed as network aliases' in networks_aliases_7.msg"
|
||||
|
||||
####################################################################
|
||||
## networks.options ################################################
|
||||
|
|
|
@ -366,6 +366,18 @@
|
|||
register: dns_options_3
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -373,7 +385,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
dns_options: []
|
||||
register: dns_options_4
|
||||
register: dns_options_5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: dns_options (empty idempotency)
|
||||
|
@ -383,7 +395,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
dns_options: []
|
||||
register: dns_options_5
|
||||
register: dns_options_6
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -397,8 +409,9 @@
|
|||
- 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
|
||||
- dns_options_4 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', '>=')
|
||||
- assert:
|
||||
that:
|
||||
|
@ -588,16 +601,18 @@
|
|||
- "TEST2=val3"
|
||||
register: env_3
|
||||
|
||||
- name: env (empty)
|
||||
- name: env (order idempotency)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
env: []
|
||||
env:
|
||||
- "TEST2=val3"
|
||||
- "TEST1=val1"
|
||||
register: env_4
|
||||
|
||||
- name: env (empty idempotency)
|
||||
- name: env (empty)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
image: alpine:3.8
|
||||
|
@ -606,6 +621,15 @@
|
|||
env: []
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -613,7 +637,7 @@
|
|||
resolve_image: no
|
||||
env:
|
||||
TEST1: true
|
||||
register: env_6
|
||||
register: env_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: env (fail invalid formatted string)
|
||||
|
@ -624,7 +648,7 @@
|
|||
env:
|
||||
- "TEST1=val3"
|
||||
- "TEST2"
|
||||
register: env_7
|
||||
register: env_8
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -638,10 +662,11 @@
|
|||
- env_1 is changed
|
||||
- env_2 is not changed
|
||||
- env_3 is changed
|
||||
- env_4 is changed
|
||||
- env_5 is not changed
|
||||
- env_6 is failed
|
||||
- env_4 is not changed
|
||||
- env_5 is changed
|
||||
- env_6 is not changed
|
||||
- env_7 is failed
|
||||
- env_8 is failed
|
||||
|
||||
####################################################################
|
||||
## env_files #######################################################
|
||||
|
@ -802,6 +827,18 @@
|
|||
register: groups_2
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -810,7 +847,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
groups:
|
||||
- "1234"
|
||||
register: groups_3
|
||||
register: groups_4
|
||||
ignore_errors: yes
|
||||
|
||||
- name: groups (empty)
|
||||
|
@ -820,7 +857,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
groups: []
|
||||
register: groups_4
|
||||
register: groups_5
|
||||
ignore_errors: yes
|
||||
|
||||
- name: groups (empty idempotency)
|
||||
|
@ -830,7 +867,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
groups: []
|
||||
register: groups_5
|
||||
register: groups_6
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -843,9 +880,10 @@
|
|||
that:
|
||||
- groups_1 is changed
|
||||
- groups_2 is not changed
|
||||
- groups_3 is changed
|
||||
- groups_3 is not 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', '>=')
|
||||
- assert:
|
||||
that:
|
||||
|
|
|
@ -142,6 +142,32 @@
|
|||
register: constraints_3
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -150,7 +176,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
placement:
|
||||
constraints: []
|
||||
register: constraints_4
|
||||
register: constraints_6
|
||||
ignore_errors: yes
|
||||
|
||||
- name: placement.constraints (empty idempotency)
|
||||
|
@ -161,7 +187,7 @@
|
|||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
placement:
|
||||
constraints: []
|
||||
register: constraints_5
|
||||
register: constraints_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -178,6 +204,8 @@
|
|||
- constraints_3 is changed
|
||||
- constraints_4 is 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', '>=')
|
||||
- assert:
|
||||
that:
|
||||
|
|
|
@ -97,6 +97,20 @@
|
|||
register: secrets_5
|
||||
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)
|
||||
docker_swarm_service:
|
||||
name: "{{ service_name }}"
|
||||
|
@ -104,7 +118,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
secrets: []
|
||||
register: secrets_6
|
||||
register: secrets_7
|
||||
ignore_errors: yes
|
||||
|
||||
- name: secrets (empty idempotency)
|
||||
|
@ -114,7 +128,7 @@
|
|||
resolve_image: no
|
||||
command: '/bin/sh -v -c "sleep 10m"'
|
||||
secrets: []
|
||||
register: secrets_7
|
||||
register: secrets_8
|
||||
ignore_errors: yes
|
||||
|
||||
- name: cleanup
|
||||
|
@ -130,8 +144,9 @@
|
|||
- secrets_3 is changed
|
||||
- secrets_4 is not changed
|
||||
- secrets_5 is not changed
|
||||
- secrets_6 is changed
|
||||
- secrets_7 is not changed
|
||||
- secrets_6 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', '>=')
|
||||
- assert:
|
||||
that:
|
||||
|
|
|
@ -169,60 +169,87 @@ def test_has_dict_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(
|
||||
[
|
||||
{"a": 1},
|
||||
{"b": 1}
|
||||
],
|
||||
[
|
||||
{"a": 1}
|
||||
]
|
||||
[1, 2],
|
||||
[2, 1],
|
||||
sort_lists=False
|
||||
)
|
||||
|
||||
# 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(
|
||||
[
|
||||
{"a": 1},
|
||||
],
|
||||
[
|
||||
{"a": 1},
|
||||
{"b": 1},
|
||||
]
|
||||
['sleep', '3400'],
|
||||
[u'sleep', u'3600'],
|
||||
sort_lists=False
|
||||
)
|
||||
|
||||
# List comparisons with dictionaries
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[
|
||||
{"a": 1},
|
||||
{"b": 1},
|
||||
],
|
||||
[
|
||||
{"a": 1},
|
||||
{"b": 1}
|
||||
]
|
||||
[{'a': 1}],
|
||||
[{'a': 1}],
|
||||
sort_key='a'
|
||||
)
|
||||
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
None,
|
||||
[
|
||||
{"b": 1},
|
||||
{"a": 1}
|
||||
]
|
||||
[{'a': 1}, {'a': 2}],
|
||||
[{'a': 1}, {'a': 2}],
|
||||
sort_key='a'
|
||||
)
|
||||
|
||||
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(
|
||||
[],
|
||||
[
|
||||
{"b": 1},
|
||||
{"a": 1}
|
||||
]
|
||||
[{'a': 1}, {'a': 2}],
|
||||
[{'a': 2}, {'a': 1}],
|
||||
sort_lists=False
|
||||
)
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
None,
|
||||
None
|
||||
|
||||
assert docker_swarm_service.has_list_changed(
|
||||
[{'a': 1}, {'a': 2}, {'a': 3}],
|
||||
[{'a': 2}, {'a': 1}],
|
||||
sort_key='a'
|
||||
)
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[],
|
||||
None
|
||||
)
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
None,
|
||||
[]
|
||||
assert docker_swarm_service.has_list_changed(
|
||||
[{'a': 1}, {'a': 2}],
|
||||
[{'a': 1}, {'a': 2}, {'a': 3}],
|
||||
sort_lists=False
|
||||
)
|
||||
|
||||
# Additional dictionary elements
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[
|
||||
{"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": "udp"},
|
||||
]
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
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": 3, "protocol": "tcp"},
|
||||
]
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
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": 2, "protocol": "tcp"},
|
||||
{"src": 3, "dst": 4, "protocol": "tcp"},
|
||||
]
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
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": "udp"},
|
||||
]
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
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": "tcp"},
|
||||
]
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[{'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']}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue