docker_swarm_service: Allow passing dicts in networks (#58961)

* Add support for passing networks as dicts

* Add function to compare a list of different objects

* Handle comparing falsy values to missing values

* Pass docker versions to Service

* Move can_update_networks to Service class

* Pass Networks in TaskTemplate when supported

* Remove weird __str__

* Add networks integration tests

* Add unit tests

* Add example

* Add changelog fragment

* Make sure that network options are clean

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

* Set networks elements as raw in arg spec

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

* Fix wrong variable naming

* Check for network options that are not valid

* Only check for None options

* Validate that aliases is a list
This commit is contained in:
Hannes Ljungberg 2019-08-18 08:55:54 +02:00 committed by Felix Fontein
parent aaaa4f1809
commit 13364fc530
5 changed files with 701 additions and 236 deletions

View file

@ -0,0 +1,2 @@
minor_changes:
- "docker_swarm_service - Support passing dictionaries in ``networks`` to allow setting ``aliases`` and ``options``."

View file

@ -358,7 +358,9 @@ options:
required: yes
networks:
description:
- List of the service networks names.
- List of the service networks names or dictionaries.
- When passed dictionaries valid sub-options are C(name) which is required and
C(aliases) and C(options).
- Prior to API version 1.29, updating and removing networks is not supported.
If changes are made the service will then be removed and recreated.
- Corresponds to the C(--network) option of C(docker service create).
@ -997,6 +999,17 @@ EXAMPLES = '''
networks:
- mynetwork
- name: Set networks as a dictionary
docker_swarm_service:
name: myservice
image: alpine:edge
networks:
- name: "mynetwork"
aliases:
- "mynetwork_alias"
options:
foo: bar
- name: Set secrets
docker_swarm_service:
name: myservice
@ -1048,6 +1061,7 @@ from ansible.module_utils.docker.common import (
DockerBaseClass,
convert_duration_to_nanosecond,
parse_healthcheck,
clean_dict_booleans_for_docker_api,
RequestException,
)
@ -1116,6 +1130,58 @@ def get_docker_environment(env, env_files):
return sorted(env_list)
def get_docker_networks(networks, network_ids):
"""
Validate a list of network names or a list of network dictionaries.
Network names will be resolved to ids by using the network_ids mapping.
"""
if networks is None:
return None
parsed_networks = []
for network in networks:
if isinstance(network, string_types):
parsed_network = {'name': network}
elif isinstance(network, dict):
if 'name' not in network:
raise TypeError(
'"name" is required when networks are passed as dictionaries.'
)
name = network.pop('name')
parsed_network = {'name': name}
aliases = network.pop('aliases', None)
if aliases is not None:
if not isinstance(aliases, list):
raise TypeError('"aliases" network option is only allowed as a list')
if not all(
isinstance(alias, string_types) for alias in aliases
):
raise TypeError('Only strings are allowed as network aliases.')
parsed_network['aliases'] = aliases
options = network.pop('options', None)
if options is not None:
if not isinstance(options, dict):
raise TypeError('Only dict is allowed as network options.')
parsed_network['options'] = clean_dict_booleans_for_docker_api(options)
# Check if any invalid keys left
if network:
invalid_keys = ', '.join(network.keys())
raise TypeError(
'%s are not valid keys for the networks option' % invalid_keys
)
else:
raise TypeError(
'Only a list of strings or dictionaries are allowed to be passed as networks.'
)
network_name = parsed_network.pop('name')
try:
parsed_network['id'] = network_ids[network_name]
except KeyError as e:
raise ValueError('Could not find a network named: %s.' % e)
parsed_networks.append(parsed_network)
return parsed_networks or []
def get_nanoseconds_from_raw_option(name, value):
if value is None:
return None
@ -1154,14 +1220,17 @@ def has_dict_changed(new_dict, old_dict):
if value is not None
)
for option, value in defined_options.items():
if value != old_dict.get(option):
old_value = old_dict.get(option)
if not value and not old_value:
continue
if value != old_value:
return True
return False
def has_list_of_dicts_changed(new_list, old_list):
def has_list_changed(new_list, old_list):
"""
Check two lists of dicts has differences.
Check two lists has differences.
"""
if new_list is None:
return False
@ -1169,13 +1238,20 @@ def has_list_of_dicts_changed(new_list, old_list):
if len(new_list) != len(old_list):
return True
for new_item, old_item in zip(new_list, old_list):
if has_dict_changed(new_item, old_item):
is_same_type = type(new_item) == type(old_item)
if not is_same_type:
return True
if isinstance(new_item, dict):
if has_dict_changed(new_item, old_item):
return True
elif new_item != old_item:
return True
return False
class DockerService(DockerBaseClass):
def __init__(self):
def __init__(self, docker_api_version, docker_py_version):
super(DockerService, self).__init__()
self.image = ""
self.command = None
@ -1227,7 +1303,9 @@ class DockerService(DockerBaseClass):
self.update_max_failure_ratio = None
self.update_order = None
self.working_dir = None
self.can_update_networks = None
self.docker_api_version = docker_api_version
self.docker_py_version = docker_py_version
def get_facts(self):
return {
@ -1281,6 +1359,22 @@ class DockerService(DockerBaseClass):
'working_dir': self.working_dir,
}
@property
def can_update_networks(self):
# Before Docker API 1.29 adding/removing networks was not supported
return (
self.docker_api_version >= LooseVersion('1.29') and
self.docker_py_version >= LooseVersion('2.7')
)
@property
def can_use_task_template_networks(self):
# In Docker API 1.25 attaching networks to TaskTemplate is preferred over Spec
return (
self.docker_api_version >= LooseVersion('1.25') and
self.docker_py_version >= LooseVersion('2.7')
)
@staticmethod
def get_restart_config_from_ansible_params(params):
restart_config = params['restart_config'] or {}
@ -1474,11 +1568,18 @@ class DockerService(DockerBaseClass):
@classmethod
def from_ansible_params(
cls, ap, old_service, image_digest, can_update_networks, secret_ids, config_ids
cls,
ap,
old_service,
image_digest,
secret_ids,
config_ids,
network_ids,
docker_api_version,
docker_py_version,
):
s = DockerService()
s = DockerService(docker_api_version, docker_py_version)
s.image = image_digest
s.can_update_networks = can_update_networks
s.args = ap['args']
s.endpoint_mode = ap['endpoint_mode']
s.dns = ap['dns']
@ -1491,12 +1592,13 @@ class DockerService(DockerBaseClass):
s.labels = ap['labels']
s.container_labels = ap['container_labels']
s.mode = ap['mode']
s.networks = ap['networks']
s.stop_signal = ap['stop_signal']
s.user = ap['user']
s.working_dir = ap['working_dir']
s.read_only = ap['read_only']
s.networks = get_docker_networks(ap['networks'], network_ids)
s.command = ap['command']
if isinstance(s.command, string_types):
s.command = shlex.split(s.command)
@ -1650,13 +1752,13 @@ class DockerService(DockerBaseClass):
if self.mode != os.mode:
needs_rebuild = True
differences.add('mode', parameter=self.mode, active=os.mode)
if has_list_of_dicts_changed(self.mounts, os.mounts):
if has_list_changed(self.mounts, os.mounts):
differences.add('mounts', parameter=self.mounts, active=os.mounts)
if has_list_of_dicts_changed(self.configs, os.configs):
if has_list_changed(self.configs, os.configs):
differences.add('configs', parameter=self.configs, active=os.configs)
if has_list_of_dicts_changed(self.secrets, os.secrets):
if has_list_changed(self.secrets, os.secrets):
differences.add('secrets', parameter=self.secrets, active=os.secrets)
if self.networks is not None and self.networks != (os.networks or []):
if has_list_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:
@ -1774,18 +1876,6 @@ class DockerService(DockerBaseClass):
old_image = old_image.split('@')[0]
return self.image != old_image, old_image
def __str__(self):
return str({
'mode': self.mode,
'env': self.env,
'endpoint_mode': self.endpoint_mode,
'mounts': self.mounts,
'configs': self.configs,
'secrets': self.secrets,
'networks': self.networks,
'replicas': self.replicas
})
def build_container_spec(self):
mounts = None
if self.mounts is not None:
@ -2000,6 +2090,10 @@ class DockerService(DockerBaseClass):
task_template_args['resources'] = resources
if self.force_update:
task_template_args['force_update'] = self.force_update
if self.can_use_task_template_networks:
networks = self.build_networks()
if networks:
task_template_args['networks'] = networks
return types.TaskTemplate(container_spec=container_spec, **task_template_args)
def build_service_mode(self):
@ -2007,22 +2101,17 @@ class DockerService(DockerBaseClass):
self.replicas = None
return types.ServiceMode(self.mode, replicas=self.replicas)
def build_networks(self, docker_networks):
def build_networks(self):
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)
for network in self.networks:
docker_network = {'Target': network['id']}
if 'aliases' in network:
docker_network['Aliases'] = network['aliases']
if 'options' in network:
docker_network['DriverOpts'] = network['options']
networks.append(docker_network)
return networks
def build_endpoint_spec(self):
@ -2043,7 +2132,7 @@ class DockerService(DockerBaseClass):
endpoint_spec_args['mode'] = self.endpoint_mode
return types.EndpointSpec(**endpoint_spec_args) if endpoint_spec_args else None
def build_docker_service(self, docker_networks):
def build_docker_service(self):
container_spec = self.build_container_spec()
placement = self.build_placement()
task_template = self.build_task_template(container_spec, placement)
@ -2051,7 +2140,6 @@ class DockerService(DockerBaseClass):
update_config = self.build_update_config()
rollback_config = self.build_rollback_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}
@ -2059,12 +2147,14 @@ class DockerService(DockerBaseClass):
service['update_config'] = update_config
if rollback_config:
service['rollback_config'] = rollback_config
if networks:
service['networks'] = networks
if endpoint_spec:
service['endpoint_spec'] = endpoint_spec
if self.labels:
service['labels'] = self.labels
if not self.can_use_task_template_networks:
networks = self.build_networks()
if networks:
service['networks'] = networks
return service
@ -2075,15 +2165,12 @@ class DockerServiceManager(object):
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()]
def get_service(self, name):
try:
raw_data = self.client.inspect_service(name)
except NotFound:
return None
ds = DockerService()
ds = DockerService(self.client.docker_api_version, self.client.docker_py_version)
task_template_data = raw_data['Spec']['TaskTemplate']
ds.image = task_template_data['ContainerSpec']['Image']
@ -2263,24 +2350,22 @@ class DockerServiceManager(object):
'mode': secret_data['File'].get('Mode')
})
networks_names_ids = self.get_networks_names_ids()
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])
network = {'id': network_data['Target']}
if 'Aliases' in network_data:
network['aliases'] = network_data['Aliases']
if 'DriverOpts' in network_data:
network['options'] = network_data['DriverOpts']
ds.networks.append(network)
ds.service_version = raw_data['Version']['Index']
ds.service_id = raw_data['ID']
return ds
def update_service(self, name, old_service, new_service):
service_data = new_service.build_docker_service(self.get_networks_names_ids())
service_data = new_service.build_docker_service()
result = self.client.update_service(
old_service.service_id,
old_service.service_version,
@ -2292,7 +2377,7 @@ class DockerServiceManager(object):
self.client.report_warnings(result, ['Warning'])
def create_service(self, name, service):
service_data = service.build_docker_service(self.get_networks_names_ids())
service_data = service.build_docker_service()
result = self.client.create_service(name=name, **service_data)
self.client.report_warnings(result, ['Warning'])
@ -2313,11 +2398,9 @@ class DockerServiceManager(object):
digest = distribution_data['Descriptor']['digest']
return '%s@%s' % (name, digest)
def can_update_networks(self):
# Before Docker API 1.29 adding/removing networks was not supported
return (
self.client.docker_api_version >= LooseVersion('1.29') and
self.client.docker_py_version >= LooseVersion('2.7')
def get_networks_names_ids(self):
return dict(
(network['Name'], network['Id']) for network in self.client.networks()
)
def get_missing_secret_ids(self):
@ -2392,16 +2475,18 @@ class DockerServiceManager(object):
% (module.params['name'], e)
)
try:
can_update_networks = self.can_update_networks()
secret_ids = self.get_missing_secret_ids()
config_ids = self.get_missing_config_ids()
network_ids = self.get_networks_names_ids()
new_service = DockerService.from_ansible_params(
module.params,
current_service,
image_digest,
can_update_networks,
secret_ids,
config_ids
config_ids,
network_ids,
self.client.docker_api_version,
self.client.docker_py_version
)
except Exception as e:
return self.client.fail(
@ -2567,7 +2652,7 @@ def main():
gid=dict(type='str'),
mode=dict(type='int'),
)),
networks=dict(type='list', elements='str'),
networks=dict(type='list', elements='raw'),
command=dict(type='raw'),
args=dict(type='list', elements='str'),
env=dict(type='raw'),

View file

@ -0,0 +1,448 @@
---
- name: Registering service name
set_fact:
service_name: "{{ name_prefix ~ '-networks' }}"
network_name_1: "{{ name_prefix ~ '-network-1' }}"
network_name_2: "{{ name_prefix ~ '-network-2' }}"
- name: Registering service name
set_fact:
service_names: "{{ service_names + [service_name] }}"
network_names: "{{ network_names + [network_name_1, network_name_2] }}"
- docker_network:
name: "{{ network_name }}"
driver: "overlay"
state: present
loop:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
loop_control:
loop_var: network_name
#####################################################################
## networks #########################################################
#####################################################################
- name: networks
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
register: networks_1
- name: networks (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
register: networks_2
- name: networks (dict 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 }}"
register: networks_3
- name: networks (change more)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
register: networks_4
- name: networks (change more idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
register: networks_5
- name: networks (change more dict 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 }}"
- name: "{{ network_name_2 }}"
register: networks_6
- name: networks (change more mixed 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 }}"
- "{{ network_name_2 }}"
register: networks_7
- name: networks (change mixed order)
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_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 }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_2 }}"
register: networks_10
- name: networks (change less 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 }}"
register: networks_11
- name: networks (empty)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks: []
register: networks_12
- name: networks (empty idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks: []
register: networks_13
- name: networks (unknown network)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "idonotexist"
register: networks_14
ignore_errors: yes
- name: networks (missing dict key name)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- foo: "bar"
register: networks_15
ignore_errors: yes
- name: networks (invalid list type)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- [1, 2, 3]
register: networks_16
ignore_errors: yes
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- networks_1 is changed
- networks_2 is not changed
- networks_3 is not changed
- networks_4 is changed
- 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_14 is failed
- '"Could not find a network named: ''idonotexist''" 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"
- assert:
that:
- networks_4.rebuilt == false
- networks_7.rebuilt == false
when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.7.0', '>=')
- assert:
that:
- networks_4.rebuilt == true
- networks_7.rebuilt == true
when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.7.0', '<')
####################################################################
## networks.aliases ################################################
####################################################################
- name: networks.aliases
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:
- "alias1"
- "alias2"
register: networks_aliases_1
- name: networks.aliases (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:
- "alias1"
- "alias2"
register: networks_aliases_2
- name: networks.aliases (change)
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:
- "alias1"
register: networks_aliases_3
- name: networks.aliases (empty)
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: []
register: networks_aliases_4
- name: networks.aliases (empty 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: []
register: networks_aliases_5
- name: networks.aliases (invalid type)
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:
- [1, 2, 3]
register: networks_aliases_6
ignore_errors: yes
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- networks_aliases_1 is changed
- networks_aliases_2 is not changed
- networks_aliases_3 is 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.options ################################################
####################################################################
- name: networks.options
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 }}"
options:
foo: bar
test: hello
register: networks_options_1
- name: networks.options (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 }}"
options:
foo: bar
test: hello
register: networks_options_2
- name: networks.options (change)
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 }}"
options:
foo: bar
test: hej
register: networks_options_3
- name: networks.options (change less)
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 }}"
options:
foo: bar
register: networks_options_4
- name: networks.options (invalid type)
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 }}"
options: [1, 2, 3]
register: networks_options_5
ignore_errors: yes
- name: networks.options (empty)
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 }}"
options: {}
register: networks_options_6
- name: networks.options (empty 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 }}"
options: {}
register: networks_options_7
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- networks_options_1 is changed
- networks_options_2 is not changed
- networks_options_3 is changed
- networks_options_4 is changed
- networks_options_5 is failed
- "'Only dict is allowed as network options' in networks_options_5.msg"
- networks_options_6 is changed
- networks_options_7 is not changed
####################################################################
####################################################################
####################################################################
- name: Delete networks
docker_network:
name: "{{ network_name }}"
state: absent
force: yes
loop:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
loop_control:
loop_var: network_name
ignore_errors: yes

View file

@ -1,25 +1,12 @@
---
- name: Registering container name
- name: Registering service name
set_fact:
service_name: "{{ name_prefix ~ '-options' }}"
network_name_1: "{{ name_prefix ~ '-network-1' }}"
network_name_2: "{{ name_prefix ~ '-network-2' }}"
- name: Registering container name
- name: Registering service name
set_fact:
service_names: "{{ service_names + [service_name] }}"
network_names: "{{ network_names + [network_name_1, network_name_2] }}"
- docker_network:
name: "{{ network_name }}"
driver: "overlay"
state: present
loop:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
loop_control:
loop_var: network_name
####################################################################
## args ############################################################
@ -1275,119 +1262,6 @@
- mode_2 is not changed
- mode_3 is changed
####################################################################
## networks ########################################################
####################################################################
- name: networks
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
register: networks_1
- name: networks (idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
register: networks_2
- name: networks (change more)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
register: networks_3
- name: networks (change more idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
register: networks_4
- name: networks (change less)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks:
- "{{ network_name_2 }}"
register: networks_5
- name: networks (change less 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 }}"
register: networks_6
- name: networks (empty)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks: []
register: networks_7
- name: networks (empty idempotency)
docker_swarm_service:
name: "{{ service_name }}"
image: alpine:3.8
resolve_image: no
command: '/bin/sh -v -c "sleep 10m"'
networks: []
register: networks_8
- name: cleanup
docker_swarm_service:
name: "{{ service_name }}"
state: absent
diff: no
- assert:
that:
- networks_1 is changed
- networks_2 is not changed
- networks_3 is changed
- networks_4 is not changed
- networks_5 is changed
- networks_6 is not changed
- networks_7 is changed
- networks_8 is not changed
- assert:
that:
- networks_3.rebuilt == false
- networks_5.rebuilt == false
when: docker_api_version is version('1.29', '>=') and docker_py_version is version('2.7.0', '>=')
- assert:
that:
- networks_3.rebuilt == true
- networks_5.rebuilt == true
when: docker_api_version is version('1.29', '<') or docker_py_version is version('2.7.0', '<')
####################################################################
## stop_grace_period ###############################################
####################################################################
@ -1915,30 +1789,3 @@
- working_dir_1 is changed
- working_dir_2 is not changed
- working_dir_3 is changed
####################################################################
####################################################################
####################################################################
- name: Delete networks
docker_network:
name: "{{ network_name }}"
state: absent
force: yes
loop:
- "{{ network_name_1 }}"
- "{{ network_name_2 }}"
loop_control:
loop_var: network_name
ignore_errors: yes
- name: Delete volumes
docker_volume:
name: "{{ volume_name }}"
state: absent
loop:
- "{{ volume_name_1 }}"
- "{{ volume_name_2 }}"
loop_control:
loop_var: volume_name
ignore_errors: yes

View file

@ -168,8 +168,8 @@ def test_has_dict_changed(docker_swarm_service):
)
def test_has_list_of_dicts_changed(docker_swarm_service):
assert docker_swarm_service.has_list_of_dicts_changed(
def test_has_list_changed(docker_swarm_service):
assert docker_swarm_service.has_list_changed(
[
{"a": 1},
{"b": 1}
@ -178,7 +178,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"a": 1}
]
)
assert docker_swarm_service.has_list_of_dicts_changed(
assert docker_swarm_service.has_list_changed(
[
{"a": 1},
],
@ -187,7 +187,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"b": 1},
]
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
[
{"a": 1},
{"b": 1},
@ -197,33 +197,33 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"b": 1}
]
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
None,
[
{"b": 1},
{"a": 1}
]
)
assert docker_swarm_service.has_list_of_dicts_changed(
assert docker_swarm_service.has_list_changed(
[],
[
{"b": 1},
{"a": 1}
]
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
None,
None
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
[],
None
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
None,
[]
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
[
{"src": 1, "dst": 2},
{"src": 1, "dst": 2, "protocol": "udp"},
@ -233,7 +233,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"src": 1, "dst": 2, "protocol": "udp"},
]
)
assert not docker_swarm_service.has_list_of_dicts_changed(
assert not docker_swarm_service.has_list_changed(
[
{"src": 1, "dst": 2, "protocol": "udp"},
{"src": 1, "dst": 3, "protocol": "tcp"},
@ -243,7 +243,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"src": 1, "dst": 3, "protocol": "tcp"},
]
)
assert docker_swarm_service.has_list_of_dicts_changed(
assert docker_swarm_service.has_list_changed(
[
{"src": 1, "dst": 2, "protocol": "udp"},
{"src": 1, "dst": 2},
@ -255,7 +255,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"src": 3, "dst": 4, "protocol": "tcp"},
]
)
assert docker_swarm_service.has_list_of_dicts_changed(
assert docker_swarm_service.has_list_changed(
[
{"src": 1, "dst": 3, "protocol": "tcp"},
{"src": 1, "dst": 2, "protocol": "udp"},
@ -265,7 +265,7 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"src": 1, "dst": 2, "protocol": "udp"},
]
)
assert docker_swarm_service.has_list_of_dicts_changed(
assert docker_swarm_service.has_list_changed(
[
{"src": 1, "dst": 2, "protocol": "udp"},
{"src": 1, "dst": 2, "protocol": "tcp", "extra": {"test": "foo"}},
@ -275,3 +275,86 @@ def test_has_list_of_dicts_changed(docker_swarm_service):
{"src": 1, "dst": 2, "protocol": "tcp"},
]
)
assert not docker_swarm_service.has_list_changed(
[{'id': '123', 'aliases': []}],
[{'id': '123'}]
)
def test_get_docker_networks(docker_swarm_service):
network_names = [
'network_1',
'network_2',
'network_3',
'network_4',
]
networks = [
network_names[0],
{'name': network_names[1]},
{'name': network_names[2], 'aliases': ['networkalias1']},
{'name': network_names[3], 'aliases': ['networkalias2'], 'options': {'foo': 'bar'}},
]
network_ids = {
network_names[0]: '1',
network_names[1]: '2',
network_names[2]: '3',
network_names[3]: '4',
}
parsed_networks = docker_swarm_service.get_docker_networks(
networks,
network_ids
)
assert len(parsed_networks) == 4
for i, network in enumerate(parsed_networks):
assert 'name' not in network
assert 'id' in network
expected_name = network_names[i]
assert network['id'] == network_ids[expected_name]
if i == 2:
assert network['aliases'] == ['networkalias1']
if i == 3:
assert network['aliases'] == ['networkalias2']
if i == 3:
assert 'foo' in network['options']
# Test missing name
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks([{'invalid': 'err'}], {'err': 1})
# test for invalid aliases type
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{'name': 'test', 'aliases': 1}],
{'test': 1}
)
# Test invalid aliases elements
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{'name': 'test', 'aliases': [1]}],
{'test': 1}
)
# Test for invalid options type
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{'name': 'test', 'options': 1}],
{'test': 1}
)
# Test for invalid networks type
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
1,
{'test': 1}
)
# Test for non existing networks
with pytest.raises(ValueError):
docker_swarm_service.get_docker_networks(
[{'name': 'idontexist'}],
{'test': 1}
)
# Test empty values
assert docker_swarm_service.get_docker_networks([], {}) == []
assert docker_swarm_service.get_docker_networks(None, {}) is None
# Test invalid options
with pytest.raises(TypeError):
docker_swarm_service.get_docker_networks(
[{'name': 'test', 'nonexisting_option': 'foo'}],
{'test': '1'}
)