Merge branch 'main' into py3.12-compat

This commit is contained in:
Milas Bowman 2023-11-20 15:45:59 -05:00 committed by GitHub
commit 1b735ef957
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 374 additions and 130 deletions

View File

@ -2,16 +2,16 @@ import copy
import ntpath import ntpath
from collections import namedtuple from collections import namedtuple
from .images import Image
from .resource import Collection, Model
from ..api import APIClient from ..api import APIClient
from ..constants import DEFAULT_DATA_CHUNK_SIZE from ..constants import DEFAULT_DATA_CHUNK_SIZE
from ..errors import ( from ..errors import (
ContainerError, DockerException, ImageNotFound, ContainerError, DockerException, ImageNotFound,
NotFound, create_unexpected_kwargs_error NotFound, create_unexpected_kwargs_error
) )
from ..types import HostConfig from ..types import HostConfig, NetworkingConfig
from ..utils import version_gte from ..utils import version_gte
from .images import Image
from .resource import Collection, Model
class Container(Model): class Container(Model):
@ -21,6 +21,7 @@ class Container(Model):
query the Docker daemon for the current properties, causing query the Docker daemon for the current properties, causing
:py:attr:`attrs` to be refreshed. :py:attr:`attrs` to be refreshed.
""" """
@property @property
def name(self): def name(self):
""" """
@ -681,10 +682,14 @@ class ContainerCollection(Collection):
This mode is incompatible with ``ports``. This mode is incompatible with ``ports``.
Incompatible with ``network``. Incompatible with ``network``.
network_driver_opt (dict): A dictionary of options to provide networking_config (Dict[str, EndpointConfig]):
to the network driver. Defaults to ``None``. Used in Dictionary of EndpointConfig objects for each container network.
conjuction with ``network``. Incompatible The key is the name of the network.
with ``network_mode``. Defaults to ``None``.
Used in conjuction with ``network``.
Incompatible with ``network_mode``.
oom_kill_disable (bool): Whether to disable OOM killer. oom_kill_disable (bool): Whether to disable OOM killer.
oom_score_adj (int): An integer value containing the score given oom_score_adj (int): An integer value containing the score given
to the container in order to tune OOM killer preferences. to the container in order to tune OOM killer preferences.
@ -849,9 +854,9 @@ class ContainerCollection(Collection):
'together.' 'together.'
) )
if kwargs.get('network_driver_opt') and not kwargs.get('network'): if kwargs.get('networking_config') and not kwargs.get('network'):
raise RuntimeError( raise RuntimeError(
'The options "network_driver_opt" can not be used ' 'The option "networking_config" can not be used '
'without "network".' 'without "network".'
) )
@ -1007,6 +1012,7 @@ class ContainerCollection(Collection):
def prune(self, filters=None): def prune(self, filters=None):
return self.client.api.prune_containers(filters=filters) return self.client.api.prune_containers(filters=filters)
prune.__doc__ = APIClient.prune_containers.__doc__ prune.__doc__ = APIClient.prune_containers.__doc__
@ -1125,12 +1131,17 @@ def _create_container_args(kwargs):
host_config_kwargs['binds'] = volumes host_config_kwargs['binds'] = volumes
network = kwargs.pop('network', None) network = kwargs.pop('network', None)
network_driver_opt = kwargs.pop('network_driver_opt', None) networking_config = kwargs.pop('networking_config', None)
if network: if network:
network_configuration = {'driver_opt': network_driver_opt} \ if networking_config:
if network_driver_opt else None # 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 host_config_kwargs['network_mode'] = network
# All kwargs should have been consumed by this point, so raise # All kwargs should have been consumed by this point, so raise

View File

