Merge branch 'main' into patch-1

This commit is contained in:
Milas Bowman 2023-11-20 16:15:52 -05:00 committed by GitHub
commit 911f866f72
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 397 additions and 136 deletions

View File

@ -5,7 +5,6 @@ from functools import partial
import requests
import requests.exceptions
import websocket
from .. import auth
from ..constants import (DEFAULT_NUM_POOLS, DEFAULT_NUM_POOLS_SSH,
@ -309,7 +308,16 @@ class APIClient(
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)

View File

@ -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 HostConfig
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):
"""
@ -688,10 +689,14 @@ class ContainerCollection(Collection):
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.
@ -856,9 +861,9 @@ class ContainerCollection(Collection):
'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".'
)
@ -1014,6 +1019,7 @@ class ContainerCollection(Collection):
def prune(self, filters=None):
return self.client.api.prune_containers(filters=filters)
prune.__doc__ = APIClient.prune_containers.__doc__
@ -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
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'] = {network: network_configuration}
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

View File

@ -13,7 +13,6 @@ requirements = [
'packaging >= 14.0',
'requests >= 2.26.0',
'urllib3 >= 1.26.0',
'websocket-client >= 0.32.0',
]
extras_require = {
@ -27,6 +26,9 @@ extras_require = {
# 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:

View File

@ -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,6 +104,96 @@ class ContainerCollectionTest(BaseIntegrationTest):
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)
@ -187,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)

View File

@ -1,12 +1,15 @@
import docker
from docker.constants import DEFAULT_DATA_CHUNK_SIZE
from docker.models.containers import Container, _create_container_args
from docker.models.images import Image
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
import pytest
class ContainerCollectionTest(unittest.TestCase):
@ -31,77 +34,80 @@ 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({
"image": 'alpine',
"command": 'echo hello world',
"blkio_weight_device": [{'Path': 'foo', 'Weight': 3}],
"blkio_weight": 2,
"cap_add": ['foo'],
"cap_drop": ['bar'],
"cgroup_parent": 'foobar',
"cgroupns": 'host',
"cpu_period": 1,
"cpu_quota": 2,
"cpu_shares": 5,
"cpuset_cpus": '0-3',
"detach": False,
"device_read_bps": [{'Path': 'foo', 'Rate': 3}],
"device_read_iops": [{'Path': 'foo', 'Rate': 3}],
"device_write_bps": [{'Path': 'foo', 'Rate': 3}],
"device_write_iops": [{'Path': 'foo', 'Rate': 3}],
"devices": ['/dev/sda:/dev/xvda:rwm'],
"dns": ['8.8.8.8'],
"domainname": 'example.com',
"dns_opt": ['foo'],
"dns_search": ['example.com'],
"entrypoint": '/bin/sh',
"environment": {'FOO': 'BAR'},
"extra_hosts": {'foo': '1.2.3.4'},
"group_add": ['blah'],
"ipc_mode": 'foo',
"kernel_memory": 123,
"labels": {'key': 'value'},
"links": {'foo': 'bar'},
"log_config": {'Type': 'json-file', 'Config': {}},
"lxc_conf": {'foo': 'bar'},
"healthcheck": {'test': 'true'},
"hostname": 'somehost',
"mac_address": 'abc123',
"mem_limit": 123,
"mem_reservation": 123,
"mem_swappiness": 2,
"memswap_limit": 456,
"name": 'somename',
"network_disabled": False,
"network": 'foo',
"network_driver_opt": {'key1': 'a'},
"oom_kill_disable": True,
"oom_score_adj": 5,
"pid_mode": 'host',
"pids_limit": 500,
"platform": 'linux',
"ports": {
1111: 4567,
2222: None
},
"privileged": True,
"publish_all_ports": True,
"read_only": True,
"restart_policy": {'Name': 'always'},
"security_opt": ['blah'],
"shm_size": 123,
"stdin_open": True,
"stop_signal": 9,
"sysctls": {'foo': 'bar'},
"tmpfs": {'/blah': ''},
"tty": True,
"ulimits": [{"Name": "nofile", "Soft": 1024, "Hard": 2048}],
"user": 'bob',
"userns_mode": 'host',
"uts_mode": 'host',
"version": '1.23',
"volume_driver": 'some_driver',
"volumes": [
'image': 'alpine',
'command': 'echo hello world',
'blkio_weight_device': [{'Path': 'foo', 'Weight': 3}],
'blkio_weight': 2,
'cap_add': ['foo'],
'cap_drop': ['bar'],
'cgroup_parent': 'foobar',
'cgroupns': 'host',
'cpu_period': 1,
'cpu_quota': 2,
'cpu_shares': 5,
'cpuset_cpus': '0-3',
'detach': False,
'device_read_bps': [{'Path': 'foo', 'Rate': 3}],
'device_read_iops': [{'Path': 'foo', 'Rate': 3}],
'device_write_bps': [{'Path': 'foo', 'Rate': 3}],
'device_write_iops': [{'Path': 'foo', 'Rate': 3}],
'devices': ['/dev/sda:/dev/xvda:rwm'],
'dns': ['8.8.8.8'],
'domainname': 'example.com',
'dns_opt': ['foo'],
'dns_search': ['example.com'],
'entrypoint': '/bin/sh',
'environment': {'FOO': 'BAR'},
'extra_hosts': {'foo': '1.2.3.4'},
'group_add': ['blah'],
'ipc_mode': 'foo',
'kernel_memory': 123,
'labels': {'key': 'value'},
'links': {'foo': 'bar'},
'log_config': {'Type': 'json-file', 'Config': {}},
'lxc_conf': {'foo': 'bar'},
'healthcheck': {'test': 'true'},
'hostname': 'somehost',
'mac_address': 'abc123',
'mem_limit': 123,
'mem_reservation': 123,
'mem_swappiness': 2,
'memswap_limit': 456,
'name': 'somename',
'network_disabled': False,
'network': 'foo',
'networking_config': networking_config,
'oom_kill_disable': True,
'oom_score_adj': 5,
'pid_mode': 'host',
'pids_limit': 500,
'platform': 'linux',
'ports': {1111: 4567, 2222: None},
'privileged': True,
'publish_all_ports': True,
'read_only': True,
'restart_policy': {'Name': 'always'},
'security_opt': ['blah'],
'shm_size': 123,
'stdin_open': True,
'stop_signal': 9,
'sysctls': {'foo': 'bar'},
'tmpfs': {'/blah': ''},
'tty': True,
'ulimits': [{"Name": "nofile", "Soft": 1024, "Hard": 2048}],
'user': 'bob',
'userns_mode': 'host',
'uts_mode': 'host',
'version': DEFAULT_DOCKER_API_VERSION,
'volume_driver': 'some_driver', 'volumes': [
'/home/user1/:/mnt/vol2',
'/var/www:/mnt/vol1:ro',
'volumename:/mnt/vol3r',
@ -109,18 +115,18 @@ class ContainerCollectionTest(unittest.TestCase):
'/anothervolumewithnohostpath:ro',
'C:\\windows\\path:D:\\hello\\world:rw'
],
"volumes_from": ['container'],
"working_dir": '/code'
'volumes_from': ['container'],
'working_dir': '/code',
})
expected = {
"image": 'alpine',
"command": 'echo hello world',
"domainname": 'example.com',
"detach": False,
"entrypoint": '/bin/sh',
"environment": {'FOO': 'BAR'},
"host_config": {
'image': 'alpine',
'command': 'echo hello world',
'domainname': 'example.com',
'detach': False,
'entrypoint': '/bin/sh',
'environment': {'FOO': 'BAR'},
'host_config': {
'Binds': [
'/home/user1/:/mnt/vol2',
'/var/www:/mnt/vol1:ro',
@ -143,9 +149,13 @@ class ContainerCollectionTest(unittest.TestCase):
'CpuQuota': 2,
'CpuShares': 5,
'CpusetCpus': '0-3',
'Devices': [{'PathOnHost': '/dev/sda',
'CgroupPermissions': 'rwm',
'PathInContainer': '/dev/xvda'}],
'Devices': [
{
'PathOnHost': '/dev/sda',
'CgroupPermissions': 'rwm',
'PathInContainer': '/dev/xvda',
},
],
'Dns': ['8.8.8.8'],
'DnsOptions': ['foo'],
'DnsSearch': ['example.com'],
@ -177,26 +187,35 @@ class ContainerCollectionTest(unittest.TestCase):
'ShmSize': 123,
'Sysctls': {'foo': 'bar'},
'Tmpfs': {'/blah': ''},
'Ulimits': [{"Name": "nofile", "Soft": 1024, "Hard": 2048}],
'Ulimits': [
{"Name": "nofile", "Soft": 1024, "Hard": 2048},
],
'UsernsMode': 'host',
'UTSMode': 'host',
'VolumeDriver': 'some_driver',
'VolumesFrom': ['container'],
},
"healthcheck": {'test': 'true'},
"hostname": 'somehost',
"labels": {'key': 'value'},
"mac_address": 'abc123',
"name": 'somename',
"network_disabled": False,
"networking_config": {'foo': {'driver_opt': {'key1': 'a'}}},
"platform": 'linux',
"ports": [('1111', 'tcp'), ('2222', 'tcp')],
"stdin_open": True,
"stop_signal": 9,
"tty": True,
"user": 'bob',
"volumes": [
'healthcheck': {'test': 'true'},
'hostname': 'somehost',
'labels': {'key': 'value'},
'mac_address': 'abc123',
'name': 'somename',
'network_disabled': False,
'networking_config': {
'EndpointsConfig': {
'foo': {
'Aliases': ['test'],
'DriverOpts': {'key1': 'a'},
},
}
},
'platform': 'linux',
'ports': [('1111', 'tcp'), ('2222', 'tcp')],
'stdin_open': True,
'stop_signal': 9,
'tty': True,
'user': 'bob',
'volumes': [
'/mnt/vol2',
'/mnt/vol1',
'/mnt/vol3r',
@ -204,7 +223,7 @@ class ContainerCollectionTest(unittest.TestCase):
'/anothervolumewithnohostpath',
'D:\\hello\\world'
],
"working_dir": '/code'
'working_dir': '/code',
}
assert create_kwargs == expected
@ -346,39 +365,105 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'},
)
def test_run_network_driver_opts_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_driver_opt={'key1': 'a'}
networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
)
def test_run_network_driver_opts_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_driver_opt={'key1': 'a'}
networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
)
def test_run_network_driver_opts(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_driver_opt={'key1': 'a'}
networking_config=networking_config
)
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'}
)
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',
networking_config=networking_config
)
client.api.create_container.assert_called_with(
detach=False,
image='alpine',
command=None,
networking_config={'EndpointsConfig': {
'foo': {'Aliases': ['test_foo'], 'DriverOpts': {'key2': 'b'}},
'bar': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}},
}},
host_config={'NetworkMode': 'foo'}
)
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',
networking_config=networking_config
)
client.api.create_container.assert_called_with(
detach=False,
image='alpine',
command=None,
networking_config={'foo': None},
host_config={'NetworkMode': 'foo'}
)
@ -409,12 +494,13 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'}
)
def test_create_network_driver_opts_without_network(self):
def test_create_networking_config_without_network(self):
client = make_fake_client()
client.containers.create(
image='alpine',
network_driver_opt={'key1': 'a'}
networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
)
client.api.create_container.assert_called_with(
@ -423,13 +509,14 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'}
)
def test_create_network_driver_opts_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_driver_opt={'key1': 'a'}
networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
)
client.api.create_container.assert_called_with(
@ -438,19 +525,81 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'none'}
)
def test_create_network_driver_opts(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_driver_opt={'key1': 'a'}
networking_config=networking_config
)
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'}
)
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',
networking_config=networking_config
)
client.api.create_container.assert_called_with(
image='alpine',
command=None,
networking_config={'EndpointsConfig': {
'foo': {'Aliases': ['test_foo'], 'DriverOpts': {'key2': 'b'}},
'bar': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}},
}},
host_config={'NetworkMode': 'foo'}
)
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',
networking_config=networking_config
)
client.api.create_container.assert_called_with(
image='alpine',
command=None,
networking_config={'foo': None},
host_config={'NetworkMode': 'foo'}
)
@ -479,6 +628,7 @@ class ContainerCollectionTest(unittest.TestCase):
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
})