v2
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
#
|
||||
# Compat for python2.7
|
||||
#
|
||||
|
||||
# One unittest needs to import builtins via __import__() so we need to have
|
||||
# the string that represents it
|
||||
try:
|
||||
import __builtin__
|
||||
except ImportError:
|
||||
BUILTINS = 'builtins'
|
||||
else:
|
||||
BUILTINS = '__builtin__'
|
||||
@@ -0,0 +1,122 @@
|
||||
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
'''
|
||||
Compat module for Python3.x's unittest.mock module
|
||||
'''
|
||||
import sys
|
||||
|
||||
# Python 2.7
|
||||
|
||||
# Note: Could use the pypi mock library on python3.x as well as python2.x. It
|
||||
# is the same as the python3 stdlib mock library
|
||||
|
||||
try:
|
||||
# Allow wildcard import because we really do want to import all of mock's
|
||||
# symbols into this compat shim
|
||||
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||
from unittest.mock import *
|
||||
except ImportError:
|
||||
# Python 2
|
||||
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||
try:
|
||||
from mock import *
|
||||
except ImportError:
|
||||
print('You need the mock library installed on python2.x to run tests')
|
||||
|
||||
|
||||
# Prior to 3.4.4, mock_open cannot handle binary read_data
|
||||
if sys.version_info >= (3,) and sys.version_info < (3, 4, 4):
|
||||
file_spec = None
|
||||
|
||||
def _iterate_read_data(read_data):
|
||||
# Helper for mock_open:
|
||||
# Retrieve lines from read_data via a generator so that separate calls to
|
||||
# readline, read, and readlines are properly interleaved
|
||||
sep = b'\n' if isinstance(read_data, bytes) else '\n'
|
||||
data_as_list = [l + sep for l in read_data.split(sep)]
|
||||
|
||||
if data_as_list[-1] == sep:
|
||||
# If the last line ended in a newline, the list comprehension will have an
|
||||
# extra entry that's just a newline. Remove this.
|
||||
data_as_list = data_as_list[:-1]
|
||||
else:
|
||||
# If there wasn't an extra newline by itself, then the file being
|
||||
# emulated doesn't have a newline to end the last line remove the
|
||||
# newline that our naive format() added
|
||||
data_as_list[-1] = data_as_list[-1][:-1]
|
||||
|
||||
for line in data_as_list:
|
||||
yield line
|
||||
|
||||
def mock_open(mock=None, read_data=''):
|
||||
"""
|
||||
A helper function to create a mock to replace the use of `open`. It works
|
||||
for `open` called directly or used as a context manager.
|
||||
|
||||
The `mock` argument is the mock object to configure. If `None` (the
|
||||
default) then a `MagicMock` will be created for you, with the API limited
|
||||
to methods or attributes available on standard file handles.
|
||||
|
||||
`read_data` is a string for the `read` methoddline`, and `readlines` of the
|
||||
file handle to return. This is an empty string by default.
|
||||
"""
|
||||
def _readlines_side_effect(*args, **kwargs):
|
||||
if handle.readlines.return_value is not None:
|
||||
return handle.readlines.return_value
|
||||
return list(_data)
|
||||
|
||||
def _read_side_effect(*args, **kwargs):
|
||||
if handle.read.return_value is not None:
|
||||
return handle.read.return_value
|
||||
return type(read_data)().join(_data)
|
||||
|
||||
def _readline_side_effect():
|
||||
if handle.readline.return_value is not None:
|
||||
while True:
|
||||
yield handle.readline.return_value
|
||||
for line in _data:
|
||||
yield line
|
||||
|
||||
global file_spec
|
||||
if file_spec is None:
|
||||
import _io
|
||||
file_spec = list(set(dir(_io.TextIOWrapper)).union(set(dir(_io.BytesIO))))
|
||||
|
||||
if mock is None:
|
||||
mock = MagicMock(name='open', spec=open)
|
||||
|
||||
handle = MagicMock(spec=file_spec)
|
||||
handle.__enter__.return_value = handle
|
||||
|
||||
_data = _iterate_read_data(read_data)
|
||||
|
||||
handle.write.return_value = None
|
||||
handle.read.return_value = None
|
||||
handle.readline.return_value = None
|
||||
handle.readlines.return_value = None
|
||||
|
||||
handle.read.side_effect = _read_side_effect
|
||||
handle.readline.side_effect = _readline_side_effect()
|
||||
handle.readlines.side_effect = _readlines_side_effect
|
||||
|
||||
mock.return_value = handle
|
||||
return mock
|
||||
@@ -0,0 +1,38 @@
|
||||
# (c) 2014, Toshio Kuratomi <tkuratomi@ansible.com>
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
'''
|
||||
Compat module for Python2.7's unittest module
|
||||
'''
|
||||
|
||||
import sys
|
||||
|
||||
# Allow wildcard import because we really do want to import all of
|
||||
# unittests's symbols into this compat shim
|
||||
# pylint: disable=wildcard-import,unused-wildcard-import
|
||||
if sys.version_info < (2, 7):
|
||||
try:
|
||||
# Need unittest2 on python2.6
|
||||
from unittest2 import *
|
||||
except ImportError:
|
||||
print('You need unittest2 installed on python2.6.x to run tests')
|
||||
else:
|
||||
from unittest import *
|
||||
@@ -0,0 +1,72 @@
|
||||
# (c) 2020 Red Hat, Inc.
|
||||
#
|
||||
# This file is part of Ansible
|
||||
#
|
||||
# Ansible is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Ansible is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
# Make coding more python3-ish
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
from io import StringIO
|
||||
import pytest
|
||||
|
||||
from ansible_collections.community.docker.tests.unit.compat import mock
|
||||
from ansible_collections.community.docker.tests.unit.compat import unittest
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.playbook.play_context import PlayContext
|
||||
from ansible_collections.community.docker.plugins.connection.docker import Connection as DockerConnection
|
||||
from ansible.plugins.loader import connection_loader
|
||||
|
||||
|
||||
class TestDockerConnectionClass(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.play_context = PlayContext()
|
||||
self.play_context.prompt = (
|
||||
'[sudo via ansible, key=ouzmdnewuhucvuaabtjmweasarviygqq] password: '
|
||||
)
|
||||
self.in_stream = StringIO()
|
||||
with mock.patch('ansible_collections.community.docker.plugins.connection.docker.get_bin_path', return_value='docker'):
|
||||
self.dc = connection_loader.get('community.docker.docker', self.play_context, self.in_stream)
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version',
|
||||
return_value=('false', 'garbage', '', 1))
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version',
|
||||
return_value=('docker version', '1.2.3', '', 0))
|
||||
def test_docker_connection_module_too_old(self, mock_new_docker_verison, mock_old_docker_version):
|
||||
self.dc._version = None
|
||||
self.dc.remote_user = 'foo'
|
||||
self.assertRaisesRegexp(AnsibleError, '^docker connection type requires docker 1.3 or higher$', self.dc._get_actual_user)
|
||||
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version',
|
||||
return_value=('false', 'garbage', '', 1))
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version',
|
||||
return_value=('docker version', '1.7.0', '', 0))
|
||||
def test_docker_connection_module(self, mock_new_docker_verison, mock_old_docker_version):
|
||||
self.dc._version = None
|
||||
version = self.dc.docker_version
|
||||
|
||||
# old version and new version fail
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._old_docker_version',
|
||||
return_value=('false', 'garbage', '', 1))
|
||||
@mock.patch('ansible_collections.community.docker.plugins.connection.docker.Connection._new_docker_version',
|
||||
return_value=('false', 'garbage', '', 1))
|
||||
def test_docker_connection_module_wrong_cmd(self, mock_new_docker_version, mock_old_docker_version):
|
||||
self.dc._version = None
|
||||
self.dc.remote_user = 'foo'
|
||||
self.assertRaisesRegexp(AnsibleError, '^Docker version check (.*?) failed: ', self.dc._get_actual_user)
|
||||
@@ -0,0 +1,228 @@
|
||||
# Copyright (c), Felix Fontein <felix@fontein.de>, 2020
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
|
||||
import json
|
||||
import textwrap
|
||||
|
||||
import pytest
|
||||
|
||||
from mock import MagicMock
|
||||
|
||||
from ansible import constants as C
|
||||
from ansible.errors import AnsibleError
|
||||
from ansible.inventory.data import InventoryData
|
||||
from ansible.inventory.manager import InventoryManager
|
||||
|
||||
from ansible_collections.community.docker.plugins.inventory.docker_containers import InventoryModule
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def inventory():
|
||||
r = InventoryModule()
|
||||
r.inventory = InventoryData()
|
||||
return r
|
||||
|
||||
|
||||
LOVING_THARP = {
|
||||
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||
'Name': '/loving_tharp',
|
||||
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||
'State': {
|
||||
'Running': True,
|
||||
},
|
||||
'Config': {
|
||||
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
LOVING_THARP_STACK = {
|
||||
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||
'Name': '/loving_tharp',
|
||||
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||
'State': {
|
||||
'Running': True,
|
||||
},
|
||||
'Config': {
|
||||
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||
'Labels': {
|
||||
'com.docker.stack.namespace': 'my_stack',
|
||||
},
|
||||
},
|
||||
'NetworkSettings': {
|
||||
'Ports': {
|
||||
'22/tcp': [
|
||||
{
|
||||
'HostIp': '0.0.0.0',
|
||||
'HostPort': '32802'
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
LOVING_THARP_SERVICE = {
|
||||
'Id': '7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a',
|
||||
'Name': '/loving_tharp',
|
||||
'Image': 'sha256:349f492ff18add678364a62a67ce9a13487f14293ae0af1baf02398aa432f385',
|
||||
'State': {
|
||||
'Running': True,
|
||||
},
|
||||
'Config': {
|
||||
'Image': 'quay.io/ansible/ubuntu1804-test-container:1.21.0',
|
||||
'Labels': {
|
||||
'com.docker.swarm.service.name': 'my_service',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def create_get_option(options, default=False):
|
||||
def get_option(option):
|
||||
if option in options:
|
||||
return options[option]
|
||||
return default
|
||||
|
||||
return get_option
|
||||
|
||||
|
||||
class FakeClient(object):
|
||||
def __init__(self, *hosts):
|
||||
self.hosts = dict()
|
||||
self.list_reply = []
|
||||
for host in hosts:
|
||||
self.list_reply.append({
|
||||
'Id': host['Id'],
|
||||
'Names': [host['Name']] if host['Name'] else [],
|
||||
'Image': host['Config']['Image'],
|
||||
'ImageId': host['Image'],
|
||||
})
|
||||
self.hosts[host['Name']] = host
|
||||
self.hosts[host['Id']] = host
|
||||
|
||||
def containers(self, all=False):
|
||||
return list(self.list_reply)
|
||||
|
||||
def inspect_container(self, id):
|
||||
return self.hosts[id]
|
||||
|
||||
def port(self, container, port):
|
||||
host = self.hosts[container['Id']]
|
||||
network_settings = host.get('NetworkSettings') or dict()
|
||||
ports = network_settings.get('Ports') or dict()
|
||||
return ports.get('{0}/tcp'.format(port)) or []
|
||||
|
||||
|
||||
def test_populate(inventory, mocker):
|
||||
client = FakeClient(LOVING_THARP)
|
||||
|
||||
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||
'verbose_output': True,
|
||||
'connection_type': 'docker-api',
|
||||
'add_legacy_groups': False,
|
||||
'compose': {},
|
||||
'groups': {},
|
||||
'keyed_groups': {},
|
||||
}))
|
||||
inventory._populate(client)
|
||||
|
||||
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||
host_1_vars = host_1.get_vars()
|
||||
|
||||
assert host_1_vars['ansible_host'] == 'loving_tharp'
|
||||
assert host_1_vars['ansible_connection'] == 'community.docker.docker_api'
|
||||
assert 'ansible_ssh_host' not in host_1_vars
|
||||
assert 'ansible_ssh_port' not in host_1_vars
|
||||
assert 'docker_state' in host_1_vars
|
||||
assert 'docker_config' in host_1_vars
|
||||
assert 'docker_image' in host_1_vars
|
||||
|
||||
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||
assert len(inventory.inventory.groups) == 2
|
||||
assert len(inventory.inventory.hosts) == 1
|
||||
|
||||
|
||||
def test_populate_service(inventory, mocker):
|
||||
client = FakeClient(LOVING_THARP_SERVICE)
|
||||
|
||||
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||
'verbose_output': False,
|
||||
'connection_type': 'docker-cli',
|
||||
'add_legacy_groups': True,
|
||||
'compose': {},
|
||||
'groups': {},
|
||||
'keyed_groups': {},
|
||||
'docker_host': 'unix://var/run/docker.sock',
|
||||
}))
|
||||
inventory._populate(client)
|
||||
|
||||
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||
host_1_vars = host_1.get_vars()
|
||||
|
||||
assert host_1_vars['ansible_host'] == 'loving_tharp'
|
||||
assert host_1_vars['ansible_connection'] == 'community.docker.docker'
|
||||
assert 'ansible_ssh_host' not in host_1_vars
|
||||
assert 'ansible_ssh_port' not in host_1_vars
|
||||
assert 'docker_state' not in host_1_vars
|
||||
assert 'docker_config' not in host_1_vars
|
||||
assert 'docker_image' not in host_1_vars
|
||||
|
||||
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['running'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['stopped'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['service_my_service'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1
|
||||
assert len(inventory.inventory.groups) == 10
|
||||
assert len(inventory.inventory.hosts) == 1
|
||||
|
||||
|
||||
def test_populate_stack(inventory, mocker):
|
||||
client = FakeClient(LOVING_THARP_STACK)
|
||||
|
||||
inventory.get_option = mocker.MagicMock(side_effect=create_get_option({
|
||||
'verbose_output': False,
|
||||
'connection_type': 'ssh',
|
||||
'add_legacy_groups': True,
|
||||
'compose': {},
|
||||
'groups': {},
|
||||
'keyed_groups': {},
|
||||
'docker_host': 'unix://var/run/docker.sock',
|
||||
'default_ip': '127.0.0.1',
|
||||
'private_ssh_port': 22,
|
||||
}))
|
||||
inventory._populate(client)
|
||||
|
||||
host_1 = inventory.inventory.get_host('loving_tharp')
|
||||
host_1_vars = host_1.get_vars()
|
||||
|
||||
assert host_1_vars['ansible_ssh_host'] == '127.0.0.1'
|
||||
assert host_1_vars['ansible_ssh_port'] == '32802'
|
||||
assert 'ansible_host' not in host_1_vars
|
||||
assert 'ansible_connection' not in host_1_vars
|
||||
assert 'docker_state' not in host_1_vars
|
||||
assert 'docker_config' not in host_1_vars
|
||||
assert 'docker_image' not in host_1_vars
|
||||
|
||||
assert len(inventory.inventory.groups['ungrouped'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['all'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['7bd547963679e'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['7bd547963679e3209cafd52aff21840b755c96fd37abcd7a6e19da8da6a7f49a'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['image_quay.io/ansible/ubuntu1804-test-container:1.21.0'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['loving_tharp'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['running'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['stopped'].hosts) == 0
|
||||
assert len(inventory.inventory.groups['stack_my_stack'].hosts) == 1
|
||||
assert len(inventory.inventory.groups['unix://var/run/docker.sock'].hosts) == 1
|
||||
assert len(inventory.inventory.groups) == 10
|
||||
assert len(inventory.inventory.hosts) == 1
|
||||
@@ -0,0 +1,518 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible_collections.community.docker.plugins.module_utils.common import (
|
||||
compare_dict_allow_more_present,
|
||||
compare_generic,
|
||||
convert_duration_to_nanosecond,
|
||||
parse_healthcheck
|
||||
)
|
||||
|
||||
DICT_ALLOW_MORE_PRESENT = (
|
||||
{
|
||||
'av': {},
|
||||
'bv': {'a': 1},
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'av': {'a': 1},
|
||||
'bv': {'a': 1, 'b': 2},
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'av': {'a': 1},
|
||||
'bv': {'b': 2},
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'av': {'a': 1},
|
||||
'bv': {'a': None, 'b': 1},
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'av': {'a': None},
|
||||
'bv': {'b': 1},
|
||||
'result': False
|
||||
},
|
||||
)
|
||||
|
||||
COMPARE_GENERIC = [
|
||||
########################################################################################
|
||||
# value
|
||||
{
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'method': 'strict',
|
||||
'type': 'value',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': 'hello',
|
||||
'b': 'hello',
|
||||
'method': 'strict',
|
||||
'type': 'value',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': None,
|
||||
'b': 'hello',
|
||||
'method': 'strict',
|
||||
'type': 'value',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': None,
|
||||
'b': None,
|
||||
'method': 'strict',
|
||||
'type': 'value',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'method': 'ignore',
|
||||
'type': 'value',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': None,
|
||||
'b': 2,
|
||||
'method': 'ignore',
|
||||
'type': 'value',
|
||||
'result': True
|
||||
},
|
||||
########################################################################################
|
||||
# list
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'list',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'x',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'list',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'list',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
'x',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'list',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'list',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'list',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'list',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'z',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
'x',
|
||||
'z',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'list',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
'x',
|
||||
],
|
||||
'method': 'ignore',
|
||||
'type': 'list',
|
||||
'result': True
|
||||
},
|
||||
########################################################################################
|
||||
# set
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'x',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
'x',
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'z',
|
||||
],
|
||||
'b': [
|
||||
'x',
|
||||
'y',
|
||||
'x',
|
||||
'z',
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
'x',
|
||||
'a',
|
||||
],
|
||||
'b': [
|
||||
'y',
|
||||
'z',
|
||||
],
|
||||
'method': 'ignore',
|
||||
'type': 'set',
|
||||
'result': True
|
||||
},
|
||||
########################################################################################
|
||||
# set(dict)
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
],
|
||||
'b': [
|
||||
{'y': 1},
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set(dict)',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1},
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1, 'y': 2},
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
{'x': 2, 'y': 3},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1},
|
||||
{'x': 2, 'y': 3},
|
||||
],
|
||||
'method': 'strict',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1, 'z': 2},
|
||||
{'x': 2, 'y': 3},
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1, 'y': 2},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1},
|
||||
{'x': 2, 'y': 3},
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set(dict)',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1, 'y': 3},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1},
|
||||
{'x': 1, 'y': 3, 'z': 4},
|
||||
],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': [
|
||||
{'x': 1},
|
||||
{'x': 2, 'y': 3},
|
||||
],
|
||||
'b': [
|
||||
{'x': 1},
|
||||
],
|
||||
'method': 'ignore',
|
||||
'type': 'set(dict)',
|
||||
'result': True
|
||||
},
|
||||
########################################################################################
|
||||
# dict
|
||||
{
|
||||
'a': {'x': 1},
|
||||
'b': {'y': 1},
|
||||
'method': 'strict',
|
||||
'type': 'dict',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': {'x': 1},
|
||||
'b': {'x': 1, 'y': 2},
|
||||
'method': 'strict',
|
||||
'type': 'dict',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': {'x': 1},
|
||||
'b': {'x': 1},
|
||||
'method': 'strict',
|
||||
'type': 'dict',
|
||||
'result': True
|
||||
},
|
||||
{
|
||||
'a': {'x': 1, 'z': 2},
|
||||
'b': {'x': 1, 'y': 2},
|
||||
'method': 'strict',
|
||||
'type': 'dict',
|
||||
'result': False
|
||||
},
|
||||
{
|
||||
'a': {'x': 1, 'z': 2},
|
||||
'b': {'x': 1, 'y': 2},
|
||||
'method': 'ignore',
|
||||
'type': 'dict',
|
||||
'result': True
|
||||
},
|
||||
] + [{
|
||||
'a': entry['av'],
|
||||
'b': entry['bv'],
|
||||
'method': 'allow_more_present',
|
||||
'type': 'dict',
|
||||
'result': entry['result']
|
||||
} for entry in DICT_ALLOW_MORE_PRESENT]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("entry", DICT_ALLOW_MORE_PRESENT)
|
||||
def test_dict_allow_more_present(entry):
|
||||
assert compare_dict_allow_more_present(entry['av'], entry['bv']) == entry['result']
|
||||
|
||||
|
||||
@pytest.mark.parametrize("entry", COMPARE_GENERIC)
|
||||
def test_compare_generic(entry):
|
||||
assert compare_generic(entry['a'], entry['b'], entry['method'], entry['type']) == entry['result']
|
||||
|
||||
|
||||
def test_convert_duration_to_nanosecond():
|
||||
nanoseconds = convert_duration_to_nanosecond('5s')
|
||||
assert nanoseconds == 5000000000
|
||||
nanoseconds = convert_duration_to_nanosecond('1m5s')
|
||||
assert nanoseconds == 65000000000
|
||||
with pytest.raises(ValueError):
|
||||
convert_duration_to_nanosecond([1, 2, 3])
|
||||
with pytest.raises(ValueError):
|
||||
convert_duration_to_nanosecond('10x')
|
||||
|
||||
|
||||
def test_parse_healthcheck():
|
||||
result, disabled = parse_healthcheck({
|
||||
'test': 'sleep 1',
|
||||
'interval': '1s',
|
||||
})
|
||||
assert disabled is False
|
||||
assert result == {
|
||||
'test': ['CMD-SHELL', 'sleep 1'],
|
||||
'interval': 1000000000
|
||||
}
|
||||
|
||||
result, disabled = parse_healthcheck({
|
||||
'test': ['NONE'],
|
||||
})
|
||||
assert result is None
|
||||
assert disabled
|
||||
|
||||
result, disabled = parse_healthcheck({
|
||||
'test': 'sleep 1',
|
||||
'interval': '1s423ms'
|
||||
})
|
||||
assert result == {
|
||||
'test': ['CMD-SHELL', 'sleep 1'],
|
||||
'interval': 1423000000
|
||||
}
|
||||
assert disabled is False
|
||||
|
||||
result, disabled = parse_healthcheck({
|
||||
'test': 'sleep 1',
|
||||
'interval': '1h1m2s3ms4us'
|
||||
})
|
||||
assert result == {
|
||||
'test': ['CMD-SHELL', 'sleep 1'],
|
||||
'interval': 3662003004000
|
||||
}
|
||||
assert disabled is False
|
||||
@@ -0,0 +1,31 @@
|
||||
# Copyright (c) 2017 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible.module_utils.six import string_types
|
||||
from ansible.module_utils.common.text.converters import to_bytes
|
||||
from ansible.module_utils.common._collections_compat import MutableMapping
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def patch_ansible_module(request, mocker):
|
||||
if isinstance(request.param, string_types):
|
||||
args = request.param
|
||||
elif isinstance(request.param, MutableMapping):
|
||||
if 'ANSIBLE_MODULE_ARGS' not in request.param:
|
||||
request.param = {'ANSIBLE_MODULE_ARGS': request.param}
|
||||
if '_ansible_remote_tmp' not in request.param['ANSIBLE_MODULE_ARGS']:
|
||||
request.param['ANSIBLE_MODULE_ARGS']['_ansible_remote_tmp'] = '/tmp'
|
||||
if '_ansible_keep_remote_files' not in request.param['ANSIBLE_MODULE_ARGS']:
|
||||
request.param['ANSIBLE_MODULE_ARGS']['_ansible_keep_remote_files'] = False
|
||||
args = json.dumps(request.param)
|
||||
else:
|
||||
raise Exception('Malformed data to the patch_ansible_module pytest fixture')
|
||||
|
||||
mocker.patch('ansible.module_utils.basic._ANSIBLE_ARGS', to_bytes(args))
|
||||
@@ -0,0 +1,22 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import unittest
|
||||
|
||||
from ansible_collections.community.docker.plugins.modules.docker_container import TaskParameters
|
||||
|
||||
|
||||
class TestTaskParameters(unittest.TestCase):
|
||||
"""Unit tests for TaskParameters."""
|
||||
|
||||
def test_parse_exposed_ports_tcp_udp(self):
|
||||
"""
|
||||
Ensure _parse_exposed_ports does not cancel ports with the same
|
||||
number but different protocol.
|
||||
"""
|
||||
task_params = TaskParameters.__new__(TaskParameters)
|
||||
task_params.exposed_ports = None
|
||||
result = task_params._parse_exposed_ports([80, '443', '443/udp'])
|
||||
self.assertTrue((80, 'tcp') in result)
|
||||
self.assertTrue((443, 'tcp') in result)
|
||||
self.assertTrue((443, 'udp') in result)
|
||||
@@ -0,0 +1,31 @@
|
||||
"""Unit tests for docker_network."""
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible_collections.community.docker.plugins.modules.docker_network import validate_cidr
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cidr,expected", [
|
||||
('192.168.0.1/16', 'ipv4'),
|
||||
('192.168.0.1/24', 'ipv4'),
|
||||
('192.168.0.1/32', 'ipv4'),
|
||||
('fdd1:ac8c:0557:7ce2::/64', 'ipv6'),
|
||||
('fdd1:ac8c:0557:7ce2::/128', 'ipv6'),
|
||||
])
|
||||
def test_validate_cidr_positives(cidr, expected):
|
||||
assert validate_cidr(cidr) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cidr", [
|
||||
'192.168.0.1',
|
||||
'192.168.0.1/34',
|
||||
'192.168.0.1/asd',
|
||||
'fdd1:ac8c:0557:7ce2::',
|
||||
])
|
||||
def test_validate_cidr_negatives(cidr):
|
||||
with pytest.raises(ValueError) as e:
|
||||
validate_cidr(cidr)
|
||||
assert '"{0}" is not a valid CIDR'.format(cidr) == str(e.value)
|
||||
@@ -0,0 +1,510 @@
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class APIErrorMock(Exception):
|
||||
def __init__(self, message, response=None, explanation=None):
|
||||
self.message = message
|
||||
self.response = response
|
||||
self.explanation = explanation
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def docker_module_mock(mocker):
|
||||
docker_module_mock = mocker.MagicMock()
|
||||
docker_utils_module_mock = mocker.MagicMock()
|
||||
docker_errors_module_mock = mocker.MagicMock()
|
||||
docker_errors_module_mock.APIError = APIErrorMock
|
||||
mock_modules = {
|
||||
'docker': docker_module_mock,
|
||||
'docker.utils': docker_utils_module_mock,
|
||||
'docker.errors': docker_errors_module_mock,
|
||||
}
|
||||
return mocker.patch.dict('sys.modules', **mock_modules)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def docker_swarm_service():
|
||||
from ansible_collections.community.docker.plugins.modules import docker_swarm_service
|
||||
|
||||
return docker_swarm_service
|
||||
|
||||
|
||||
def test_retry_on_out_of_sequence_error(mocker, docker_swarm_service):
|
||||
run_mock = mocker.MagicMock(
|
||||
side_effect=APIErrorMock(
|
||||
message='',
|
||||
response=None,
|
||||
explanation='rpc error: code = Unknown desc = update out of sequence',
|
||||
)
|
||||
)
|
||||
manager = docker_swarm_service.DockerServiceManager(client=None)
|
||||
manager.run = run_mock
|
||||
with pytest.raises(APIErrorMock):
|
||||
manager.run_safe()
|
||||
assert run_mock.call_count == 3
|
||||
|
||||
|
||||
def test_no_retry_on_general_api_error(mocker, docker_swarm_service):
|
||||
run_mock = mocker.MagicMock(
|
||||
side_effect=APIErrorMock(message='', response=None, explanation='some error')
|
||||
)
|
||||
manager = docker_swarm_service.DockerServiceManager(client=None)
|
||||
manager.run = run_mock
|
||||
with pytest.raises(APIErrorMock):
|
||||
manager.run_safe()
|
||||
assert run_mock.call_count == 1
|
||||
|
||||
|
||||
def test_get_docker_environment(mocker, docker_swarm_service):
|
||||
env_file_result = {'TEST1': 'A', 'TEST2': 'B', 'TEST3': 'C'}
|
||||
env_dict = {'TEST3': 'CC', 'TEST4': 'D'}
|
||||
env_string = "TEST3=CC,TEST4=D"
|
||||
|
||||
env_list = ['TEST3=CC', 'TEST4=D']
|
||||
expected_result = sorted(['TEST1=A', 'TEST2=B', 'TEST3=CC', 'TEST4=D'])
|
||||
mocker.patch.object(
|
||||
docker_swarm_service, 'parse_env_file', return_value=env_file_result
|
||||
)
|
||||
mocker.patch.object(
|
||||
docker_swarm_service,
|
||||
'format_environment',
|
||||
side_effect=lambda d: ['{0}={1}'.format(key, value) for key, value in d.items()],
|
||||
)
|
||||
# Test with env dict and file
|
||||
result = docker_swarm_service.get_docker_environment(
|
||||
env_dict, env_files=['dummypath']
|
||||
)
|
||||
assert result == expected_result
|
||||
# Test with env list and file
|
||||
result = docker_swarm_service.get_docker_environment(
|
||||
env_list,
|
||||
env_files=['dummypath']
|
||||
)
|
||||
assert result == expected_result
|
||||
# Test with env string and file
|
||||
result = docker_swarm_service.get_docker_environment(
|
||||
env_string, env_files=['dummypath']
|
||||
)
|
||||
assert result == expected_result
|
||||
|
||||
assert result == expected_result
|
||||
# Test with empty env
|
||||
result = docker_swarm_service.get_docker_environment(
|
||||
[], env_files=None
|
||||
)
|
||||
assert result == []
|
||||
# Test with empty env_files
|
||||
result = docker_swarm_service.get_docker_environment(
|
||||
None, env_files=[]
|
||||
)
|
||||
assert result == []
|
||||
|
||||
|
||||
def test_get_nanoseconds_from_raw_option(docker_swarm_service):
|
||||
value = docker_swarm_service.get_nanoseconds_from_raw_option('test', None)
|
||||
assert value is None
|
||||
|
||||
value = docker_swarm_service.get_nanoseconds_from_raw_option('test', '1m30s535ms')
|
||||
assert value == 90535000000
|
||||
|
||||
value = docker_swarm_service.get_nanoseconds_from_raw_option('test', 10000000000)
|
||||
assert value == 10000000000
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
docker_swarm_service.get_nanoseconds_from_raw_option('test', [])
|
||||
|
||||
|
||||
def test_has_dict_changed(docker_swarm_service):
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
{"a": 1},
|
||||
{"a": 1},
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
{"a": 1},
|
||||
{"a": 1, "b": 2}
|
||||
)
|
||||
assert docker_swarm_service.has_dict_changed(
|
||||
{"a": 1},
|
||||
{"a": 2, "b": 2}
|
||||
)
|
||||
assert docker_swarm_service.has_dict_changed(
|
||||
{"a": 1, "b": 1},
|
||||
{"a": 1}
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
None,
|
||||
{"a": 2, "b": 2}
|
||||
)
|
||||
assert docker_swarm_service.has_dict_changed(
|
||||
{},
|
||||
{"a": 2, "b": 2}
|
||||
)
|
||||
assert docker_swarm_service.has_dict_changed(
|
||||
{"a": 1},
|
||||
{}
|
||||
)
|
||||
assert docker_swarm_service.has_dict_changed(
|
||||
{"a": 1},
|
||||
None
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
{},
|
||||
{}
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
None,
|
||||
None
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
{},
|
||||
None
|
||||
)
|
||||
assert not docker_swarm_service.has_dict_changed(
|
||||
None,
|
||||
{}
|
||||
)
|
||||
|
||||
|
||||
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(
|
||||
[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(
|
||||
['sleep', '3400'],
|
||||
[u'sleep', u'3600'],
|
||||
sort_lists=False
|
||||
)
|
||||
|
||||
# List comparisons with dictionaries
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[{'a': 1}],
|
||||
[{'a': 1}],
|
||||
sort_key='a'
|
||||
)
|
||||
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[{'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(
|
||||
[{'a': 1}, {'a': 2}],
|
||||
[{'a': 2}, {'a': 1}],
|
||||
sort_lists=False
|
||||
)
|
||||
|
||||
assert docker_swarm_service.has_list_changed(
|
||||
[{'a': 1}, {'a': 2}, {'a': 3}],
|
||||
[{'a': 2}, {'a': 1}],
|
||||
sort_key='a'
|
||||
)
|
||||
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},
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
],
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "tcp"},
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
assert not docker_swarm_service.has_list_changed(
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
{"src": 1, "dst": 3, "protocol": "tcp"},
|
||||
],
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
{"src": 1, "dst": 3, "protocol": "tcp"},
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
assert docker_swarm_service.has_list_changed(
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
{"src": 1, "dst": 2},
|
||||
{"src": 3, "dst": 4},
|
||||
],
|
||||
[
|
||||
{"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(
|
||||
[
|
||||
{"src": 1, "dst": 3, "protocol": "tcp"},
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
],
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "tcp"},
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
],
|
||||
sort_key='dst'
|
||||
)
|
||||
assert docker_swarm_service.has_list_changed(
|
||||
[
|
||||
{"src": 1, "dst": 2, "protocol": "udp"},
|
||||
{"src": 1, "dst": 2, "protocol": "tcp", "extra": {"test": "foo"}},
|
||||
],
|
||||
[
|
||||
{"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'}],
|
||||
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']}
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
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'}
|
||||
)
|
||||
@@ -0,0 +1,36 @@
|
||||
# Copyright (c) 2018 Ansible Project
|
||||
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
||||
|
||||
from __future__ import (absolute_import, division, print_function)
|
||||
__metaclass__ = type
|
||||
|
||||
import json
|
||||
|
||||
import pytest
|
||||
|
||||
from ansible_collections.community.docker.plugins.modules import docker_volume
|
||||
from ansible_collections.community.docker.plugins.module_utils import common
|
||||
|
||||
pytestmark = pytest.mark.usefixtures('patch_ansible_module')
|
||||
|
||||
TESTCASE_DOCKER_VOLUME = [
|
||||
{
|
||||
'name': 'daemon_config',
|
||||
'state': 'present'
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('patch_ansible_module', TESTCASE_DOCKER_VOLUME, indirect=['patch_ansible_module'])
|
||||
def test_create_volume_on_invalid_docker_version(mocker, capfd):
|
||||
mocker.patch.object(common, 'HAS_DOCKER_PY', True)
|
||||
mocker.patch.object(common, 'docker_version', '1.8.0')
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
docker_volume.main()
|
||||
|
||||
out, dummy = capfd.readouterr()
|
||||
results = json.loads(out)
|
||||
assert results['failed']
|
||||
assert 'Error: Docker SDK for Python version is 1.8.0 ' in results['msg']
|
||||
assert 'Minimum version required is 1.10.0.' in results['msg']
|
||||
@@ -0,0 +1,2 @@
|
||||
unittest2 ; python_version < '2.7'
|
||||
importlib ; python_version < '2.7'
|
||||
Reference in New Issue
Block a user