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(