Skip to content

Commit

Permalink
Merge branch 'main' into patch-1
Browse files Browse the repository at this point in the history
  • Loading branch information
milas authored Nov 20, 2023
2 parents 5abae2d + 26e0725 commit 911f866
Show file tree
Hide file tree
Showing 5 changed files with 397 additions and 136 deletions.
12 changes: 10 additions & 2 deletions docker/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import requests
import requests.exceptions
import websocket

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

def _create_websocket_connection(self, url):
return websocket.create_connection(url)
try:
import websocket
return websocket.create_connection(url)
except ImportError as ie:
raise DockerException(
'The `websocket-client` library is required '
'for using websocket connections. '
'You can install the `docker` library '
'with the [websocket] extra to install it.'
) from ie

def _get_raw_response_socket(self, response):
self._raise_for_status(response)
Expand Down
39 changes: 25 additions & 14 deletions docker/models/containers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 HostConfig
from ..types import HostConfig, NetworkingConfig
from ..utils import version_gte
from .images import Image
from .resource import Collection, Model


class Container(Model):
Expand All @@ -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):
"""
Expand Down Expand Up @@ -688,10 +689,14 @@ 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``.
networking_config (Dict[str, EndpointConfig]):
Dictionary of EndpointConfig objects for each container network.
The key is the name of the network.
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.
Expand Down Expand Up @@ -856,9 +861,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('networking_config') and not kwargs.get('network'):
raise RuntimeError(
'The options "network_driver_opt" can not be used '
'The option "networking_config" can not be used '
'without "network".'
)

Expand Down Expand Up @@ -1014,6 +1019,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__


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

network = kwargs.pop('network', None)
network_driver_opt = kwargs.pop('network_driver_opt', None)
networking_config = kwargs.pop('networking_config', None)
if network:
network_configuration = {'driver_opt': network_driver_opt} \
if network_driver_opt else None

create_kwargs['networking_config'] = {network: network_configuration}
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(
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
Expand Down
4 changes: 3 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
'packaging >= 14.0',
'requests >= 2.26.0',
'urllib3 >= 1.26.0',
'websocket-client >= 0.32.0',
]

extras_require = {
Expand All @@ -27,6 +26,9 @@

# Only required when connecting using the ssh:// protocol
'ssh': ['paramiko>=2.4.3'],

# Only required when using websockets
'websockets': ['websocket-client >= 1.3.0'],
}

with open('./test-requirements.txt') as test_reqs_txt:
Expand Down
96 changes: 93 additions & 3 deletions tests/integration/models_containers_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -104,6 +104,96 @@ def test_run_with_network(self):
assert 'Networks' in attrs['NetworkSettings']
assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name]

def test_run_with_networking_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'}

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,
networking_config=networking_config,
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_networking_config_with_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)

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):
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,
networking_config=networking_config,
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'] 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)

Expand Down Expand Up @@ -187,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)
Expand Down
Loading

0 comments on commit 911f866

Please sign in to comment.