Skip to content

Commit 911f866

Browse files
authored
Merge branch 'main' into patch-1
2 parents 5abae2d + 26e0725 commit 911f866

File tree

5 files changed

+397
-136
lines changed

5 files changed

+397
-136
lines changed

docker/api/client.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55

66
import requests
77
import requests.exceptions
8-
import websocket
98

109
from .. import auth
1110
from ..constants import (DEFAULT_NUM_POOLS, DEFAULT_NUM_POOLS_SSH,
@@ -309,7 +308,16 @@ def _attach_websocket(self, container, params=None):
309308
return self._create_websocket_connection(full_url)
310309

311310
def _create_websocket_connection(self, url):
312-
return websocket.create_connection(url)
311+
try:
312+
import websocket
313+
return websocket.create_connection(url)
314+
except ImportError as ie:
315+
raise DockerException(
316+
'The `websocket-client` library is required '
317+
'for using websocket connections. '
318+
'You can install the `docker` library '
319+
'with the [websocket] extra to install it.'
320+
) from ie
313321

314322
def _get_raw_response_socket(self, response):
315323
self._raise_for_status(response)

docker/models/containers.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@
22
import ntpath
33
from collections import namedtuple
44

5+
from .images import Image
6+
from .resource import Collection, Model
57
from ..api import APIClient
68
from ..constants import DEFAULT_DATA_CHUNK_SIZE
79
from ..errors import (
810
ContainerError, DockerException, ImageNotFound,
911
NotFound, create_unexpected_kwargs_error
1012
)
11-
from ..types import HostConfig
13+
from ..types import HostConfig, NetworkingConfig
1214
from ..utils import version_gte
13-
from .images import Image
14-
from .resource import Collection, Model
1515

1616

1717
class Container(Model):
@@ -21,6 +21,7 @@ class Container(Model):
2121
query the Docker daemon for the current properties, causing
2222
:py:attr:`attrs` to be refreshed.
2323
"""
24+
2425
@property
2526
def name(self):
2627
"""
@@ -688,10 +689,14 @@ def run(self, image, command=None, stdout=True, stderr=False,
688689
This mode is incompatible with ``ports``.
689690
690691
Incompatible with ``network``.
691-
network_driver_opt (dict): A dictionary of options to provide
692-
to the network driver. Defaults to ``None``. Used in
693-
conjuction with ``network``. Incompatible
694-
with ``network_mode``.
692+
networking_config (Dict[str, EndpointConfig]):
693+
Dictionary of EndpointConfig objects for each container network.
694+
The key is the name of the network.
695+
Defaults to ``None``.
696+
697+
Used in conjuction with ``network``.
698+
699+
Incompatible with ``network_mode``.
695700
oom_kill_disable (bool): Whether to disable OOM killer.
696701
oom_score_adj (int): An integer value containing the score given
697702
to the container in order to tune OOM killer preferences.
@@ -856,9 +861,9 @@ def run(self, image, command=None, stdout=True, stderr=False,
856861
'together.'
857862
)
858863

859-
if kwargs.get('network_driver_opt') and not kwargs.get('network'):
864+
if kwargs.get('networking_config') and not kwargs.get('network'):
860865
raise RuntimeError(
861-
'The options "network_driver_opt" can not be used '
866+
'The option "networking_config" can not be used '
862867
'without "network".'
863868
)
864869

@@ -1014,6 +1019,7 @@ def list(self, all=False, before=None, filters=None, limit=-1, since=None,
10141019

10151020
def prune(self, filters=None):
10161021
return self.client.api.prune_containers(filters=filters)
1022+
10171023
prune.__doc__ = APIClient.prune_containers.__doc__
10181024

10191025

@@ -1132,12 +1138,17 @@ def _create_container_args(kwargs):
11321138
host_config_kwargs['binds'] = volumes
11331139

11341140
network = kwargs.pop('network', None)
1135-
network_driver_opt = kwargs.pop('network_driver_opt', None)
1141+
networking_config = kwargs.pop('networking_config', None)
11361142
if network:
1137-
network_configuration = {'driver_opt': network_driver_opt} \
1138-
if network_driver_opt else None
1139-
1140-
create_kwargs['networking_config'] = {network: network_configuration}
1143+
if networking_config:
1144+
# Sanity check: check if the network is defined in the
1145+
# networking config dict, otherwise switch to None
1146+
if network not in networking_config:
1147+
networking_config = None
1148+
1149+
create_kwargs['networking_config'] = NetworkingConfig(
1150+
networking_config
1151+
) if networking_config else {network: None}
11411152
host_config_kwargs['network_mode'] = network
11421153

11431154
# All kwargs should have been consumed by this point, so raise

setup.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
'packaging >= 14.0',
1414
'requests >= 2.26.0',
1515
'urllib3 >= 1.26.0',
16-
'websocket-client >= 0.32.0',
1716
]
1817

1918
extras_require = {
@@ -27,6 +26,9 @@
2726

2827
# Only required when connecting using the ssh:// protocol
2928
'ssh': ['paramiko>=2.4.3'],
29+
30+
# Only required when using websockets
31+
'websockets': ['websocket-client >= 1.3.0'],
3032
}
3133

3234
with open('./test-requirements.txt') as test_reqs_txt:

tests/integration/models_containers_test.py

Lines changed: 93 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@
55
import pytest
66

77
import docker
8-
from ..helpers import random_name
9-
from ..helpers import requires_api_version
108
from .base import BaseIntegrationTest
119
from .base import TEST_API_VERSION
10+
from ..helpers import random_name
11+
from ..helpers import requires_api_version
1212

1313

1414
class ContainerCollectionTest(BaseIntegrationTest):
@@ -104,6 +104,96 @@ def test_run_with_network(self):
104104
assert 'Networks' in attrs['NetworkSettings']
105105
assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name]
106106

107+
def test_run_with_networking_config(self):
108+
net_name = random_name()
109+
client = docker.from_env(version=TEST_API_VERSION)
110+
client.networks.create(net_name)
111+
self.tmp_networks.append(net_name)
112+
113+
test_aliases = ['hello']
114+
test_driver_opt = {'key1': 'a'}
115+
116+
networking_config = {
117+
net_name: client.api.create_endpoint_config(
118+
aliases=test_aliases,
119+
driver_opt=test_driver_opt
120+
)
121+
}
122+
123+
container = client.containers.run(
124+
'alpine', 'echo hello world', network=net_name,
125+
networking_config=networking_config,
126+
detach=True
127+
)
128+
self.tmp_containers.append(container.id)
129+
130+
attrs = container.attrs
131+
132+
assert 'NetworkSettings' in attrs
133+
assert 'Networks' in attrs['NetworkSettings']
134+
assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name]
135+
assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] == \
136+
test_aliases
137+
assert attrs['NetworkSettings']['Networks'][net_name]['DriverOpts'] \
138+
== test_driver_opt
139+
140+
def test_run_with_networking_config_with_undeclared_network(self):
141+
net_name = random_name()
142+
client = docker.from_env(version=TEST_API_VERSION)
143+
client.networks.create(net_name)
144+
self.tmp_networks.append(net_name)
145+
146+
test_aliases = ['hello']
147+
test_driver_opt = {'key1': 'a'}
148+
149+
networking_config = {
150+
net_name: client.api.create_endpoint_config(
151+
aliases=test_aliases,
152+
driver_opt=test_driver_opt
153+
),
154+
'bar': client.api.create_endpoint_config(
155+
aliases=['test'],
156+
driver_opt={'key2': 'b'}
157+
),
158+
}
159+
160+
with pytest.raises(docker.errors.APIError):
161+
container = client.containers.run(
162+
'alpine', 'echo hello world', network=net_name,
163+
networking_config=networking_config,
164+
detach=True
165+
)
166+
self.tmp_containers.append(container.id)
167+
168+
def test_run_with_networking_config_only_undeclared_network(self):
169+
net_name = random_name()
170+
client = docker.from_env(version=TEST_API_VERSION)
171+
client.networks.create(net_name)
172+
self.tmp_networks.append(net_name)
173+
174+
networking_config = {
175+
'bar': client.api.create_endpoint_config(
176+
aliases=['hello'],
177+
driver_opt={'key1': 'a'}
178+
),
179+
}
180+
181+
container = client.containers.run(
182+
'alpine', 'echo hello world', network=net_name,
183+
networking_config=networking_config,
184+
detach=True
185+
)
186+
self.tmp_containers.append(container.id)
187+
188+
attrs = container.attrs
189+
190+
assert 'NetworkSettings' in attrs
191+
assert 'Networks' in attrs['NetworkSettings']
192+
assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name]
193+
assert attrs['NetworkSettings']['Networks'][net_name]['Aliases'] is None
194+
assert (attrs['NetworkSettings']['Networks'][net_name]['DriverOpts']
195+
is None)
196+
107197
def test_run_with_none_driver(self):
108198
client = docker.from_env(version=TEST_API_VERSION)
109199

@@ -187,7 +277,7 @@ def test_get(self):
187277
container = client.containers.run("alpine", "sleep 300", detach=True)
188278
self.tmp_containers.append(container.id)
189279
assert client.containers.get(container.id).attrs[
190-
'Config']['Image'] == "alpine"
280+
'Config']['Image'] == "alpine"
191281

192282
def test_list(self):
193283
client = docker.from_env(version=TEST_API_VERSION)

0 commit comments

Comments
 (0)