@ -5,10 +5,10 @@ import threading
import pytest import pytest
import docker import docker
from ..helpers import random_name
from ..helpers import requires_api_version
from .base import BaseIntegrationTest from .base import BaseIntegrationTest
from .base import TEST_API_VERSION from .base import TEST_API_VERSION
from ..helpers import random_name
from ..helpers import requires_api_version
class ContainerCollectionTest(BaseIntegrationTest): class ContainerCollectionTest(BaseIntegrationTest):
@ -104,6 +104,96 @@ class ContainerCollectionTest(BaseIntegrationTest):
assert 'Networks' in attrs['NetworkSettings'] assert 'Networks' in attrs['NetworkSettings']
assert list(attrs['NetworkSettings']['Networks'].keys()) == [net_name] 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) 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,
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): def test_run_with_none_driver(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
@ -187,7 +277,7 @@ class ContainerCollectionTest(BaseIntegrationTest):
container = client.containers.run("alpine", "sleep 300", detach=True) container = client.containers.run("alpine", "sleep 300", detach=True)
self.tmp_containers.append(container.id) self.tmp_containers.append(container.id)
assert client.containers.get(container.id).attrs[ assert client.containers.get(container.id).attrs[
'Config']['Image'] == "alpine" 'Config']['Image'] == "alpine"
def test_list(self): def test_list(self):
client = docker.from_env(version=TEST_API_VERSION) 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 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 import FAKE_CONTAINER_ID, FAKE_IMAGE_ID, FAKE_EXEC_ID
from .fake_api_client import make_fake_client from .fake_api_client import make_fake_client
import pytest
class ContainerCollectionTest(unittest.TestCase): class ContainerCollectionTest(unittest.TestCase):
@ -31,77 +34,84 @@ class ContainerCollectionTest(unittest.TestCase):
) )
def test_create_container_args(self): def test_create_container_args(self):
create_kwargs = _create_container_args({ networking_config = {
"image": 'alpine', 'foo': EndpointConfig(
"command": 'echo hello world', DEFAULT_DOCKER_API_VERSION, aliases=['test'],
"blkio_weight_device": [{'Path': 'foo', 'Weight': 3}], driver_opt={'key1': 'a'}
"blkio_weight": 2, )
"cap_add": ['foo'], }
"cap_drop": ['bar'],
"cgroup_parent": 'foobar', create_kwargs = _create_container_args(dict(
"cgroupns": 'host', image='alpine',
"cpu_period": 1, command='echo hello world',
"cpu_quota": 2, blkio_weight_device=[{'Path': 'foo', 'Weight': 3}],
"cpu_shares": 5, blkio_weight=2,
"cpuset_cpus": '0-3', cap_add=['foo'],
"detach": False, cap_drop=['bar'],
"device_read_bps": [{'Path': 'foo', 'Rate': 3}], cgroup_parent='foobar',
"device_read_iops": [{'Path': 'foo', 'Rate': 3}], cgroupns='host',
"device_write_bps": [{'Path': 'foo', 'Rate': 3}], cpu_period=1,
"device_write_iops": [{'Path': 'foo', 'Rate': 3}], cpu_quota=2,
"devices": ['/dev/sda:/dev/xvda:rwm'], cpu_shares=5,
"dns": ['8.8.8.8'], cpuset_cpus='0-3',
"domainname": 'example.com', detach=False,
"dns_opt": ['foo'], device_read_bps=[{'Path': 'foo', 'Rate': 3}],
"dns_search": ['example.com'], device_read_iops=[{'Path': 'foo', 'Rate': 3}],
"entrypoint": '/bin/sh', device_write_bps=[{'Path': 'foo', 'Rate': 3}],
"environment": {'FOO': 'BAR'}, device_write_iops=[{'Path': 'foo', 'Rate': 3}],
"extra_hosts": {'foo': '1.2.3.4'}, devices=['/dev/sda:/dev/xvda:rwm'],
"group_add": ['blah'], dns=['8.8.8.8'],
"ipc_mode": 'foo', domainname='example.com',
"kernel_memory": 123, dns_opt=['foo'],
"labels": {'key': 'value'}, dns_search=['example.com'],
"links": {'foo': 'bar'}, entrypoint='/bin/sh',
"log_config": {'Type': 'json-file', 'Config': {}}, environment={'FOO': 'BAR'},
"lxc_conf": {'foo': 'bar'}, extra_hosts={'foo': '1.2.3.4'},
"healthcheck": {'test': 'true'}, group_add=['blah'],
"hostname": 'somehost', ipc_mode='foo',
"mac_address": 'abc123', kernel_memory=123,
"mem_limit": 123, labels={'key': 'value'},
"mem_reservation": 123, links={'foo': 'bar'},
"mem_swappiness": 2, log_config={'Type': 'json-file', 'Config': {}},
"memswap_limit": 456, lxc_conf={'foo': 'bar'},
"name": 'somename', healthcheck={'test': 'true'},
"network_disabled": False, hostname='somehost',
"network": 'foo', mac_address='abc123',
"network_driver_opt": {'key1': 'a'}, mem_limit=123,
"oom_kill_disable": True, mem_reservation=123,
"oom_score_adj": 5, mem_swappiness=2,
"pid_mode": 'host', memswap_limit=456,
"pids_limit": 500, name='somename',
"platform": 'linux', network_disabled=False,
"ports": { 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, 1111: 4567,
2222: None 2222: None
}, },
"privileged": True, privileged=True,
"publish_all_ports": True, publish_all_ports=True,
"read_only": True, read_only=True,
"restart_policy": {'Name': 'always'}, restart_policy={'Name': 'always'},
"security_opt": ['blah'], security_opt=['blah'],
"shm_size": 123, shm_size=123,
"stdin_open": True, stdin_open=True,
"stop_signal": 9, stop_signal=9,
"sysctls": {'foo': 'bar'}, sysctls={'foo': 'bar'},
"tmpfs": {'/blah': ''}, tmpfs={'/blah': ''},
"tty": True, tty=True,
"ulimits": [{"Name": "nofile", "Soft": 1024, "Hard": 2048}], ulimits=[{"Name": "nofile", "Soft": 1024, "Hard": 2048}],
"user": 'bob', user='bob',
"userns_mode": 'host', userns_mode='host',
"uts_mode": 'host', uts_mode='host',
"version": '1.23', version=DEFAULT_DOCKER_API_VERSION,
"volume_driver": 'some_driver', volume_driver='some_driver',
"volumes": [ volumes=[
'/home/user1/:/mnt/vol2', '/home/user1/:/mnt/vol2',
'/var/www:/mnt/vol1:ro', '/var/www:/mnt/vol1:ro',
'volumename:/mnt/vol3r', 'volumename:/mnt/vol3r',
@ -109,18 +119,18 @@ class ContainerCollectionTest(unittest.TestCase):
'/anothervolumewithnohostpath:ro', '/anothervolumewithnohostpath:ro',
'C:\\windows\\path:D:\\hello\\world:rw' 'C:\\windows\\path:D:\\hello\\world:rw'
], ],
"volumes_from": ['container'], volumes_from=['container'],
"working_dir": '/code' working_dir='/code'
}) ))
expected = { expected = dict(
"image": 'alpine', image='alpine',
"command": 'echo hello world', command='echo hello world',
"domainname": 'example.com', domainname='example.com',
"detach": False, detach=False,
"entrypoint": '/bin/sh', entrypoint='/bin/sh',
"environment": {'FOO': 'BAR'}, environment={'FOO': 'BAR'},
"host_config": { host_config={
'Binds': [ 'Binds': [
'/home/user1/:/mnt/vol2', '/home/user1/:/mnt/vol2',
'/var/www:/mnt/vol1:ro', '/var/www:/mnt/vol1:ro',
@ -183,20 +193,22 @@ class ContainerCollectionTest(unittest.TestCase):
'VolumeDriver': 'some_driver', 'VolumeDriver': 'some_driver',
'VolumesFrom': ['container'], 'VolumesFrom': ['container'],
}, },
"healthcheck": {'test': 'true'}, healthcheck={'test': 'true'},
"hostname": 'somehost', hostname='somehost',
"labels": {'key': 'value'}, labels={'key': 'value'},
"mac_address": 'abc123', mac_address='abc123',
"name": 'somename', name='somename',
"network_disabled": False, network_disabled=False,
"networking_config": {'foo': {'driver_opt': {'key1': 'a'}}}, networking_config={'EndpointsConfig': {
"platform": 'linux', 'foo': {'Aliases': ['test'], 'DriverOpts': {'key1': 'a'}}}
"ports": [('1111', 'tcp'), ('2222', 'tcp')], },
"stdin_open": True, platform='linux',
"stop_signal": 9, ports=[('1111', 'tcp'), ('2222', 'tcp')],
"tty": True, stdin_open=True,
"user": 'bob', stop_signal=9,
"volumes": [ tty=True,
user='bob',
volumes=[
'/mnt/vol2', '/mnt/vol2',
'/mnt/vol1', '/mnt/vol1',
'/mnt/vol3r', '/mnt/vol3r',
@ -204,8 +216,8 @@ class ContainerCollectionTest(unittest.TestCase):
'/anothervolumewithnohostpath', '/anothervolumewithnohostpath',
'D:\\hello\\world' 'D:\\hello\\world'
], ],
"working_dir": '/code' working_dir='/code'
} )
assert create_kwargs == expected assert create_kwargs == expected
@ -346,39 +358,105 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'}, 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() client = make_fake_client()
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
client.containers.run( client.containers.run(
image='alpine', 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() client = make_fake_client()
with pytest.raises(RuntimeError): with pytest.raises(RuntimeError):
client.containers.run( client.containers.run(
image='alpine', image='alpine',
network_mode='none', 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() client = make_fake_client()
networking_config = {
'foo': EndpointConfig(
DEFAULT_DOCKER_API_VERSION, aliases=['test'],
driver_opt={'key1': 'a'}
)
}
client.containers.run( client.containers.run(
image='alpine', image='alpine',
network='foo', network='foo',
network_driver_opt={'key1': 'a'} networking_config=networking_config
) )
client.api.create_container.assert_called_with( client.api.create_container.assert_called_with(
detach=False, detach=False,
image='alpine', image='alpine',
command=None, 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'} host_config={'NetworkMode': 'foo'}
) )
@ -409,12 +487,13 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'} 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 = make_fake_client()
client.containers.create( client.containers.create(
image='alpine', image='alpine',
network_driver_opt={'key1': 'a'} networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
) )
client.api.create_container.assert_called_with( client.api.create_container.assert_called_with(
@ -423,13 +502,14 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'default'} 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 = make_fake_client()
client.containers.create( client.containers.create(
image='alpine', image='alpine',
network_mode='none', network_mode='none',
network_driver_opt={'key1': 'a'} networking_config={'aliases': ['test'],
'driver_opt': {'key1': 'a'}}
) )
client.api.create_container.assert_called_with( client.api.create_container.assert_called_with(
@ -438,19 +518,81 @@ class ContainerCollectionTest(unittest.TestCase):
host_config={'NetworkMode': 'none'} host_config={'NetworkMode': 'none'}
) )
def test_create_network_driver_opts(self): def test_create_networking_config(self):
client = make_fake_client() client = make_fake_client()
networking_config = {
'foo': EndpointConfig(
DEFAULT_DOCKER_API_VERSION, aliases=['test'],
driver_opt={'key1': 'a'}
)
}
client.containers.create( client.containers.create(
image='alpine', image='alpine',
network='foo', network='foo',
network_driver_opt={'key1': 'a'} networking_config=networking_config
) )
client.api.create_container.assert_called_with( client.api.create_container.assert_called_with(
image='alpine', image='alpine',
command=None, 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'} host_config={'NetworkMode': 'foo'}
) )
@ -479,6 +621,7 @@ class ContainerCollectionTest(unittest.TestCase):
def test_list_ignore_removed(self): def test_list_ignore_removed(self):
def side_effect(*args, **kwargs): def side_effect(*args, **kwargs):
raise docker.errors.NotFound('Container not found') raise docker.errors.NotFound('Container not found')
client = make_fake_client({ client = make_fake_client({
'inspect_container.side_effect': side_effect 'inspect_container.side_effect': side_effect
}) })