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 2eeefda1e..5ba6297bd 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -8,7 +8,7 @@ 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 @@ def run(self, image, command=None, stdout=True, stderr=False, 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 | 55 +++++++++++++++++----------- 2 files changed, 36 insertions(+), 23 deletions(-) diff --git a/docker/models/containers.py b/docker/models/containers.py index 5ba6297bd..1d2e58c64 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -870,9 +870,9 @@ def run(self, image, command=None, stdout=True, stderr=False, '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 0592af5e0..240b592fb 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -1,12 +1,12 @@ +import pytest +import unittest + import docker -from docker.constants import DEFAULT_DATA_CHUNK_SIZE +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 -import unittest - 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 @@ def test_create_container_args(self): 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 @@ def test_create_container_args(self): 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 @@ def test_create_container_args(self): 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 @@ def test_run_platform(self): 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 @@ def test_create_with_image_object(self): 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 @@ def test_create_network_driver_opts_without_network(self): 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 @@ def test_create_network_driver_opts_with_network_mode(self): 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 @@ def test_list(self): 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 240b592fb..f721bedbe 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -2,7 +2,8 @@ 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 1d2e58c64..bc2ed011f 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 bc2ed011f..3312b0e2d 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -703,6 +703,8 @@ def run(self, image, command=None, stdout=True, stderr=False, (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 @@ def prune(self, filters=None): ] +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 eac4c9790..050efa01c 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -104,6 +104,63 @@ def test_run_with_network(self): 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 f721bedbe..3425ea897 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -390,6 +390,44 @@ def test_run_network_config(self): 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 @@ def test_create_network_config(self): 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 3425ea897..f6dccaaba 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -541,7 +541,6 @@ def test_create_network_config_only_undeclared_params(self): 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 3312b0e2d..87e64ed48 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -2,16 +2,16 @@ 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 @@ def run(self, image, command=None, stdout=True, stderr=False, 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 @@ def run(self, image, command=None, stdout=True, stderr=False, '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 @@ def list(self, all=False, before=None, filters=None, limit=-1, since=None, def prune(self, filters=None): return self.client.api.prune_containers(filters=filters) + prune.__doc__ = APIClient.prune_containers.__doc__ @@ -1124,17 +1106,6 @@ def prune(self, filters=None): ] -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 050efa01c..330c658e1 100644 --- a/tests/integration/models_containers_test.py +++ b/tests/integration/models_containers_test.py @@ -5,10 +5,10 @@ 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 @@ def test_run_with_network(self): 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 @@ def test_run_with_network_config(self): 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 @@ def test_run_with_network_config(self): 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 @@ def test_run_with_network_config_undeclared_params(self): 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 @@ def test_run_with_network_config_undeclared_params(self): 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 @@ def test_get(self): 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 f6dccaaba..bd3092b67 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 @@ def test_run(self): ) 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 @@ def test_create_container_args(self): 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 @@ def test_run_platform(self): 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 @@ def test_run_network_config(self): 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 @@ def test_run_network_config_undeclared_params(self): 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 @@ def test_create_with_image_object(self): 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 @@ def test_create_network_config_without_network(self): 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 @@ def test_create_network_config_with_network_mode(self): 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 @@ def test_create_network_config(self): 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(