From 1d697680d2286153448fb6fe6085251aeb3fad1e Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sat, 6 May 2023 13:49:01 +0200 Subject: [PATCH 1/7] Full support to networking config during container creation Signed-off-by: Mariano Scazzariello --- docker/models/containers.py | 44 +++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 2eeefda1..5ba6297b 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -8,7 +8,7 @@ from ..errors import ( ContainerError, DockerException, ImageNotFound, NotFound, create_unexpected_kwargs_error ) -from ..types import HostConfig +from ..types import EndpointConfig, HostConfig, NetworkingConfig from ..utils import version_gte from .images import Image from .resource import Collection, Model @@ -680,10 +680,32 @@ class ContainerCollection(Collection): This mode is incompatible with ``ports``. Incompatible with ``network``. - network_driver_opt (dict): A dictionary of options to provide - to the network driver. Defaults to ``None``. Used in - conjuction with ``network``. Incompatible - with ``network_mode``. + network_config (dict): A dictionary containing options that are + passed to the network driver during the connection. + Defaults to ``None``. + The dictionary contains the following keys: + + - ``aliases`` (:py:class:`list`): A list of aliases for + the network endpoint. + Names in that list can be used within the network to + reach this container. Defaults to ``None``. + - ``links`` (:py:class:`list`): A list of links for + the network endpoint endpoint. + Containers declared in this list will be linked to this + container. Defaults to ``None``. + - ``ipv4_address`` (str): The IP address to assign to + this container on the network, using the IPv4 protocol. + Defaults to ``None``. + - ``ipv6_address`` (str): The IP address to assign to + this container on the network, using the IPv6 protocol. + Defaults to ``None``. + - ``link_local_ips`` (:py:class:`list`): A list of link-local + (IPv4/IPv6) addresses. + - ``driver_opt`` (dict): A dictionary of options to provide to + the network driver. Defaults to ``None``. + + Used in conjuction with ``network``. + Incompatible with ``network_mode``. oom_kill_disable (bool): Whether to disable OOM killer. oom_score_adj (int): An integer value containing the score given to the container in order to tune OOM killer preferences. @@ -1124,12 +1146,16 @@ def _create_container_args(kwargs): host_config_kwargs['binds'] = volumes network = kwargs.pop('network', None) - network_driver_opt = kwargs.pop('network_driver_opt', None) + network_config = kwargs.pop('network_config', None) if network: - network_configuration = {'driver_opt': network_driver_opt} \ - if network_driver_opt else None + network_configuration = EndpointConfig( + host_config_kwargs['version'], + **network_config + ) if network_config else None - create_kwargs['networking_config'] = {network: network_configuration} + create_kwargs['networking_config'] = NetworkingConfig( + {network: network_configuration} + ) host_config_kwargs['network_mode'] = network # All kwargs should have been consumed by this point, so raise From a662d5a3051e49ac12caef967245d9e718eb1cb3 Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sun, 7 May 2023 11:42:23 +0200 Subject: [PATCH 2/7] Fix pytests Signed-off-by: Mariano Scazzariello --- docker/models/containers.py | 4 +- tests/unit/models_containers_test.py | 57 +++++++++++++++++----------- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 5ba6297b..1d2e58c6 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -870,9 +870,9 @@ class ContainerCollection(Collection): 'together.' ) - if kwargs.get('network_driver_opt') and not kwargs.get('network'): + if kwargs.get('network_config') and not kwargs.get('network'): raise RuntimeError( - 'The options "network_driver_opt" can not be used ' + 'The option "network_config" can not be used ' 'without "network".' ) diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 0592af5e..240b592f 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -1,12 +1,12 @@ -import docker -from docker.constants import DEFAULT_DATA_CHUNK_SIZE -from docker.models.containers import Container, _create_container_args -from docker.models.images import Image +import pytest import unittest +import docker +from docker.constants import DEFAULT_DATA_CHUNK_SIZE, DEFAULT_DOCKER_API_VERSION +from docker.models.containers import Container, _create_container_args +from docker.models.images import Image from .fake_api import FAKE_CONTAINER_ID, FAKE_IMAGE_ID, FAKE_EXEC_ID from .fake_api_client import make_fake_client -import pytest class ContainerCollectionTest(unittest.TestCase): @@ -74,7 +74,7 @@ class ContainerCollectionTest(unittest.TestCase): name='somename', network_disabled=False, network='foo', - network_driver_opt={'key1': 'a'}, + network_config={'aliases': ['test'], 'driver_opt': {'key1': 'a'}}, oom_kill_disable=True, oom_score_adj=5, pid_mode='host', @@ -99,7 +99,7 @@ class ContainerCollectionTest(unittest.TestCase): user='bob', userns_mode='host', uts_mode='host', - version='1.23', + version=DEFAULT_DOCKER_API_VERSION, volume_driver='some_driver', volumes=[ '/home/user1/:/mnt/vol2', @@ -189,7 +189,9 @@ class ContainerCollectionTest(unittest.TestCase): mac_address='abc123', name='somename', network_disabled=False, - networking_config={'foo': {'driver_opt': {'key1': 'a'}}}, + networking_config={'EndpointsConfig': { + 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} + }, platform='linux', ports=[('1111', 'tcp'), ('2222', 'tcp')], stdin_open=True, @@ -346,39 +348,44 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'}, ) - def test_run_network_driver_opts_without_network(self): + def test_run_network_config_without_network(self): client = make_fake_client() with pytest.raises(RuntimeError): client.containers.run( image='alpine', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) - def test_run_network_driver_opts_with_network_mode(self): + def test_run_network_config_with_network_mode(self): client = make_fake_client() with pytest.raises(RuntimeError): client.containers.run( image='alpine', network_mode='none', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) - def test_run_network_driver_opts(self): + def test_run_network_config(self): client = make_fake_client() client.containers.run( image='alpine', network='foo', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( detach=False, image='alpine', command=None, - networking_config={'foo': {'driver_opt': {'key1': 'a'}}}, + networking_config={'EndpointsConfig': { + 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} + }, host_config={'NetworkMode': 'foo'} ) @@ -409,12 +416,13 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'} ) - def test_create_network_driver_opts_without_network(self): + def test_create_network_config_without_network(self): client = make_fake_client() client.containers.create( image='alpine', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( @@ -423,13 +431,14 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'} ) - def test_create_network_driver_opts_with_network_mode(self): + def test_create_network_config_with_network_mode(self): client = make_fake_client() client.containers.create( image='alpine', network_mode='none', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( @@ -438,19 +447,22 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'none'} ) - def test_create_network_driver_opts(self): + def test_create_network_config(self): client = make_fake_client() client.containers.create( image='alpine', network='foo', - network_driver_opt={'key1': 'a'} + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( image='alpine', command=None, - networking_config={'foo': {'driver_opt': {'key1': 'a'}}}, + networking_config={'EndpointsConfig': { + 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} + }, host_config={'NetworkMode': 'foo'} ) @@ -479,6 +491,7 @@ class ContainerCollectionTest(unittest.TestCase): def test_list_ignore_removed(self): def side_effect(*args, **kwargs): raise docker.errors.NotFound('Container not found') + client = make_fake_client({ 'inspect_container.side_effect': side_effect }) From a18f91bf08b4dca8dcf7627c8477a12ff2c1ca6a Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sun, 7 May 2023 11:49:59 +0200 Subject: [PATCH 3/7] Fix long line Signed-off-by: Mariano Scazzariello --- tests/unit/models_containers_test.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 240b592f..f721bedb 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -2,7 +2,8 @@ import pytest import unittest import docker -from docker.constants import DEFAULT_DATA_CHUNK_SIZE, DEFAULT_DOCKER_API_VERSION +from docker.constants import DEFAULT_DATA_CHUNK_SIZE, \ + DEFAULT_DOCKER_API_VERSION from docker.models.containers import Container, _create_container_args from docker.models.images import Image from .fake_api import FAKE_CONTAINER_ID, FAKE_IMAGE_ID, FAKE_EXEC_ID From 7870503c523a130a2c8731df292eb904cd1a7345 Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sun, 7 May 2023 12:15:32 +0200 Subject: [PATCH 4/7] Fix case when "network_config" is not passed Signed-off-by: Mariano Scazzariello --- docker/models/containers.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 1d2e58c6..bc2ed011 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -1148,14 +1148,14 @@ def _create_container_args(kwargs): network = kwargs.pop('network', None) network_config = kwargs.pop('network_config', None) if network: - network_configuration = EndpointConfig( + endpoint_config = EndpointConfig( host_config_kwargs['version'], **network_config ) if network_config else None create_kwargs['networking_config'] = NetworkingConfig( - {network: network_configuration} - ) + {network: endpoint_config} + ) if endpoint_config else {network: None} host_config_kwargs['network_mode'] = network # All kwargs should have been consumed by this point, so raise From e011ff5be89f84f999847d73d73ff695b9c8c4d4 Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sun, 7 May 2023 12:40:08 +0200 Subject: [PATCH 5/7] More sanity checking of EndpointConfig params Signed-off-by: Mariano Scazzariello --- docker/models/containers.py | 29 ++++++-- tests/integration/models_containers_test.py | 57 ++++++++++++++++ tests/unit/models_containers_test.py | 75 +++++++++++++++++++++ 3 files changed, 157 insertions(+), 4 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index bc2ed011..3312b0e2 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -703,6 +703,8 @@ class ContainerCollection(Collection): (IPv4/IPv6) addresses. - ``driver_opt`` (dict): A dictionary of options to provide to the network driver. Defaults to ``None``. + - ``mac_address`` (str): MAC Address to assign to the network + interface. Defaults to ``None``. Requires API >= 1.25. Used in conjuction with ``network``. Incompatible with ``network_mode``. @@ -1122,6 +1124,17 @@ RUN_HOST_CONFIG_KWARGS = [ ] +NETWORKING_CONFIG_ARGS = [ + 'aliases', + 'links', + 'ipv4_address', + 'ipv6_address', + 'link_local_ips', + 'driver_opt', + 'mac_address' +] + + def _create_container_args(kwargs): """ Convert arguments to create() to arguments to create_container(). @@ -1148,10 +1161,18 @@ def _create_container_args(kwargs): network = kwargs.pop('network', None) network_config = kwargs.pop('network_config', None) if network: - endpoint_config = EndpointConfig( - host_config_kwargs['version'], - **network_config - ) if network_config else None + endpoint_config = None + + if network_config: + clean_endpoint_args = {} + for arg_name in NETWORKING_CONFIG_ARGS: + if arg_name in network_config: + clean_endpoint_args[arg_name] = network_config[arg_name] + + if clean_endpoint_args: + endpoint_config = EndpointConfig( + host_config_kwargs['version'], **clean_endpoint_args + ) create_kwargs['networking_config'] = NetworkingConfig( {network: endpoint_config} diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index eac4c979..050efa01 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -104,6 +104,63 @@ class ContainerCollectionTest(BaseIntegrationTest): assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] + def test_run_with_network_config(self): + net_name = random_name() + client = docker.from_env(version=TEST_API_VERSION) + client.networks.create(net_name) + self.tmp_networks.append(net_name) + + test_aliases = ['hello'] + test_driver_opt = {'key1': 'a'} + + container = client.containers.run( + 'alpine', 'echo hello world', network=net_name, + network_config={'aliases': test_aliases, + 'driver_opt': test_driver_opt}, + detach=True + ) + self.tmp_containers.append(container.id) + + attrs = container.attrs + + assert 'NetworkSettings' in attrs + assert 'Networks' in attrs['NetworkSettings'] + assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] + assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == \ + test_aliases + assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ + == test_driver_opt + + def test_run_with_network_config_undeclared_params(self): + net_name = random_name() + client = docker.from_env(version=TEST_API_VERSION) + client.networks.create(net_name) + self.tmp_networks.append(net_name) + + test_aliases = ['hello'] + test_driver_opt = {'key1': 'a'} + + container = client.containers.run( + 'alpine', 'echo hello world', network=net_name, + network_config={'aliases': test_aliases, + 'driver_opt': test_driver_opt, + 'undeclared_param': 'random_value'}, + detach=True + ) + self.tmp_containers.append(container.id) + + attrs = container.attrs + + assert 'NetworkSettings' in attrs + assert 'Networks' in attrs['NetworkSettings'] + assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] + assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == \ + test_aliases + assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ + == test_driver_opt + assert 'undeclared_param' not in \ + attrs['NetworkSettings']['Networks'][net_name] + def test_run_with_none_driver(self): client = docker.from_env(version=TEST_API_VERSION) diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index f721bedb..3425ea89 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -390,6 +390,44 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'foo'} ) + def test_run_network_config_undeclared_params(self): + client = make_fake_client() + + client.containers.run( + image='alpine', + network='foo', + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}, + 'undeclared_param': 'random_value'} + ) + + client.api.create_container.assert_called_with( + detach=False, + image='alpine', + command=None, + networking_config={'EndpointsConfig': { + 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} + }, + host_config={'NetworkMode': 'foo'} + ) + + def test_run_network_config_only_undeclared_params(self): + client = make_fake_client() + + client.containers.run( + image='alpine', + network='foo', + network_config={'undeclared_param': 'random_value'} + ) + + client.api.create_container.assert_called_with( + detach=False, + image='alpine', + command=None, + networking_config={'foo': None}, + host_config={'NetworkMode': 'foo'} + ) + def test_create(self): client = make_fake_client() container = client.containers.create( @@ -467,6 +505,43 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'foo'} ) + def test_create_network_config_undeclared_params(self): + client = make_fake_client() + + client.containers.create( + image='alpine', + network='foo', + network_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}, + 'undeclared_param': 'random_value'} + ) + + client.api.create_container.assert_called_with( + image='alpine', + command=None, + networking_config={'EndpointsConfig': { + 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} + }, + host_config={'NetworkMode': 'foo'} + ) + + def test_create_network_config_only_undeclared_params(self): + client = make_fake_client() + + client.containers.create( + image='alpine', + network='foo', + network_config={'undeclared_param': 'random_value'} + ) + + client.api.create_container.assert_called_with( + image='alpine', + command=None, + networking_config={'foo': None}, + host_config={'NetworkMode': 'foo'} + ) + + def test_get(self): client = make_fake_client() container = client.containers.get(FAKE_CONTAINER_ID) From 0318ad8e7ee67c9ef0fbffaaf70029f255963012 Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Mon, 15 May 2023 14:49:55 +0200 Subject: [PATCH 6/7] Fix blank line Signed-off-by: Mariano Scazzariello --- tests/unit/models_containers_test.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 3425ea89..f6dccaab 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -541,7 +541,6 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'foo'} ) - def test_get(self): client = make_fake_client() container = client.containers.get(FAKE_CONTAINER_ID) From 7752996f783bf56084902eb931836edd0b368a90 Mon Sep 17 00:00:00 2001 From: Mariano Scazzariello Date: Sat, 30 Sep 2023 00:20:44 +0200 Subject: [PATCH 7/7] Replace `network_config` with a dict of EndpointConfig - Renamed parameter from `network_config` to `networking_config` to be more semantically correct with the rest of the API. --- docker/models/containers.py | 74 +++--------- tests/integration/models_containers_test.py | 65 +++++++--- tests/unit/models_containers_test.py | 127 ++++++++++++++------ 3 files changed, 159 insertions(+), 107 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 3312b0e2..87e64ed4 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -2,16 +2,16 @@ import copy import ntpath from collections import namedtuple +from .images import Image +from .resource import Collection, Model from ..api import APIClient from ..constants import DEFAULT_DATA_CHUNK_SIZE from ..errors import ( ContainerError, DockerException, ImageNotFound, NotFound, create_unexpected_kwargs_error ) -from ..types import EndpointConfig, HostConfig, NetworkingConfig +from ..types import HostConfig, NetworkingConfig from ..utils import version_gte -from .images import Image -from .resource import Collection, Model class Container(Model): @@ -21,6 +21,7 @@ class Container(Model): query the Docker daemon for the current properties, causing :py:attr:`attrs` to be refreshed. """ + @property def name(self): """ @@ -680,33 +681,13 @@ class ContainerCollection(Collection): This mode is incompatible with ``ports``. Incompatible with ``network``. - network_config (dict): A dictionary containing options that are - passed to the network driver during the connection. + networking_config (Dict[str, EndpointConfig]): + Dictionary of EndpointConfig objects for each container network. + The key is the name of the network. Defaults to ``None``. - The dictionary contains the following keys: - - - ``aliases`` (:py:class:`list`): A list of aliases for - the network endpoint. - Names in that list can be used within the network to - reach this container. Defaults to ``None``. - - ``links`` (:py:class:`list`): A list of links for - the network endpoint endpoint. - Containers declared in this list will be linked to this - container. Defaults to ``None``. - - ``ipv4_address`` (str): The IP address to assign to - this container on the network, using the IPv4 protocol. - Defaults to ``None``. - - ``ipv6_address`` (str): The IP address to assign to - this container on the network, using the IPv6 protocol. - Defaults to ``None``. - - ``link_local_ips`` (:py:class:`list`): A list of link-local - (IPv4/IPv6) addresses. - - ``driver_opt`` (dict): A dictionary of options to provide to - the network driver. Defaults to ``None``. - - ``mac_address`` (str): MAC Address to assign to the network - interface. Defaults to ``None``. Requires API >= 1.25. Used in conjuction with ``network``. + Incompatible with ``network_mode``. oom_kill_disable (bool): Whether to disable OOM killer. oom_score_adj (int): An integer value containing the score given @@ -872,9 +853,9 @@ class ContainerCollection(Collection): 'together.' ) - if kwargs.get('network_config') and not kwargs.get('network'): + if kwargs.get('networking_config') and not kwargs.get('network'): raise RuntimeError( - 'The option "network_config" can not be used ' + 'The option "networking_config" can not be used ' 'without "network".' ) @@ -1030,6 +1011,7 @@ class ContainerCollection(Collection): def prune(self, filters=None): return self.client.api.prune_containers(filters=filters) + prune.__doc__ = APIClient.prune_containers.__doc__ @@ -1124,17 +1106,6 @@ RUN_HOST_CONFIG_KWARGS = [ ] -NETWORKING_CONFIG_ARGS = [ - 'aliases', - 'links', - 'ipv4_address', - 'ipv6_address', - 'link_local_ips', - 'driver_opt', - 'mac_address' -] - - def _create_container_args(kwargs): """ Convert arguments to create() to arguments to create_container(). @@ -1159,24 +1130,17 @@ def _create_container_args(kwargs): host_config_kwargs['binds'] = volumes network = kwargs.pop('network', None) - network_config = kwargs.pop('network_config', None) + networking_config = kwargs.pop('networking_config', None) if network: - endpoint_config = None - - if network_config: - clean_endpoint_args = {} - for arg_name in NETWORKING_CONFIG_ARGS: - if arg_name in network_config: - clean_endpoint_args[arg_name] = network_config[arg_name] - - if clean_endpoint_args: - endpoint_config = EndpointConfig( - host_config_kwargs['version'], **clean_endpoint_args - ) + if networking_config: + # Sanity check: check if the network is defined in the + # networking config dict, otherwise switch to None + if network not in networking_config: + networking_config = None create_kwargs['networking_config'] = NetworkingConfig( - {network: endpoint_config} - ) if endpoint_config else {network: None} + networking_config + ) if networking_config else {network: None} host_config_kwargs['network_mode'] = network # All kwargs should have been consumed by this point, so raise diff --git a/tests/integration/models_containers_test.py b/tests/integration/models_containers_test.py index 050efa01..330c658e 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -5,10 +5,10 @@ import threading import pytest import docker -from ..helpers import random_name -from ..helpers import requires_api_version from .base import BaseIntegrationTest from .base import TEST_API_VERSION +from ..helpers import random_name +from ..helpers import requires_api_version class ContainerCollectionTest(BaseIntegrationTest): @@ -104,7 +104,7 @@ class ContainerCollectionTest(BaseIntegrationTest): assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - def test_run_with_network_config(self): + def test_run_with_networking_config(self): net_name = random_name() client = docker.from_env(version=TEST_API_VERSION) client.networks.create(net_name) @@ -113,10 +113,16 @@ class ContainerCollectionTest(BaseIntegrationTest): test_aliases = ['hello'] test_driver_opt = {'key1': 'a'} + networking_config = { + net_name: client.api.create_endpoint_config( + aliases=test_aliases, + driver_opt=test_driver_opt + ) + } + container = client.containers.run( 'alpine', 'echo hello world', network=net_name, - network_config={'aliases': test_aliases, - 'driver_opt': test_driver_opt}, + networking_config=networking_config, detach=True ) self.tmp_containers.append(container.id) @@ -131,7 +137,7 @@ class ContainerCollectionTest(BaseIntegrationTest): assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ == test_driver_opt - def test_run_with_network_config_undeclared_params(self): + def test_run_with_networking_config_with_undeclared_network(self): net_name = random_name() client = docker.from_env(version=TEST_API_VERSION) client.networks.create(net_name) @@ -140,11 +146,41 @@ class ContainerCollectionTest(BaseIntegrationTest): test_aliases = ['hello'] test_driver_opt = {'key1': 'a'} + networking_config = { + net_name: client.api.create_endpoint_config( + aliases=test_aliases, + driver_opt=test_driver_opt + ), + 'bar': client.api.create_endpoint_config( + aliases=['test'], + driver_opt={'key2': 'b'} + ), + } + + with pytest.raises(docker.errors.APIError) as e: + container = client.containers.run( + 'alpine', 'echo hello world', network=net_name, + networking_config=networking_config, + detach=True + ) + self.tmp_containers.append(container.id) + + def test_run_with_networking_config_only_undeclared_network(self): + net_name = random_name() + client = docker.from_env(version=TEST_API_VERSION) + client.networks.create(net_name) + self.tmp_networks.append(net_name) + + networking_config = { + 'bar': client.api.create_endpoint_config( + aliases=['hello'], + driver_opt={'key1': 'a'} + ), + } + container = client.containers.run( 'alpine', 'echo hello world', network=net_name, - network_config={'aliases': test_aliases, - 'driver_opt': test_driver_opt, - 'undeclared_param': 'random_value'}, + networking_config=networking_config, detach=True ) self.tmp_containers.append(container.id) @@ -154,12 +190,9 @@ class ContainerCollectionTest(BaseIntegrationTest): assert 'NetworkSettings' in attrs assert 'Networks' in attrs['NetworkSettings'] assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] - assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == \ - test_aliases - assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \ - == test_driver_opt - assert 'undeclared_param' not in \ - attrs['NetworkSettings']['Networks'][net_name] + assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] is None + assert (attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] + is None) def test_run_with_none_driver(self): client = docker.from_env(version=TEST_API_VERSION) @@ -244,7 +277,7 @@ class ContainerCollectionTest(BaseIntegrationTest): container = client.containers.run("alpine", "sleep 300", detach=True) self.tmp_containers.append(container.id) assert client.containers.get(container.id).attrs[ - 'Config']['Image'] == "alpine" + 'Config']['Image'] == "alpine" def test_list(self): client = docker.from_env(version=TEST_API_VERSION) diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index f6dccaab..bd3092b6 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -1,11 +1,13 @@ -import pytest import unittest +import pytest + import docker from docker.constants import DEFAULT_DATA_CHUNK_SIZE, \ DEFAULT_DOCKER_API_VERSION from docker.models.containers import Container, _create_container_args from docker.models.images import Image +from docker.types import EndpointConfig from .fake_api import FAKE_CONTAINER_ID, FAKE_IMAGE_ID, FAKE_EXEC_ID from .fake_api_client import make_fake_client @@ -32,6 +34,13 @@ class ContainerCollectionTest(unittest.TestCase): ) def test_create_container_args(self): + networking_config = { + 'foo': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + create_kwargs = _create_container_args(dict( image='alpine', command='echo hello world', @@ -75,7 +84,7 @@ class ContainerCollectionTest(unittest.TestCase): name='somename', network_disabled=False, network='foo', - network_config={'aliases': ['test'], 'driver_opt': {'key1': 'a'}}, + networking_config=networking_config, oom_kill_disable=True, oom_score_adj=5, pid_mode='host', @@ -349,35 +358,41 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'}, ) - def test_run_network_config_without_network(self): + def test_run_networking_config_without_network(self): client = make_fake_client() with pytest.raises(RuntimeError): client.containers.run( image='alpine', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) - def test_run_network_config_with_network_mode(self): + def test_run_networking_config_with_network_mode(self): client = make_fake_client() with pytest.raises(RuntimeError): client.containers.run( image='alpine', network_mode='none', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) - def test_run_network_config(self): + def test_run_networking_config(self): client = make_fake_client() + networking_config = { + 'foo': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.run( image='alpine', network='foo', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config=networking_config ) client.api.create_container.assert_called_with( @@ -390,15 +405,24 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'foo'} ) - def test_run_network_config_undeclared_params(self): + def test_run_networking_config_with_undeclared_network(self): client = make_fake_client() + networking_config = { + 'foo': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test_foo'], + driver_opt={'key2': 'b'} + ), + 'bar': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.run( image='alpine', network='foo', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}, - 'undeclared_param': 'random_value'} + networking_config=networking_config ) client.api.create_container.assert_called_with( @@ -406,18 +430,26 @@ class ContainerCollectionTest(unittest.TestCase): image='alpine', command=None, networking_config={'EndpointsConfig': { - 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} - }, + 'foo': {'Aliases': ['test_foo'], 'DriverOpts': {'key2': 'b'}}, + 'bar': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}, + }}, host_config={'NetworkMode': 'foo'} ) - def test_run_network_config_only_undeclared_params(self): + def test_run_networking_config_only_undeclared_network(self): client = make_fake_client() + networking_config = { + 'bar': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.run( image='alpine', network='foo', - network_config={'undeclared_param': 'random_value'} + networking_config=networking_config ) client.api.create_container.assert_called_with( @@ -455,13 +487,13 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'} ) - def test_create_network_config_without_network(self): + def test_create_networking_config_without_network(self): client = make_fake_client() client.containers.create( image='alpine', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( @@ -470,14 +502,14 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'default'} ) - def test_create_network_config_with_network_mode(self): + def test_create_networking_config_with_network_mode(self): client = make_fake_client() client.containers.create( image='alpine', network_mode='none', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config={'aliases': ['test'], + 'driver_opt': {'key1': 'a'}} ) client.api.create_container.assert_called_with( @@ -486,14 +518,20 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'none'} ) - def test_create_network_config(self): + def test_create_networking_config(self): client = make_fake_client() + networking_config = { + 'foo': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.create( image='alpine', network='foo', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}} + networking_config=networking_config ) client.api.create_container.assert_called_with( @@ -505,33 +543,50 @@ class ContainerCollectionTest(unittest.TestCase): host_config={'NetworkMode': 'foo'} ) - def test_create_network_config_undeclared_params(self): + def test_create_networking_config_with_undeclared_network(self): client = make_fake_client() + networking_config = { + 'foo': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test_foo'], + driver_opt={'key2': 'b'} + ), + 'bar': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.create( image='alpine', network='foo', - network_config={'aliases': ['test'], - 'driver_opt': {'key1': 'a'}, - 'undeclared_param': 'random_value'} + networking_config=networking_config ) client.api.create_container.assert_called_with( image='alpine', command=None, networking_config={'EndpointsConfig': { - 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}} - }, + 'foo': {'Aliases': ['test_foo'], 'DriverOpts': {'key2': 'b'}}, + 'bar': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}, + }}, host_config={'NetworkMode': 'foo'} ) - def test_create_network_config_only_undeclared_params(self): + def test_create_networking_config_only_undeclared_network(self): client = make_fake_client() + networking_config = { + 'bar': EndpointConfig( + DEFAULT_DOCKER_API_VERSION, aliases=['test'], + driver_opt={'key1': 'a'} + ) + } + client.containers.create( image='alpine', network='foo', - network_config={'undeclared_param': 'random_value'} + networking_config=networking_config ) client.api.create_container.assert_called_with(