From 79edcc28f7b87785578f13c99145c33db81697e5 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 5 Apr 2017 17:05:08 -0700 Subject: [PATCH] Add support for volume_driver in HostConfig Some cleanup in ContainerConfig + warning if volume_driver is provided (API>1.20) Signed-off-by: Joffrey F --- docker/types/containers.py | 91 ++++++++++++++++++-------------- tests/unit/api_container_test.py | 11 ++-- tests/unit/dockertypes_test.py | 25 ++++++++- 3 files changed, 79 insertions(+), 48 deletions(-) diff --git a/docker/types/containers.py b/docker/types/containers.py index 5a5079a8..0af24cb8 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -118,7 +118,7 @@ class HostConfig(dict): tmpfs=None, oom_score_adj=None, dns_opt=None, cpu_shares=None, cpuset_cpus=None, userns_mode=None, pids_limit=None, isolation=None, auto_remove=False, storage_opt=None, - init=None, init_path=None): + init=None, init_path=None, volume_driver=None): if mem_limit is not None: self['Memory'] = parse_bytes(mem_limit) @@ -428,6 +428,11 @@ class HostConfig(dict): raise host_config_version_error('init_path', '1.25') self['InitPath'] = init_path + if volume_driver is not None: + if version_lt(version, '1.21'): + raise host_config_version_error('volume_driver', '1.21') + self['VolumeDriver'] = volume_driver + def host_config_type_error(param, param_value, expected): error_msg = 'Invalid type for {0} param: expected {1} but found {2}' @@ -456,43 +461,27 @@ class ContainerConfig(dict): stop_signal=None, networking_config=None, healthcheck=None, stop_timeout=None ): - if isinstance(command, six.string_types): - command = split_command(command) + if version_gte(version, '1.10'): + message = ('{0!r} parameter has no effect on create_container().' + ' It has been moved to host_config') + if dns is not None: + raise errors.InvalidVersion(message.format('dns')) + if volumes_from is not None: + raise errors.InvalidVersion(message.format('volumes_from')) - if isinstance(entrypoint, six.string_types): - entrypoint = split_command(entrypoint) - - if isinstance(environment, dict): - environment = format_environment(environment) - - if labels is not None and version_lt(version, '1.18'): - raise errors.InvalidVersion( - 'labels were only introduced in API version 1.18' - ) - - if cpuset is not None or cpu_shares is not None: - if version_gte(version, '1.18'): + if version_lt(version, '1.18'): + if labels is not None: + raise errors.InvalidVersion( + 'labels were only introduced in API version 1.18' + ) + else: + if cpuset is not None or cpu_shares is not None: warnings.warn( 'The cpuset_cpus and cpu_shares options have been moved to' ' host_config in API version 1.18, and will be removed', DeprecationWarning ) - if stop_signal is not None and version_lt(version, '1.21'): - raise errors.InvalidVersion( - 'stop_signal was only introduced in API version 1.21' - ) - - if stop_timeout is not None and version_lt(version, '1.25'): - raise errors.InvalidVersion( - 'stop_timeout was only introduced in API version 1.25' - ) - - if healthcheck is not None and version_lt(version, '1.24'): - raise errors.InvalidVersion( - 'Health options were only introduced in API version 1.24' - ) - if version_lt(version, '1.19'): if volume_driver is not None: raise errors.InvalidVersion( @@ -513,6 +502,38 @@ class ContainerConfig(dict): 'version 1.19' ) + if version_lt(version, '1.21'): + if stop_signal is not None: + raise errors.InvalidVersion( + 'stop_signal was only introduced in API version 1.21' + ) + else: + if volume_driver is not None: + warnings.warn( + 'The volume_driver option has been moved to' + ' host_config in API version 1.21, and will be removed', + DeprecationWarning + ) + + if stop_timeout is not None and version_lt(version, '1.25'): + raise errors.InvalidVersion( + 'stop_timeout was only introduced in API version 1.25' + ) + + if healthcheck is not None and version_lt(version, '1.24'): + raise errors.InvalidVersion( + 'Health options were only introduced in API version 1.24' + ) + + if isinstance(command, six.string_types): + command = split_command(command) + + if isinstance(entrypoint, six.string_types): + entrypoint = split_command(entrypoint) + + if isinstance(environment, dict): + environment = format_environment(environment) + if isinstance(labels, list): labels = dict((lbl, six.text_type('')) for lbl in labels) @@ -566,14 +587,6 @@ class ContainerConfig(dict): attach_stdin = True stdin_once = True - if version_gte(version, '1.10'): - message = ('{0!r} parameter has no effect on create_container().' - ' It has been moved to host_config') - if dns is not None: - raise errors.InvalidVersion(message.format('dns')) - if volumes_from is not None: - raise errors.InvalidVersion(message.format('volumes_from')) - self.update({ 'Hostname': hostname, 'Domainname': domainname, diff --git a/tests/unit/api_container_test.py b/tests/unit/api_container_test.py index 51d66781..ad79c5c6 100644 --- a/tests/unit/api_container_test.py +++ b/tests/unit/api_container_test.py @@ -407,11 +407,8 @@ class CreateContainerTest(BaseAPIClientTest): {'Content-Type': 'application/json'}) def test_create_container_empty_volumes_from(self): - self.client.create_container('busybox', 'true', volumes_from=[]) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertTrue('VolumesFrom' not in data) + with pytest.raises(docker.errors.InvalidVersion): + self.client.create_container('busybox', 'true', volumes_from=[]) def test_create_named_container(self): self.client.create_container('busybox', 'true', @@ -978,11 +975,11 @@ class CreateContainerTest(BaseAPIClientTest): self.client.create_container( 'busybox', 'true', host_config=self.client.create_host_config( + volume_driver='foodriver', binds={volume_name: { "bind": mount_dest, "ro": False }}), - volume_driver='foodriver', ) args = fake_request.call_args @@ -990,8 +987,8 @@ class CreateContainerTest(BaseAPIClientTest): args[0][1], url_prefix + 'containers/create' ) expected_payload = self.base_create_payload() - expected_payload['VolumeDriver'] = 'foodriver' expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['VolumeDriver'] = 'foodriver' expected_payload['HostConfig']['Binds'] = ["name:/mnt:rw"] self.assertEqual(json.loads(args[1]['data']), expected_payload) self.assertEqual(args[1]['headers'], diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index 5c470ffa..cb1d90ca 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -1,14 +1,15 @@ # -*- coding: utf-8 -*- import unittest +import warnings import pytest from docker.constants import DEFAULT_DOCKER_API_VERSION from docker.errors import InvalidArgument, InvalidVersion from docker.types import ( - EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, - ServiceMode, Ulimit, + ContainerConfig, EndpointConfig, HostConfig, IPAMConfig, IPAMPool, + LogConfig, Mount, ServiceMode, Ulimit, ) try: @@ -165,6 +166,26 @@ class HostConfigTest(unittest.TestCase): with pytest.raises(TypeError): create_host_config(version='1.24', mem_swappiness='40') + def test_create_host_config_with_volume_driver(self): + with pytest.raises(InvalidVersion): + create_host_config(version='1.20', volume_driver='local') + + config = create_host_config(version='1.21', volume_driver='local') + assert config.get('VolumeDriver') == 'local' + + +class ContainerConfigTest(unittest.TestCase): + def test_create_container_config_volume_driver_warning(self): + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + ContainerConfig( + version='1.21', image='scratch', command=None, + volume_driver='local' + ) + + assert len(w) == 1 + assert 'The volume_driver option has been moved' in str(w[0].message) + class UlimitTest(unittest.TestCase): def test_create_host_config_dict_ulimit(self):