Skip to content

Commit 30089ec

Browse files
authored
Merge pull request #2637 from docker/4.3.0-release
4.3.0 release
2 parents 7f11cd4 + 7db995b commit 30089ec

22 files changed

+277
-36
lines changed

Jenkinsfile

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def buildImages = { ->
3131
}
3232

3333
def getDockerVersions = { ->
34-
def dockerVersions = ["19.03.5"]
34+
def dockerVersions = ["19.03.12"]
3535
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2") {
3636
def result = sh(script: """docker run --rm \\
3737
--entrypoint=python \\
@@ -66,7 +66,7 @@ def runTests = { Map settings ->
6666
throw new Exception("Need test image object, e.g.: `runTests(testImage: img)`")
6767
}
6868
if (!dockerVersion) {
69-
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '1.12.3')`")
69+
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '19.03.12')`")
7070
}
7171
if (!pythonVersion) {
7272
throw new Exception("Need Python version being tested, e.g.: `runTests(pythonVersion: 'py2.7')`")

Makefile

+2-2
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ integration-test: build
4141
integration-test-py3: build-py3
4242
docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 py.test -v tests/integration/${file}
4343

44-
TEST_API_VERSION ?= 1.35
45-
TEST_ENGINE_VERSION ?= 19.03.5
44+
TEST_API_VERSION ?= 1.39
45+
TEST_ENGINE_VERSION ?= 19.03.12
4646

4747
.PHONY: setup-network
4848
setup-network:

docker/api/container.py

+16-3
Original file line numberDiff line numberDiff line change
@@ -480,6 +480,9 @@ def create_host_config(self, *args, **kwargs):
480480
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
481481
to have read-write access to the host's ``/dev/sda`` via a
482482
node named ``/dev/xvda`` inside the container.
483+
device_requests (:py:class:`list`): Expose host resources such as
484+
GPUs to the container, as a list of
485+
:py:class:`docker.types.DeviceRequest` instances.
483486
dns (:py:class:`list`): Set custom DNS servers.
484487
dns_opt (:py:class:`list`): Additional options to be added to the
485488
container's ``resolv.conf`` file
@@ -636,6 +639,8 @@ def create_endpoint_config(self, *args, **kwargs):
636639
network, using the IPv6 protocol. Defaults to ``None``.
637640
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
638641
addresses.
642+
driver_opt (dict): A dictionary of options to provide to the
643+
network driver. Defaults to ``None``.
639644
640645
Returns:
641646
(dict) An endpoint config.
@@ -694,7 +699,8 @@ def export(self, container, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
694699
return self._stream_raw_result(res, chunk_size, False)
695700

696701
@utils.check_resource('container')
697-
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
702+
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
703+
encode_stream=False):
698704
"""
699705
Retrieve a file or folder from a container in the form of a tar
700706
archive.
@@ -705,6 +711,8 @@ def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
705711
chunk_size (int): The number of bytes returned by each iteration
706712
of the generator. If ``None``, data will be streamed as it is
707713
received. Default: 2 MB
714+
encode_stream (bool): Determines if data should be encoded
715+
(gzip-compressed) during transmission. Default: False
708716
709717
Returns:
710718
(tuple): First element is a raw tar data stream. Second element is
@@ -729,8 +737,13 @@ def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
729737
params = {
730738
'path': path
731739
}
740+
headers = {
741+
"Accept-Encoding": "gzip, deflate"
742+
} if encode_stream else {
743+
"Accept-Encoding": "identity"
744+
}
732745
url = self._url('/containers/{0}/archive', container)
733-
res = self._get(url, params=params, stream=True)
746+
res = self._get(url, params=params, stream=True, headers=headers)
734747
self._raise_for_status(res)
735748
encoded_stat = res.headers.get('x-docker-container-path-stat')
736749
return (
@@ -1120,7 +1133,7 @@ def stats(self, container, decode=None, stream=True):
11201133
else:
11211134
if decode:
11221135
raise errors.InvalidArgument(
1123-
"decode is only available in conjuction with stream=True"
1136+
"decode is only available in conjunction with stream=True"
11241137
)
11251138
return self._result(self._get(url, params={'stream': False}),
11261139
json=True)

docker/api/network.py

+3-2
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,7 @@ def inspect_network(self, net_id, verbose=None, scope=None):
216216
def connect_container_to_network(self, container, net_id,
217217
ipv4_address=None, ipv6_address=None,
218218
aliases=None, links=None,
219-
link_local_ips=None):
219+
link_local_ips=None, driver_opt=None):
220220
"""
221221
Connect a container to a network.
222222
@@ -240,7 +240,8 @@ def connect_container_to_network(self, container, net_id,
240240
"Container": container,
241241
"EndpointConfig": self.create_endpoint_config(
242242
aliases=aliases, links=links, ipv4_address=ipv4_address,
243-
ipv6_address=ipv6_address, link_local_ips=link_local_ips
243+
ipv6_address=ipv6_address, link_local_ips=link_local_ips,
244+
driver_opt=driver_opt
244245
),
245246
}
246247

docker/constants.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import sys
22
from .version import version
33

4-
DEFAULT_DOCKER_API_VERSION = '1.35'
4+
DEFAULT_DOCKER_API_VERSION = '1.39'
55
MINIMUM_DOCKER_API_VERSION = '1.21'
66
DEFAULT_TIMEOUT_SECONDS = 60
77
STREAM_HEADER_SIZE_BYTES = 8

docker/models/containers.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,8 @@ def export(self, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
225225
"""
226226
return self.client.api.export(self.id, chunk_size)
227227

228-
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
228+
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
229+
encode_stream=False):
229230
"""
230231
Retrieve a file or folder from the container in the form of a tar
231232
archive.
@@ -235,6 +236,8 @@ def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
235236
chunk_size (int): The number of bytes returned by each iteration
236237
of the generator. If ``None``, data will be streamed as it is
237238
received. Default: 2 MB
239+
encode_stream (bool): Determines if data should be encoded
240+
(gzip-compressed) during transmission. Default: False
238241
239242
Returns:
240243
(tuple): First element is a raw tar data stream. Second element is
@@ -255,7 +258,8 @@ def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
255258
... f.write(chunk)
256259
>>> f.close()
257260
"""
258-
return self.client.api.get_archive(self.id, path, chunk_size)
261+
return self.client.api.get_archive(self.id, path,
262+
chunk_size, encode_stream)
259263

260264
def kill(self, signal=None):
261265
"""
@@ -579,6 +583,9 @@ def run(self, image, command=None, stdout=True, stderr=False,
579583
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
580584
to have read-write access to the host's ``/dev/sda`` via a
581585
node named ``/dev/xvda`` inside the container.
586+
device_requests (:py:class:`list`): Expose host resources such as
587+
GPUs to the container, as a list of
588+
:py:class:`docker.types.DeviceRequest` instances.
582589
dns (:py:class:`list`): Set custom DNS servers.
583590
dns_opt (:py:class:`list`): Additional options to be added to the
584591
container's ``resolv.conf`` file.
@@ -998,6 +1005,7 @@ def prune(self, filters=None):
9981005
'device_write_bps',
9991006
'device_write_iops',
10001007
'devices',
1008+
'device_requests',
10011009
'dns_opt',
10021010
'dns_search',
10031011
'dns',

docker/models/networks.py

+2
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ def connect(self, container, *args, **kwargs):
4646
network, using the IPv6 protocol. Defaults to ``None``.
4747
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
4848
addresses.
49+
driver_opt (dict): A dictionary of options to provide to the
50+
network driver. Defaults to ``None``.
4951
5052
Raises:
5153
:py:class:`docker.errors.APIError`

docker/tls.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def __init__(self, client_cert=None, ca_cert=None, verify=None,
3232
# https://docs.docker.com/engine/articles/https/
3333
# This diverges from the Docker CLI in that users can specify 'tls'
3434
# here, but also disable any public/default CA pool verification by
35-
# leaving tls_verify=False
35+
# leaving verify=False
3636

3737
self.assert_hostname = assert_hostname
3838
self.assert_fingerprint = assert_fingerprint
@@ -62,7 +62,7 @@ def __init__(self, client_cert=None, ca_cert=None, verify=None,
6262
# https://github.com/docker/docker-py/issues/963
6363
self.ssl_version = ssl.PROTOCOL_TLSv1
6464

65-
# "tls" and "tls_verify" must have both or neither cert/key files In
65+
# "client_cert" must have both or neither cert/key files. In
6666
# either case, Alert the user when both are expected, but any are
6767
# missing.
6868

@@ -71,15 +71,15 @@ def __init__(self, client_cert=None, ca_cert=None, verify=None,
7171
tls_cert, tls_key = client_cert
7272
except ValueError:
7373
raise errors.TLSParameterError(
74-
'client_config must be a tuple of'
74+
'client_cert must be a tuple of'
7575
' (client certificate, key file)'
7676
)
7777

7878
if not (tls_cert and tls_key) or (not os.path.isfile(tls_cert) or
7979
not os.path.isfile(tls_key)):
8080
raise errors.TLSParameterError(
8181
'Path to a certificate and key files must be provided'
82-
' through the client_config param'
82+
' through the client_cert param'
8383
)
8484
self.cert = (tls_cert, tls_key)
8585

@@ -88,7 +88,7 @@ def __init__(self, client_cert=None, ca_cert=None, verify=None,
8888
self.ca_cert = ca_cert
8989
if self.verify and self.ca_cert and not os.path.isfile(self.ca_cert):
9090
raise errors.TLSParameterError(
91-
'Invalid CA certificate provided for `tls_ca_cert`.'
91+
'Invalid CA certificate provided for `ca_cert`.'
9292
)
9393

9494
def configure_client(self, client):

docker/types/__init__.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# flake8: noqa
2-
from .containers import ContainerConfig, HostConfig, LogConfig, Ulimit
2+
from .containers import (
3+
ContainerConfig, HostConfig, LogConfig, Ulimit, DeviceRequest
4+
)
35
from .daemon import CancellableStream
46
from .healthcheck import Healthcheck
57
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig

docker/types/containers.py

+112-1
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,104 @@ def hard(self, value):
154154
self['Hard'] = value
155155

156156

157+
class DeviceRequest(DictType):
158+
"""
159+
Create a device request to be used with
160+
:py:meth:`~docker.api.container.ContainerApiMixin.create_host_config`.
161+
162+
Args:
163+
164+
driver (str): Which driver to use for this device. Optional.
165+
count (int): Number or devices to request. Optional.
166+
Set to -1 to request all available devices.
167+
device_ids (list): List of strings for device IDs. Optional.
168+
Set either ``count`` or ``device_ids``.
169+
capabilities (list): List of lists of strings to request
170+
capabilities. Optional. The global list acts like an OR,
171+
and the sub-lists are AND. The driver will try to satisfy
172+
one of the sub-lists.
173+
Available capabilities for the ``nvidia`` driver can be found
174+
`here <https://github.com/NVIDIA/nvidia-container-runtime>`_.
175+
options (dict): Driver-specific options. Optional.
176+
"""
177+
178+
def __init__(self, **kwargs):
179+
driver = kwargs.get('driver', kwargs.get('Driver'))
180+
count = kwargs.get('count', kwargs.get('Count'))
181+
device_ids = kwargs.get('device_ids', kwargs.get('DeviceIDs'))
182+
capabilities = kwargs.get('capabilities', kwargs.get('Capabilities'))
183+
options = kwargs.get('options', kwargs.get('Options'))
184+
185+
if driver is None:
186+
driver = ''
187+
elif not isinstance(driver, six.string_types):
188+
raise ValueError('DeviceRequest.driver must be a string')
189+
if count is None:
190+
count = 0
191+
elif not isinstance(count, int):
192+
raise ValueError('DeviceRequest.count must be an integer')
193+
if device_ids is None:
194+
device_ids = []
195+
elif not isinstance(device_ids, list):
196+
raise ValueError('DeviceRequest.device_ids must be a list')
197+
if capabilities is None:
198+
capabilities = []
199+
elif not isinstance(capabilities, list):
200+
raise ValueError('DeviceRequest.capabilities must be a list')
201+
if options is None:
202+
options = {}
203+
elif not isinstance(options, dict):
204+
raise ValueError('DeviceRequest.options must be a dict')
205+
206+
super(DeviceRequest, self).__init__({
207+
'Driver': driver,
208+
'Count': count,
209+
'DeviceIDs': device_ids,
210+
'Capabilities': capabilities,
211+
'Options': options
212+
})
213+
214+
@property
215+
def driver(self):
216+
return self['Driver']
217+
218+
@driver.setter
219+
def driver(self, value):
220+
self['Driver'] = value
221+
222+
@property
223+
def count(self):
224+
return self['Count']
225+
226+
@count.setter
227+
def count(self, value):
228+
self['Count'] = value
229+
230+
@property
231+
def device_ids(self):
232+
return self['DeviceIDs']
233+
234+
@device_ids.setter
235+
def device_ids(self, value):
236+
self['DeviceIDs'] = value
237+
238+
@property
239+
def capabilities(self):
240+
return self['Capabilities']
241+
242+
@capabilities.setter
243+
def capabilities(self, value):
244+
self['Capabilities'] = value
245+
246+
@property
247+
def options(self):
248+
return self['Options']
249+
250+
@options.setter
251+
def options(self, value):
252+
self['Options'] = value
253+
254+
157255
class HostConfig(dict):
158256
def __init__(self, version, binds=None, port_bindings=None,
159257
lxc_conf=None, publish_all_ports=False, links=None,
@@ -176,7 +274,7 @@ def __init__(self, version, binds=None, port_bindings=None,
176274
volume_driver=None, cpu_count=None, cpu_percent=None,
177275
nano_cpus=None, cpuset_mems=None, runtime=None, mounts=None,
178276
cpu_rt_period=None, cpu_rt_runtime=None,
179-
device_cgroup_rules=None):
277+
device_cgroup_rules=None, device_requests=None):
180278

181279
if mem_limit is not None:
182280
self['Memory'] = parse_bytes(mem_limit)
@@ -536,6 +634,19 @@ def __init__(self, version, binds=None, port_bindings=None,
536634
)
537635
self['DeviceCgroupRules'] = device_cgroup_rules
538636

637+
if device_requests is not None:
638+
if version_lt(version, '1.40'):
639+
raise host_config_version_error('device_requests', '1.40')
640+
if not isinstance(device_requests, list):
641+
raise host_config_type_error(
642+
'device_requests', device_requests, 'list'
643+
)
644+
self['DeviceRequests'] = []
645+
for req in device_requests:
646+
if not isinstance(req, DeviceRequest):
647+
req = DeviceRequest(**req)
648+
self['DeviceRequests'].append(req)
649+
539650

540651
def host_config_type_error(param, param_value, expected):
541652
error_msg = 'Invalid type for {0} param: expected {1} but found {2}'

docker/types/networks.py

+10-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
class EndpointConfig(dict):
66
def __init__(self, version, aliases=None, links=None, ipv4_address=None,
7-
ipv6_address=None, link_local_ips=None):
7+
ipv6_address=None, link_local_ips=None, driver_opt=None):
88
if version_lt(version, '1.22'):
99
raise errors.InvalidVersion(
1010
'Endpoint config is not supported for API version < 1.22'
@@ -33,6 +33,15 @@ def __init__(self, version, aliases=None, links=None, ipv4_address=None,
3333
if ipam_config:
3434
self['IPAMConfig'] = ipam_config
3535

36+
if driver_opt:
37+
if version_lt(version, '1.32'):
38+
raise errors.InvalidVersion(
39+
'DriverOpts is not supported for API version < 1.32'
40+
)
41+
if not isinstance(driver_opt, dict):
42+
raise TypeError('driver_opt must be a dictionary')
43+
self['DriverOpts'] = driver_opt
44+
3645

3746
class NetworkingConfig(dict):
3847
def __init__(self, endpoints_config=None):

docker/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
version = "4.2.2"
1+
version = "4.3.0"
22
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])

0 commit comments

Comments
 (0)