Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support all Network.connect parameters in client.containers.run and client.containers.create #3121

Merged
merged 8 commits into from
Nov 20, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 59 additions & 12 deletions docker/models/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -680,10 +680,34 @@ 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is an EndpointConfig type:

class EndpointConfig(dict):
def __init__(self, version, aliases=None, links=None, ipv4_address=None,
ipv6_address=None, link_local_ips=None, driver_opt=None,
mac_address=None):

Newer versions of the Moby API (will) allow defining multiple networks on create, so let's keep this the same here, e.g. Dict[str, EndpointConfig], so that we don't have to update this again / make another breaking change.

(I'm ok with this breaking change to replace network_driver_opt since it was just introduced in v6, so it is hopefully not that entrenched in codebases yet.)

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``.
- ``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
to the container in order to tune OOM killer preferences.
Expand Down Expand Up @@ -848,9 +872,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".'
)

Expand Down Expand Up @@ -1100,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().
Expand All @@ -1124,12 +1159,24 @@ 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

create_kwargs['networking_config'] = {network: network_configuration}
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}
) if endpoint_config else {network: None}
host_config_kwargs['network_mode'] = network

# All kwargs should have been consumed by this point, so raise
Expand Down
57 changes: 57 additions & 0 deletions tests/integration/models_containers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
Loading