Compare commits

...

26 Commits
main ... 4.3.0

Author SHA1 Message Date
Anca Iordache 30089ec681
Merge pull request #2637 from docker/4.3.0-release
4.3.0 release
2020-08-10 18:35:10 +02:00
aiordache 7db995b1ce Fix changelog merge
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 18:21:57 +02:00
aiordache 7e967c9e80 Fix merge
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 18:15:18 +02:00
aiordache 2d219ff739 Prepare release 4.3.0
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 18:09:07 +02:00
Sebastiaan van Stijn e6a64e3671 Update default API version to v1.39 (#2512)
* Update default API version to v1.39

When running the docker-py integration tests in the Moby repository, some
tests were skipped because the API version used was too low:

    SKIPPED [1] tests/integration/api_service_test.py:882: API version is too low (< 1.38)
    SKIPPED [1] tests/integration/api_swarm_test.py:59: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:38: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:45: API version is too low (< 1.39)
    SKIPPED [1] tests/integration/api_swarm_test.py:52: API version is too low (< 1.39)

While it's possible to override the API version to use for testing
using the `DOCKER_TEST_API_VERSION` environment variable, we may want
to set the default to a version that supports all features that were
added.

This patch updates the default API version to v1.39, which is the minimum
version required for those features, and corresponds with Docker 18.09.

Note that the API version of the current (19.03) Docker release is v1.40,
but using that version as default would exclude users that did not update
their Docker version yet (and would not be needed yet for the features provided).

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>

* Makefile: set DOCKER_TEST_API_VERSION to v1.39

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-10 18:09:07 +02:00
Ville Skyttä 88234f3916 Fix parameter names in TLSConfig error messages and comments
Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2020-08-10 18:09:07 +02:00
Niklas Saari 281bc31e21 Disable compression by default when using get_archive method
Signed-off-by: Niklas Saari <niklas.saari@tutanota.com>
2020-08-10 18:09:07 +02:00
Sebastiaan van Stijn 351b131fe9 Update test engine version to 19.03.12
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-10 18:09:07 +02:00
Ville Skyttä 687f23afe4 Spelling fixes (#2571)
Signed-off-by: Ville Skyttä <ville.skytta@iki.fi>
2020-08-10 18:09:07 +02:00
Lucidiot 75ed0ecf5a Add device requests (#2471)
* Add DeviceRequest type

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Add device_requests kwarg in host config

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Add unit test for device requests

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Fix unit test

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Use parentheses for multiline import

Signed-off-by: Erwan Rouchet <rouchet@teklia.com>

* Create 1.40 client for device-requests test

Signed-off-by: Laurie O <laurie_opperman@hotmail.com>

Co-authored-by: Laurie O <laurie_opperman@hotmail.com>
Co-authored-by: Bastien Abadie <abadie@teklia.com>
2020-08-10 18:09:07 +02:00
Sebastiaan van Stijn 00213a274b Fix CreateContainerTest.test_invalid_log_driver_raises_exception
This test was updated in 7d92fbdee1, but
omitted the "error" prefix in the message, causing the test to fail;

    _________ CreateContainerTest.test_invalid_log_driver_raises_exception _________
    tests/integration/api_container_test.py:293: in test_invalid_log_driver_raises_exception
        assert excinfo.value.explanation in expected_msgs
    E   AssertionError: assert 'error looking up logging plugin asdf: plugin "asdf" not found' in ["logger: no log driver named 'asdf' is registered", 'looking up logging plugin asdf: plugin "asdf" not found']
    E    +  where 'error looking up logging plugin asdf: plugin "asdf" not found' = APIError(HTTPError('400 Client Error: Bad Request for url: http+docker://localhost/v1.39/containers/create')).explanation
    E    +    where APIError(HTTPError('400 Client Error: Bad Request for url: http+docker://localhost/v1.39/containers/create')) = <ExceptionInfo APIError tblen=6>.value

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-10 18:09:07 +02:00
Ofek Lev d8961ebf12 Upgrade Windows dependency
Signed-off-by: Ofek Lev <ofekmeister@gmail.com>
2020-08-10 18:09:07 +02:00
Mike Haboustak 4b4379a4c7 Add support for DriverOpts in EndpointConfig
Docker API 1.32 added support for providing options to a network driver
via EndpointConfig when connecting a container to a network.

Signed-off-by: Mike Haboustak <haboustak@gmail.com>
2020-08-10 18:09:07 +02:00
Ulysses Souza 10cea0d2a0 Update version to 4.3.0-dev
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-08-10 18:09:07 +02:00
aiordache 9bb7c20938 Make orchestrator field optional
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-08-10 18:07:27 +02:00
Ulysses Souza 0268b02351 Specify when to use `tls` on Context constructor
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-08-10 18:01:24 +02:00
Ulysses Souza a3b42309e9 Post release 4.2.0 update:
- Changelog
- Next Version

Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-08-10 17:59:01 +02:00
Anca Iordache 424421a2b3 Implement context management, lifecycle and unittests.
Signed-off-by: Anca Iordache <anca.iordache@docker.com>
2020-08-10 17:57:52 +02:00
Till Riedel 908b4b3add obey Hostname Username Port and ProxyCommand settings from .ssh/config
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-08-10 17:50:37 +02:00
Till Riedel 25a831165c set logging level of paramiko to warn
Signed-off-by: Till Riedel <riedel@teco.edu>
2020-08-10 17:48:54 +02:00
Sebastiaan van Stijn 341ae50b86 Update credentials-helpers to v0.6.2
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-10 17:39:21 +02:00
Ulysses Souza 7f11cd4179
Merge pull request #2608 from ulyssessouza/4.2.2-release
Bump 4.2.2 release
2020-06-30 18:16:22 +02:00
Ulysses Souza 3f1cb9e36f Bump 4.2.2
Signed-off-by: Ulysses Souza <ulysses.souza@docker.com>
2020-06-30 17:40:32 +02:00
aiordache 02d0051b79 Skip parsing non-docker endpoints
Signed-off-by: aiordache <anca.iordache@docker.com>
2020-06-30 17:40:32 +02:00
Sebastiaan van Stijn 2c54188438 Jenkinsfile: update node selection labels
Make sure we use the LTS nodes, to prevent using machines that
we prepared with cgroups v2 (which is not yet supported by docker v19.03)

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-06-30 17:40:32 +02:00
Ulysses Souza d40c061210 Update version after 4.2.1 release
Signed-off-by: Ulysses Souza <ulyssessouza@gmail.com>
2020-06-30 17:40:32 +02:00
23 changed files with 351 additions and 74 deletions

10
Jenkinsfile vendored
View File

@ -17,7 +17,7 @@ def buildImage = { name, buildargs, pyTag ->
}
def buildImages = { ->
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2", cleanWorkspace: true) {
stage("build image") {
checkout(scm)
@ -31,8 +31,8 @@ def buildImages = { ->
}
def getDockerVersions = { ->
def dockerVersions = ["19.03.5"]
wrappedNode(label: "ubuntu && !zfs && amd64") {
def dockerVersions = ["19.03.12"]
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2") {
def result = sh(script: """docker run --rm \\
--entrypoint=python \\
${imageNamePy3} \\
@ -66,14 +66,14 @@ def runTests = { Map settings ->
throw new Exception("Need test image object, e.g.: `runTests(testImage: img)`")
}
if (!dockerVersion) {
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '1.12.3')`")
throw new Exception("Need Docker version to test, e.g.: `runTests(dockerVersion: '19.03.12')`")
}
if (!pythonVersion) {
throw new Exception("Need Python version being tested, e.g.: `runTests(pythonVersion: 'py2.7')`")
}
{ ->
wrappedNode(label: "ubuntu && !zfs && amd64", cleanWorkspace: true) {
wrappedNode(label: "amd64 && ubuntu-1804 && overlay2", cleanWorkspace: true) {
stage("test python=${pythonVersion} / docker=${dockerVersion}") {
checkout(scm)
def dindContainerName = "dpy-dind-\$BUILD_NUMBER-\$EXECUTOR_NUMBER-${pythonVersion}-${dockerVersion}"

View File

@ -41,8 +41,8 @@ integration-test: build
integration-test-py3: build-py3
docker run -t --rm -v /var/run/docker.sock:/var/run/docker.sock docker-sdk-python3 py.test -v tests/integration/${file}
TEST_API_VERSION ?= 1.35
TEST_ENGINE_VERSION ?= 19.03.5
TEST_API_VERSION ?= 1.39
TEST_ENGINE_VERSION ?= 19.03.12
.PHONY: setup-network
setup-network:

View File

@ -480,6 +480,9 @@ class ContainerApiMixin(object):
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
dns (:py:class:`list`): Set custom DNS servers.
dns_opt (:py:class:`list`): Additional options to be added to the
container's ``resolv.conf`` file
@ -636,6 +639,8 @@ class ContainerApiMixin(object):
network, using the IPv6 protocol. Defaults to ``None``.
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
addresses.
driver_opt (dict): A dictionary of options to provide to the
network driver. Defaults to ``None``.
Returns:
(dict) An endpoint config.
@ -694,7 +699,8 @@ class ContainerApiMixin(object):
return self._stream_raw_result(res, chunk_size, False)
@utils.check_resource('container')
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
def get_archive(self, container, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
encode_stream=False):
"""
Retrieve a file or folder from a container in the form of a tar
archive.
@ -705,6 +711,8 @@ class ContainerApiMixin(object):
chunk_size (int): The number of bytes returned by each iteration
of the generator. If ``None``, data will be streamed as it is
received. Default: 2 MB
encode_stream (bool): Determines if data should be encoded
(gzip-compressed) during transmission. Default: False
Returns:
(tuple): First element is a raw tar data stream. Second element is
@ -729,8 +737,13 @@ class ContainerApiMixin(object):
params = {
'path': path
}
headers = {
"Accept-Encoding": "gzip, deflate"
} if encode_stream else {
"Accept-Encoding": "identity"
}
url = self._url('/containers/{0}/archive', container)
res = self._get(url, params=params, stream=True)
res = self._get(url, params=params, stream=True, headers=headers)
self._raise_for_status(res)
encoded_stat = res.headers.get('x-docker-container-path-stat')
return (
@ -1120,7 +1133,7 @@ class ContainerApiMixin(object):
else:
if decode:
raise errors.InvalidArgument(
"decode is only available in conjuction with stream=True"
"decode is only available in conjunction with stream=True"
)
return self._result(self._get(url, params={'stream': False}),
json=True)

View File

@ -216,7 +216,7 @@ class NetworkApiMixin(object):
def connect_container_to_network(self, container, net_id,
ipv4_address=None, ipv6_address=None,
aliases=None, links=None,
link_local_ips=None):
link_local_ips=None, driver_opt=None):
"""
Connect a container to a network.
@ -240,7 +240,8 @@ class NetworkApiMixin(object):
"Container": container,
"EndpointConfig": self.create_endpoint_config(
aliases=aliases, links=links, ipv4_address=ipv4_address,
ipv6_address=ipv6_address, link_local_ips=link_local_ips
ipv6_address=ipv6_address, link_local_ips=link_local_ips,
driver_opt=driver_opt
),
}

View File

@ -1,7 +1,7 @@
import sys
from .version import version
DEFAULT_DOCKER_API_VERSION = '1.35'
DEFAULT_DOCKER_API_VERSION = '1.39'
MINIMUM_DOCKER_API_VERSION = '1.21'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8

View File

@ -16,30 +16,42 @@ class Context:
if not name:
raise Exception("Name not provided")
self.name = name
self.context_type = None
self.orchestrator = orchestrator
self.endpoints = {}
self.tls_cfg = {}
self.meta_path = "IN MEMORY"
self.tls_path = "IN MEMORY"
if not endpoints:
# set default docker endpoint if no endpoint is set
default_endpoint = "docker" if (
not orchestrator or orchestrator == "swarm"
) else orchestrator
self.endpoints = {
default_endpoint: {
"Host": get_context_host(host, tls),
"SkipTLSVerify": not tls
}
}
else:
for k, v in endpoints.items():
ekeys = v.keys()
for param in ["Host", "SkipTLSVerify"]:
if param not in ekeys:
raise ContextException(
"Missing parameter {} from endpoint {}".format(
param, k))
self.endpoints = endpoints
return
self.tls_cfg = {}
self.meta_path = "IN MEMORY"
self.tls_path = "IN MEMORY"
# check docker endpoints
for k, v in endpoints.items():
if not isinstance(v, dict):
# unknown format
raise ContextException("""Unknown endpoint format for
context {}: {}""".format(name, v))
self.endpoints[k] = v
if k != "docker":
continue
self.endpoints[k]["Host"] = v.get("Host", get_context_host(
host, tls))
self.endpoints[k]["SkipTLSVerify"] = bool(v.get(
"SkipTLSVerify", not tls))
def set_endpoint(
self, name="docker", host=None, tls_cfg=None,
@ -59,9 +71,13 @@ class Context:
@classmethod
def load_context(cls, name):
name, orchestrator, endpoints = Context._load_meta(name)
if name:
instance = cls(name, orchestrator, endpoints=endpoints)
meta = Context._load_meta(name)
if meta:
instance = cls(
meta["Name"],
orchestrator=meta["Metadata"].get("StackOrchestrator", None),
endpoints=meta.get("Endpoints", None))
instance.context_type = meta["Metadata"].get("Type", None)
instance._load_certs()
instance.meta_path = get_meta_dir(name)
return instance
@ -69,26 +85,30 @@ class Context:
@classmethod
def _load_meta(cls, name):
metadata = {}
meta_file = get_meta_file(name)
if os.path.isfile(meta_file):
with open(meta_file) as f:
try:
with open(meta_file) as f:
metadata = json.load(f)
for k, v in metadata["Endpoints"].items():
metadata["Endpoints"][k]["SkipTLSVerify"] = bool(
v["SkipTLSVerify"])
except (IOError, KeyError, ValueError) as e:
# unknown format
raise Exception("""Detected corrupted meta file for
context {} : {}""".format(name, e))
if not os.path.isfile(meta_file):
return None
return (
metadata["Name"],
metadata["Metadata"].get("StackOrchestrator", None),
metadata["Endpoints"])
return None, None, None
metadata = {}
try:
with open(meta_file) as f:
metadata = json.load(f)
except (IOError, KeyError, ValueError) as e:
# unknown format
raise Exception("""Detected corrupted meta file for
context {} : {}""".format(name, e))
# for docker endpoints, set defaults for
# Host and SkipTLSVerify fields
for k, v in metadata["Endpoints"].items():
if k != "docker":
continue
metadata["Endpoints"][k]["Host"] = v.get(
"Host", get_context_host(None, False))
metadata["Endpoints"][k]["SkipTLSVerify"] = bool(
v.get("SkipTLSVerify", True))
return metadata
def _load_certs(self):
certs = {}
@ -157,6 +177,9 @@ class Context:
result.update(self.Storage)
return result
def is_docker_host(self):
return self.context_type is None
@property
def Name(self):
return self.name
@ -164,8 +187,12 @@ class Context:
@property
def Host(self):
if not self.orchestrator or self.orchestrator == "swarm":
return self.endpoints["docker"]["Host"]
return self.endpoints[self.orchestrator]["Host"]
endpoint = self.endpoints.get("docker", None)
if endpoint:
return endpoint.get("Host", None)
return None
return self.endpoints[self.orchestrator].get("Host", None)
@property
def Orchestrator(self):

View File

@ -225,7 +225,8 @@ class Container(Model):
"""
return self.client.api.export(self.id, chunk_size)
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE):
def get_archive(self, path, chunk_size=DEFAULT_DATA_CHUNK_SIZE,
encode_stream=False):
"""
Retrieve a file or folder from the container in the form of a tar
archive.
@ -235,6 +236,8 @@ class Container(Model):
chunk_size (int): The number of bytes returned by each iteration
of the generator. If ``None``, data will be streamed as it is
received. Default: 2 MB
encode_stream (bool): Determines if data should be encoded
(gzip-compressed) during transmission. Default: False
Returns:
(tuple): First element is a raw tar data stream. Second element is
@ -255,7 +258,8 @@ class Container(Model):
... f.write(chunk)
>>> f.close()
"""
return self.client.api.get_archive(self.id, path, chunk_size)
return self.client.api.get_archive(self.id, path,
chunk_size, encode_stream)
def kill(self, signal=None):
"""
@ -579,6 +583,9 @@ class ContainerCollection(Collection):
For example, ``/dev/sda:/dev/xvda:rwm`` allows the container
to have read-write access to the host's ``/dev/sda`` via a
node named ``/dev/xvda`` inside the container.
device_requests (:py:class:`list`): Expose host resources such as
GPUs to the container, as a list of
:py:class:`docker.types.DeviceRequest` instances.
dns (:py:class:`list`): Set custom DNS servers.
dns_opt (:py:class:`list`): Additional options to be added to the
container's ``resolv.conf`` file.
@ -998,6 +1005,7 @@ RUN_HOST_CONFIG_KWARGS = [
'device_write_bps',
'device_write_iops',
'devices',
'device_requests',
'dns_opt',
'dns_search',
'dns',

View File

@ -46,6 +46,8 @@ class Network(Model):
network, using the IPv6 protocol. Defaults to ``None``.
link_local_ips (:py:class:`list`): A list of link-local (IPv4/IPv6)
addresses.
driver_opt (dict): A dictionary of options to provide to the
network driver. Defaults to ``None``.
Raises:
:py:class:`docker.errors.APIError`

View File

@ -32,7 +32,7 @@ class TLSConfig(object):
# https://docs.docker.com/engine/articles/https/
# This diverges from the Docker CLI in that users can specify 'tls'
# here, but also disable any public/default CA pool verification by
# leaving tls_verify=False
# leaving verify=False
self.assert_hostname = assert_hostname
self.assert_fingerprint = assert_fingerprint
@ -62,7 +62,7 @@ class TLSConfig(object):
# https://github.com/docker/docker-py/issues/963
self.ssl_version = ssl.PROTOCOL_TLSv1
# "tls" and "tls_verify" must have both or neither cert/key files In
# "client_cert" must have both or neither cert/key files. In
# either case, Alert the user when both are expected, but any are
# missing.
@ -71,7 +71,7 @@ class TLSConfig(object):
tls_cert, tls_key = client_cert
except ValueError:
raise errors.TLSParameterError(
'client_config must be a tuple of'
'client_cert must be a tuple of'
' (client certificate, key file)'
)
@ -79,7 +79,7 @@ class TLSConfig(object):
not os.path.isfile(tls_key)):
raise errors.TLSParameterError(
'Path to a certificate and key files must be provided'
' through the client_config param'
' through the client_cert param'
)
self.cert = (tls_cert, tls_key)
@ -88,7 +88,7 @@ class TLSConfig(object):
self.ca_cert = ca_cert
if self.verify and self.ca_cert and not os.path.isfile(self.ca_cert):
raise errors.TLSParameterError(
'Invalid CA certificate provided for `tls_ca_cert`.'
'Invalid CA certificate provided for `ca_cert`.'
)
def configure_client(self, client):

View File

@ -1,5 +1,7 @@
# flake8: noqa
from .containers import ContainerConfig, HostConfig, LogConfig, Ulimit
from .containers import (
ContainerConfig, HostConfig, LogConfig, Ulimit, DeviceRequest
)
from .daemon import CancellableStream
from .healthcheck import Healthcheck
from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig

View File

@ -154,6 +154,104 @@ class Ulimit(DictType):
self['Hard'] = value
class DeviceRequest(DictType):
"""
Create a device request to be used with
:py:meth:`~docker.api.container.ContainerApiMixin.create_host_config`.
Args:
driver (str): Which driver to use for this device. Optional.
count (int): Number or devices to request. Optional.
Set to -1 to request all available devices.
device_ids (list): List of strings for device IDs. Optional.
Set either ``count`` or ``device_ids``.
capabilities (list): List of lists of strings to request
capabilities. Optional. The global list acts like an OR,
and the sub-lists are AND. The driver will try to satisfy
one of the sub-lists.
Available capabilities for the ``nvidia`` driver can be found
`here <https://github.com/NVIDIA/nvidia-container-runtime>`_.
options (dict): Driver-specific options. Optional.
"""
def __init__(self, **kwargs):
driver = kwargs.get('driver', kwargs.get('Driver'))
count = kwargs.get('count', kwargs.get('Count'))
device_ids = kwargs.get('device_ids', kwargs.get('DeviceIDs'))
capabilities = kwargs.get('capabilities', kwargs.get('Capabilities'))
options = kwargs.get('options', kwargs.get('Options'))
if driver is None:
driver = ''
elif not isinstance(driver, six.string_types):
raise ValueError('DeviceRequest.driver must be a string')
if count is None:
count = 0
elif not isinstance(count, int):
raise ValueError('DeviceRequest.count must be an integer')
if device_ids is None:
device_ids = []
elif not isinstance(device_ids, list):
raise ValueError('DeviceRequest.device_ids must be a list')
if capabilities is None:
capabilities = []
elif not isinstance(capabilities, list):
raise ValueError('DeviceRequest.capabilities must be a list')
if options is None:
options = {}
elif not isinstance(options, dict):
raise ValueError('DeviceRequest.options must be a dict')
super(DeviceRequest, self).__init__({
'Driver': driver,
'Count': count,
'DeviceIDs': device_ids,
'Capabilities': capabilities,
'Options': options
})
@property
def driver(self):
return self['Driver']
@driver.setter
def driver(self, value):
self['Driver'] = value
@property
def count(self):
return self['Count']
@count.setter
def count(self, value):
self['Count'] = value
@property
def device_ids(self):
return self['DeviceIDs']
@device_ids.setter
def device_ids(self, value):
self['DeviceIDs'] = value
@property
def capabilities(self):
return self['Capabilities']
@capabilities.setter
def capabilities(self, value):
self['Capabilities'] = value
@property
def options(self):
return self['Options']
@options.setter
def options(self, value):
self['Options'] = value
class HostConfig(dict):
def __init__(self, version, binds=None, port_bindings=None,
lxc_conf=None, publish_all_ports=False, links=None,
@ -176,7 +274,7 @@ class HostConfig(dict):
volume_driver=None, cpu_count=None, cpu_percent=None,
nano_cpus=None, cpuset_mems=None, runtime=None, mounts=None,
cpu_rt_period=None, cpu_rt_runtime=None,
device_cgroup_rules=None):
device_cgroup_rules=None, device_requests=None):
if mem_limit is not None:
self['Memory'] = parse_bytes(mem_limit)
@ -536,6 +634,19 @@ class HostConfig(dict):
)
self['DeviceCgroupRules'] = device_cgroup_rules
if device_requests is not None:
if version_lt(version, '1.40'):
raise host_config_version_error('device_requests', '1.40')
if not isinstance(device_requests, list):
raise host_config_type_error(
'device_requests', device_requests, 'list'
)
self['DeviceRequests'] = []
for req in device_requests:
if not isinstance(req, DeviceRequest):
req = DeviceRequest(**req)
self['DeviceRequests'].append(req)
def host_config_type_error(param, param_value, expected):
error_msg = 'Invalid type for {0} param: expected {1} but found {2}'

View File

@ -4,7 +4,7 @@ from ..utils import normalize_links, version_lt
class EndpointConfig(dict):
def __init__(self, version, aliases=None, links=None, ipv4_address=None,
ipv6_address=None, link_local_ips=None):
ipv6_address=None, link_local_ips=None, driver_opt=None):
if version_lt(version, '1.22'):
raise errors.InvalidVersion(
'Endpoint config is not supported for API version < 1.22'
@ -33,6 +33,15 @@ class EndpointConfig(dict):
if ipam_config:
self['IPAMConfig'] = ipam_config
if driver_opt:
if version_lt(version, '1.32'):
raise errors.InvalidVersion(
'DriverOpts is not supported for API version < 1.32'
)
if not isinstance(driver_opt, dict):
raise TypeError('driver_opt must be a dictionary')
self['DriverOpts'] = driver_opt
class NetworkingConfig(dict):
def __init__(self, endpoints_config=None):

View File

@ -1,2 +1,2 @@
version = "4.2.1"
version = "4.3.0"
version_info = tuple([int(d) for d in version.split("-")[0].split(".")])

View File

@ -1,6 +1,30 @@
Change log
==========
4.3.0
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/64?closed=1)
### Features
- Add `DeviceRequest` type to expose host resources such as GPUs
- Add support for `DriverOpts` in EndpointConfig
- Disable compression by default when using container.get_archive method
### Miscellaneous
- Update default API version to v1.39
- Update test engine version to 19.03.12
4.2.2
-----
[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone/66?closed=1)
### Bugfixes
- Fix context load for non-docker endpoints
4.2.1
-----
@ -27,7 +51,6 @@ Change log
- Add support for docker contexts through `docker.ContextAPI`
4.1.0
-----
@ -121,7 +144,7 @@ Change log
### Bugfixes
* Fix base_url to keep TCP protocol on utils.py by letting the responsability of changing the
* Fix base_url to keep TCP protocol on utils.py by letting the responsibility of changing the
protocol to `parse_host` afterwards, letting `base_url` with the original value.
* XFAIL test_attach_stream_and_cancel on TLS
@ -1225,7 +1248,7 @@ like the others
(`Client.volumes`, `Client.create_volume`, `Client.inspect_volume`,
`Client.remove_volume`).
* Added support for the `group_add` parameter in `create_host_config`.
* Added support for the CPU CFS (`cpu_quota` and `cpu_period`) parameteres
* Added support for the CPU CFS (`cpu_quota` and `cpu_period`) parameters
in `create_host_config`.
* Added support for the archive API endpoint (`Client.get_archive`,
`Client.put_archive`).

View File

@ -11,8 +11,7 @@ paramiko==2.4.2
pycparser==2.17
pyOpenSSL==18.0.0
pyparsing==2.2.0
pypiwin32==219; sys_platform == 'win32' and python_version < '3.6'
pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6'
pywin32==227; sys_platform == 'win32'
requests==2.20.0
six==1.10.0
urllib3==1.24.3

View File

@ -24,10 +24,7 @@ extras_require = {
':python_version < "3.3"': 'ipaddress >= 1.0.16',
# win32 APIs if on Windows (required for npipe support)
# Python 3.6 is only compatible with v220 ; Python < 3.5 is not supported
# on v220 ; ALL versions are broken for v222 (as of 2018-01-26)
':sys_platform == "win32" and python_version < "3.6"': 'pypiwin32==219',
':sys_platform == "win32" and python_version >= "3.6"': 'pypiwin32==223',
':sys_platform == "win32"': 'pywin32==227',
# If using docker-py over TLS, highly recommend this option is
# pip-installed or pinned.

View File

@ -4,7 +4,7 @@ FROM python:${PYTHON_VERSION}
ARG APT_MIRROR
RUN sed -ri "s/(httpredir|deb).debian.org/${APT_MIRROR:-deb.debian.org}/g" /etc/apt/sources.list \
&& sed -ri "s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g" /etc/apt/sources.list
&& sed -ri "s/(security).debian.org/${APT_MIRROR:-security.debian.org}/g" /etc/apt/sources.list
RUN apt-get update && apt-get -y install \
gnupg2 \

View File

@ -279,7 +279,7 @@ class CreateContainerTest(BaseAPIIntegrationTest):
expected_msgs = [
"logger: no log driver named 'asdf' is registered",
"looking up logging plugin asdf: plugin \"asdf\" not found",
"error looking up logging plugin asdf: plugin \"asdf\" not found",
]
with pytest.raises(docker.errors.APIError) as excinfo:
# raises an internal server error 500

View File

@ -275,6 +275,27 @@ class TestNetworks(BaseAPIIntegrationTest):
assert 'LinkLocalIPs' in net_cfg['IPAMConfig']
assert net_cfg['IPAMConfig']['LinkLocalIPs'] == ['169.254.8.8']
@requires_api_version('1.32')
def test_create_with_driveropt(self):
container = self.client.create_container(
TEST_IMG, 'top',
networking_config=self.client.create_networking_config(
{
'bridge': self.client.create_endpoint_config(
driver_opt={'com.docker-py.setting': 'on'}
)
}
),
host_config=self.client.create_host_config(network_mode='bridge')
)
self.tmp_containers.append(container)
self.client.start(container)
container_data = self.client.inspect_container(container)
net_cfg = container_data['NetworkSettings']['Networks']['bridge']
assert 'DriverOpts' in net_cfg
assert 'com.docker-py.setting' in net_cfg['DriverOpts']
assert net_cfg['DriverOpts']['com.docker-py.setting'] == 'on'
@requires_api_version('1.22')
def test_create_with_links(self):
net_name, net_id = self.create_network()

View File

@ -5,6 +5,7 @@ import json
import signal
import docker
from docker.api import APIClient
import pytest
import six
@ -12,7 +13,7 @@ from . import fake_api
from ..helpers import requires_api_version
from .api_test import (
BaseAPIClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS,
fake_inspect_container
fake_inspect_container, url_base
)
try:
@ -767,6 +768,67 @@ class CreateContainerTest(BaseAPIClientTest):
assert args[1]['headers'] == {'Content-Type': 'application/json'}
assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS
def test_create_container_with_device_requests(self):
client = APIClient(version='1.40')
fake_api.fake_responses.setdefault(
'{0}/v1.40/containers/create'.format(fake_api.prefix),
fake_api.post_fake_create_container,
)
client.create_container(
'busybox', 'true', host_config=client.create_host_config(
device_requests=[
{
'device_ids': [
'0',
'GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a'
]
},
{
'driver': 'nvidia',
'Count': -1,
'capabilities': [
['gpu', 'utility']
],
'options': {
'key': 'value'
}
}
]
)
)
args = fake_request.call_args
assert args[0][1] == url_base + 'v1.40/' + 'containers/create'
expected_payload = self.base_create_payload()
expected_payload['HostConfig'] = client.create_host_config()
expected_payload['HostConfig']['DeviceRequests'] = [
{
'Driver': '',
'Count': 0,
'DeviceIDs': [
'0',
'GPU-3a23c669-1f69-c64e-cf85-44e9b07e7a2a'
],
'Capabilities': [],
'Options': {}
},
{
'Driver': 'nvidia',
'Count': -1,
'DeviceIDs': [],
'Capabilities': [
['gpu', 'utility']
],
'Options': {
'key': 'value'
}
}
]
assert json.loads(args[1]['data']) == expected_payload
assert args[1]['headers']['Content-Type'] == 'application/json'
assert set(args[1]['headers']) <= {'Content-Type', 'User-Agent'}
assert args[1]['timeout'] == DEFAULT_TIMEOUT_SECONDS
def test_create_container_with_labels_dict(self):
labels_dict = {
six.text_type('foo'): six.text_type('1'),

View File

@ -136,7 +136,8 @@ class NetworkTest(BaseAPIClientTest):
container={'Id': container_id},
net_id=network_id,
aliases=['foo', 'bar'],
links=[('baz', 'quux')]
links=[('baz', 'quux')],
driver_opt={'com.docker-py.setting': 'yes'},
)
assert post.call_args[0][0] == (
@ -148,6 +149,7 @@ class NetworkTest(BaseAPIClientTest):
'EndpointConfig': {
'Aliases': ['foo', 'bar'],
'Links': ['baz:quux'],
'DriverOpts': {'com.docker-py.setting': 'yes'},
},
}

View File

@ -450,7 +450,7 @@ class ContainerTest(unittest.TestCase):
container = client.containers.get(FAKE_CONTAINER_ID)
container.get_archive('foo')
client.api.get_archive.assert_called_with(
FAKE_CONTAINER_ID, 'foo', DEFAULT_DATA_CHUNK_SIZE
FAKE_CONTAINER_ID, 'foo', DEFAULT_DATA_CHUNK_SIZE, False
)
def test_image(self):

View File

@ -335,7 +335,7 @@ class ExcludePathsTest(unittest.TestCase):
# Dockerignore reference stipulates that absolute paths are
# equivalent to relative paths, hence /../foo should be
# equivalent to ../foo. It also stipulates that paths are run
# through Go's filepath.Clean, which explicitely "replace
# through Go's filepath.Clean, which explicitly "replace
# "/.." by "/" at the beginning of a path".
assert exclude_paths(
base,