From 29dee5ac2e6a952b4d66090549a5a443e15c29d6 Mon Sep 17 00:00:00 2001 From: Marco Trillo Date: Fri, 29 Jun 2018 14:54:48 +0200 Subject: [PATCH 01/11] Add support for `uts_mode` parameter in `Client.create_host_config`. This parameter allows to set the UTS namespace of the container, as in the `--uts=X` Docker CLI parameter: The only allowed value, if set, is "host". Signed-off-by: Marco Trillo Signed-off-by: Diego Alvarez --- docker/api/container.py | 2 ++ docker/models/containers.py | 1 + docker/types/containers.py | 15 ++++++++++----- tests/integration/api_container_test.py | 10 ++++++++++ tests/unit/dockertypes_test.py | 6 ++++++ tests/unit/models_containers_test.py | 2 ++ 6 files changed, 31 insertions(+), 5 deletions(-) diff --git a/docker/api/container.py b/docker/api/container.py index d4f75f54b2..d8416066d5 100644 --- a/docker/api/container.py +++ b/docker/api/container.py @@ -547,6 +547,8 @@ def create_host_config(self, *args, **kwargs): userns_mode (str): Sets the user namespace mode for the container when user namespace remapping option is enabled. Supported values are: ``host`` + uts_mode (str): Sets the UTS namespace mode for the container. + Supported values are: ``host`` volumes_from (:py:class:`list`): List of container names or IDs to get volumes from. runtime (str): Runtime to use with this container. diff --git a/docker/models/containers.py b/docker/models/containers.py index b33a718f75..de6222ec49 100644 --- a/docker/models/containers.py +++ b/docker/models/containers.py @@ -995,6 +995,7 @@ def prune(self, filters=None): 'tmpfs', 'ulimits', 'userns_mode', + 'uts_mode', 'version', 'volumes_from', 'runtime' diff --git a/docker/types/containers.py b/docker/types/containers.py index 252142073f..e7841bcb46 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -115,11 +115,11 @@ def __init__(self, version, binds=None, port_bindings=None, device_read_iops=None, device_write_iops=None, oom_kill_disable=False, shm_size=None, sysctls=None, 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, volume_driver=None, - cpu_count=None, cpu_percent=None, nano_cpus=None, - cpuset_mems=None, runtime=None, mounts=None, + cpuset_cpus=None, userns_mode=None, uts_mode=None, + pids_limit=None, isolation=None, auto_remove=False, + storage_opt=None, init=None, init_path=None, + 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): @@ -392,6 +392,11 @@ def __init__(self, version, binds=None, port_bindings=None, raise host_config_value_error("userns_mode", userns_mode) self['UsernsMode'] = userns_mode + if uts_mode: + if uts_mode != "host": + raise host_config_value_error("uts_mode", uts_mode) + self['UTSMode'] = uts_mode + if pids_limit: if not isinstance(pids_limit, int): raise host_config_type_error('pids_limit', pids_limit, 'int') diff --git a/tests/integration/api_container_test.py b/tests/integration/api_container_test.py index ff7014879c..6ce846bb20 100644 --- a/tests/integration/api_container_test.py +++ b/tests/integration/api_container_test.py @@ -490,6 +490,16 @@ def test_create_with_device_cgroup_rules(self): self.client.start(ctnr) assert rule in self.client.logs(ctnr).decode('utf-8') + def test_create_with_uts_mode(self): + container = self.client.create_container( + BUSYBOX, ['echo'], host_config=self.client.create_host_config( + uts_mode='host' + ) + ) + self.tmp_containers.append(container) + config = self.client.inspect_container(container) + assert config['HostConfig']['UTSMode'] == 'host' + @pytest.mark.xfail( IS_WINDOWS_PLATFORM, reason='Test not designed for Windows platform' diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index 2be05784bb..cdacf8cd5b 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -85,6 +85,12 @@ def test_create_host_config_with_userns_mode(self): with pytest.raises(ValueError): create_host_config(version='1.23', userns_mode='host12') + def test_create_host_config_with_uts(self): + config = create_host_config(version='1.15', uts_mode='host') + assert config.get('UTSMode') == 'host' + with pytest.raises(ValueError): + create_host_config(version='1.15', uts_mode='host12') + def test_create_host_config_with_oom_score_adj(self): config = create_host_config(version='1.22', oom_score_adj=100) assert config.get('OomScoreAdj') == 100 diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 48a5288869..22dd241064 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -95,6 +95,7 @@ def test_create_container_args(self): ulimits=[{"Name": "nofile", "Soft": 1024, "Hard": 2048}], user='bob', userns_mode='host', + uts_mode='host', version='1.23', volume_driver='some_driver', volumes=[ @@ -174,6 +175,7 @@ def test_create_container_args(self): 'Tmpfs': {'/blah': ''}, 'Ulimits': [{"Name": "nofile", "Soft": 1024, "Hard": 2048}], 'UsernsMode': 'host', + 'UTSMode': 'host', 'VolumesFrom': ['container'], }, healthcheck={'test': 'true'}, From dac943a91dfc6622ff77bae5521214a4faaa3f02 Mon Sep 17 00:00:00 2001 From: Aron Parsons Date: Wed, 18 Jul 2018 19:02:44 -0400 Subject: [PATCH 02/11] honor placement preferences via services.create() this allows creating a service with placement preferences when calling services.create(). only constraints were being honored before. related to https://github.com/docker/docker-py/pull/1615 Signed-off-by: Aron Parsons --- docker/models/services.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker/models/services.py b/docker/models/services.py index 458d2c8730..612f34544e 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -321,10 +321,15 @@ def _get_create_service_kwargs(func_name, kwargs): if 'container_labels' in kwargs: container_spec_kwargs['labels'] = kwargs.pop('container_labels') + placement = {} + if 'constraints' in kwargs: - task_template_kwargs['placement'] = { - 'Constraints': kwargs.pop('constraints') - } + placement['Constraints'] = kwargs.pop('constraints') + + if 'preferences' in kwargs: + placement['Preferences'] = kwargs.pop('preferences') + + task_template_kwargs['placement'] = placement if 'log_driver' in kwargs: task_template_kwargs['log_driver'] = { From f71d1cf3cfdce2cce77edc9dcdc33879175d44f7 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Wed, 18 Jul 2018 16:59:09 -0700 Subject: [PATCH 03/11] Update deps for 3.3 & 3.7 support Signed-off-by: Joffrey F --- .travis.yml | 4 ++++ requirements.txt | 6 ++++-- setup.py | 11 ++++++++--- test-requirements.txt | 3 ++- tox.ini | 2 +- 5 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index 842e352836..1c837a2634 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,6 +10,10 @@ matrix: env: TOXENV=py35 - python: 3.6 env: TOXENV=py36 + - python: 3.7 + env: TOXENV=py37 + dist: xenial + sudo: true - env: TOXENV=flake8 install: diff --git a/requirements.txt b/requirements.txt index 6c5e7d03be..289dea9150 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,8 @@ appdirs==1.4.3 asn1crypto==0.22.0 backports.ssl-match-hostname==3.5.0.1 cffi==1.10.0 -cryptography==1.9 +cryptography==1.9; python_version == '3.3' +cryptography==2.3; python_version > '3.3' docker-pycreds==0.3.0 enum34==1.1.6 idna==2.5 @@ -12,7 +13,8 @@ pycparser==2.17 pyOpenSSL==17.0.0 pyparsing==2.2.0 pypiwin32==219; sys_platform == 'win32' and python_version < '3.6' -pypiwin32==220; sys_platform == 'win32' and python_version >= '3.6' +pypiwin32==223; sys_platform == 'win32' and python_version >= '3.6' requests==2.14.2 six==1.10.0 websocket-client==0.40.0 +urllib3==1.21.1; python_version == '3.3' \ No newline at end of file diff --git a/setup.py b/setup.py index 57b2b5a813..1b208e5c66 100644 --- a/setup.py +++ b/setup.py @@ -10,10 +10,10 @@ SOURCE_DIR = os.path.join(ROOT_DIR) requirements = [ - 'requests >= 2.14.2, != 2.18.0', 'six >= 1.4.0', 'websocket-client >= 0.32.0', - 'docker-pycreds >= 0.3.0' + 'docker-pycreds >= 0.3.0', + 'requests >= 2.14.2, != 2.18.0', ] extras_require = { @@ -27,7 +27,10 @@ # 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==220', + ':sys_platform == "win32" and python_version >= "3.6"': 'pypiwin32==223', + + # urllib3 drops support for Python 3.3 in 1.23 + ':python_version == "3.3"': 'urllib3 < 1.23', # If using docker-py over TLS, highly recommend this option is # pip-installed or pinned. @@ -38,6 +41,7 @@ # installing the extra dependencies, install the following instead: # 'requests[security] >= 2.5.2, != 2.11.0, != 2.12.2' 'tls': ['pyOpenSSL>=0.14', 'cryptography>=1.3.4', 'idna>=2.0.0'], + } version = None @@ -81,6 +85,7 @@ 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', 'Topic :: Utilities', 'License :: OSI Approved :: Apache Software License', ], diff --git a/test-requirements.txt b/test-requirements.txt index 09680b6897..9ad59cc664 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,6 +1,7 @@ coverage==3.7.1 flake8==3.4.1 mock==1.0.1 -pytest==2.9.1 +pytest==2.9.1; python_version == '3.3' +pytest==3.6.3; python_version > '3.3' pytest-cov==2.1.0 pytest-timeout==1.2.1 diff --git a/tox.ini b/tox.ini index 41d88605d3..5396147ecc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist = py27, py33, py34, py35, py36, flake8 +envlist = py27, py34, py35, py36, py37, flake8 skipsdist=True [testenv] From d4345b5824a23d088bf9c44d9fb87382f159a43a Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Tue, 10 Jul 2018 15:51:17 -0400 Subject: [PATCH 04/11] Add credHelpers support to set_auth_headers in build Signed-off-by: Joffrey F --- docker/api/build.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/docker/api/build.py b/docker/api/build.py index 419255fc62..0486dce62d 100644 --- a/docker/api/build.py +++ b/docker/api/build.py @@ -293,20 +293,28 @@ def _set_auth_headers(self, headers): # Send the full auth configuration (if any exists), since the build # could use any (or all) of the registries. if self._auth_configs: + auth_cfgs = self._auth_configs auth_data = {} - if self._auth_configs.get('credsStore'): + if auth_cfgs.get('credsStore'): # Using a credentials store, we need to retrieve the # credentials for each registry listed in the config.json file # Matches CLI behavior: https://github.com/docker/docker/blob/ # 67b85f9d26f1b0b2b240f2d794748fac0f45243c/cliconfig/ # credentials/native_store.go#L68-L83 - for registry in self._auth_configs.get('auths', {}).keys(): + for registry in auth_cfgs.get('auths', {}).keys(): auth_data[registry] = auth.resolve_authconfig( - self._auth_configs, registry, + auth_cfgs, registry, credstore_env=self.credstore_env, ) else: - auth_data = self._auth_configs.get('auths', {}).copy() + for registry in auth_cfgs.get('credHelpers', {}).keys(): + auth_data[registry] = auth.resolve_authconfig( + auth_cfgs, registry, + credstore_env=self.credstore_env + ) + for registry, creds in auth_cfgs.get('auths', {}).items(): + if registry not in auth_data: + auth_data[registry] = creds # See https://github.com/docker/docker-py/issues/1683 if auth.INDEX_NAME in auth_data: auth_data[auth.INDEX_URL] = auth_data[auth.INDEX_NAME] From 185f72723ab8bac589e73e58be64f1665472762e Mon Sep 17 00:00:00 2001 From: Nikolay Murga Date: Fri, 20 Jul 2018 12:53:44 +0300 Subject: [PATCH 05/11] Add 'rollback' command as allowed for failure_action Signed-off-by: Nikolay Murga --- docker/types/services.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/types/services.py b/docker/types/services.py index 31f4750f45..a914cef691 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -371,7 +371,7 @@ class UpdateConfig(dict): delay (int): Amount of time between updates. failure_action (string): Action to take if an updated task fails to run, or stops running during the update. Acceptable values are - ``continue`` and ``pause``. Default: ``continue`` + ``continue``, ``rollback`` and ``pause``. Default: ``continue`` monitor (int): Amount of time to monitor each updated task for failures, in nanoseconds. max_failure_ratio (float): The fraction of tasks that may fail during @@ -385,7 +385,7 @@ def __init__(self, parallelism=0, delay=None, failure_action='continue', self['Parallelism'] = parallelism if delay is not None: self['Delay'] = delay - if failure_action not in ('pause', 'continue'): + if failure_action not in ('pause', 'continue', 'rollback'): raise errors.InvalidArgument( 'failure_action must be either `pause` or `continue`.' ) From 8ee446631d6da7ca8e52a4f65aafecf2de1c8ea6 Mon Sep 17 00:00:00 2001 From: Nikolay Murga Date: Fri, 20 Jul 2018 13:20:19 +0300 Subject: [PATCH 06/11] Add documentation for delay property Signed-off-by: Nikolay Murga --- docker/types/services.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/types/services.py b/docker/types/services.py index a914cef691..294076384b 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -368,7 +368,7 @@ class UpdateConfig(dict): parallelism (int): Maximum number of tasks to be updated in one iteration (0 means unlimited parallelism). Default: 0. - delay (int): Amount of time between updates. + delay (int): Amount of time between updates, in nanoseconds. failure_action (string): Action to take if an updated task fails to run, or stops running during the update. Acceptable values are ``continue``, ``rollback`` and ``pause``. Default: ``continue`` From cbc7623ea09fd63a75f74378da11bd7cf266e32c Mon Sep 17 00:00:00 2001 From: Anthony Sottile Date: Thu, 2 Aug 2018 12:00:11 -0700 Subject: [PATCH 07/11] Allow user=0 to be passed in create_container Signed-off-by: Anthony Sottile --- docker/types/containers.py | 2 +- tests/unit/types_containers_test.py | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 tests/unit/types_containers_test.py diff --git a/docker/types/containers.py b/docker/types/containers.py index e7841bcb46..9dfea8ceb8 100644 --- a/docker/types/containers.py +++ b/docker/types/containers.py @@ -578,7 +578,7 @@ def __init__( 'Hostname': hostname, 'Domainname': domainname, 'ExposedPorts': ports, - 'User': six.text_type(user) if user else None, + 'User': six.text_type(user) if user is not None else None, 'Tty': tty, 'OpenStdin': stdin_open, 'StdinOnce': stdin_once, diff --git a/tests/unit/types_containers_test.py b/tests/unit/types_containers_test.py new file mode 100644 index 0000000000..b0ad0a71ac --- /dev/null +++ b/tests/unit/types_containers_test.py @@ -0,0 +1,6 @@ +from docker.types.containers import ContainerConfig + + +def test_uid_0_is_not_elided(): + x = ContainerConfig(image='i', version='v', command='true', user=0) + assert x['User'] == '0' From c2c9ccdd803352fef42eaf9efedf9238e2613a85 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Fri, 3 Aug 2018 14:04:04 -0700 Subject: [PATCH 08/11] Improve placement handling in DockerClient.services.create Signed-off-by: Joffrey F --- docker/models/services.py | 22 ++++++++++++++-------- tests/unit/models_services_test.py | 8 +++++++- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/docker/models/services.py b/docker/models/services.py index 612f34544e..7fbd165102 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -1,6 +1,6 @@ import copy from docker.errors import create_unexpected_kwargs_error, InvalidArgument -from docker.types import TaskTemplate, ContainerSpec, ServiceMode +from docker.types import TaskTemplate, ContainerSpec, Placement, ServiceMode from .resource import Model, Collection @@ -153,6 +153,9 @@ def create(self, image, command=None, **kwargs): command (list of str or str): Command to run. args (list of str): Arguments to the command. constraints (list of str): Placement constraints. + preferences (list of str): Placement preferences. + platforms (list of tuple): A list of platforms constraints + expressed as ``(arch, os)`` tuples container_labels (dict): Labels to apply to the container. endpoint_spec (EndpointSpec): Properties that can be configured to access and load balance a service. Default: ``None``. @@ -302,6 +305,12 @@ def list(self, **kwargs): 'endpoint_spec', ] +PLACEMENT_KWARGS = [ + 'constraints', + 'preferences', + 'platforms', +] + def _get_create_service_kwargs(func_name, kwargs): # Copy over things which can be copied directly @@ -322,13 +331,10 @@ def _get_create_service_kwargs(func_name, kwargs): container_spec_kwargs['labels'] = kwargs.pop('container_labels') placement = {} - - if 'constraints' in kwargs: - placement['Constraints'] = kwargs.pop('constraints') - - if 'preferences' in kwargs: - placement['Preferences'] = kwargs.pop('preferences') - + for key in copy.copy(kwargs): + if key in PLACEMENT_KWARGS: + placement[key] = kwargs.pop(key) + placement = Placement(**placement) task_template_kwargs['placement'] = placement if 'log_driver' in kwargs: diff --git a/tests/unit/models_services_test.py b/tests/unit/models_services_test.py index 247bb4a4aa..a4ac50c3fe 100644 --- a/tests/unit/models_services_test.py +++ b/tests/unit/models_services_test.py @@ -26,6 +26,8 @@ def test_get_create_service_kwargs(self): 'mounts': [{'some': 'mounts'}], 'stop_grace_period': 5, 'constraints': ['foo=bar'], + 'preferences': ['bar=baz'], + 'platforms': [('x86_64', 'linux')], }) task_template = kwargs.pop('task_template') @@ -41,7 +43,11 @@ def test_get_create_service_kwargs(self): 'ContainerSpec', 'Resources', 'RestartPolicy', 'Placement', 'LogDriver', 'Networks' ]) - assert task_template['Placement'] == {'Constraints': ['foo=bar']} + assert task_template['Placement'] == { + 'Constraints': ['foo=bar'], + 'Preferences': ['bar=baz'], + 'Platforms': [{'Architecture': 'x86_64', 'OS': 'linux'}], + } assert task_template['LogDriver'] == { 'Name': 'logdriver', 'Options': {'foo': 'bar'} From e4b509ecace12dab1244b3dbcc33196d5e59e9d3 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 9 Aug 2018 15:56:11 -0700 Subject: [PATCH 09/11] Add version checks and test Signed-off-by: Joffrey F --- docker/api/service.py | 6 ++++++ docker/types/services.py | 5 +++-- tests/integration/api_service_test.py | 14 ++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/docker/api/service.py b/docker/api/service.py index 03b0ca6ea2..1dbe2697f1 100644 --- a/docker/api/service.py +++ b/docker/api/service.py @@ -18,6 +18,12 @@ def raise_version_error(param, min_version): if 'Monitor' in update_config: raise_version_error('UpdateConfig.monitor', '1.25') + if utils.version_lt(version, '1.28'): + if update_config.get('FailureAction') == 'rollback': + raise_version_error( + 'UpdateConfig.failure_action rollback', '1.28' + ) + if utils.version_lt(version, '1.29'): if 'Order' in update_config: raise_version_error('UpdateConfig.order', '1.29') diff --git a/docker/types/services.py b/docker/types/services.py index 294076384b..a883f3ff7d 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -371,7 +371,8 @@ class UpdateConfig(dict): delay (int): Amount of time between updates, in nanoseconds. failure_action (string): Action to take if an updated task fails to run, or stops running during the update. Acceptable values are - ``continue``, ``rollback`` and ``pause``. Default: ``continue`` + ``continue``, ``pause``, as well as ``rollback`` since API v1.28. + Default: ``continue`` monitor (int): Amount of time to monitor each updated task for failures, in nanoseconds. max_failure_ratio (float): The fraction of tasks that may fail during @@ -387,7 +388,7 @@ def __init__(self, parallelism=0, delay=None, failure_action='continue', self['Delay'] = delay if failure_action not in ('pause', 'continue', 'rollback'): raise errors.InvalidArgument( - 'failure_action must be either `pause` or `continue`.' + 'failure_action must be one of `pause`, `continue`, `rollback`' ) self['FailureAction'] = failure_action diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index 85f9dccf26..ba2ed91f72 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -281,6 +281,20 @@ def test_create_service_with_update_config(self): assert update_config['Delay'] == uc['Delay'] assert update_config['FailureAction'] == uc['FailureAction'] + @requires_api_version('1.28') + def test_create_service_with_failure_action_rollback(self): + container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + task_tmpl = docker.types.TaskTemplate(container_spec) + update_config = docker.types.UpdateConfig(failure_action='rollback') + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, update_config=update_config, name=name + ) + svc_info = self.client.inspect_service(svc_id) + assert 'UpdateConfig' in svc_info['Spec'] + uc = svc_info['Spec']['UpdateConfig'] + assert update_config['FailureAction'] == uc['FailureAction'] + @requires_api_version('1.25') def test_create_service_with_update_config_monitor(self): container_spec = docker.types.ContainerSpec('busybox', ['true']) From 91e9258659410f54e6f4bfa621cf5ebc854e0e41 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 9 Aug 2018 16:41:25 -0700 Subject: [PATCH 10/11] Add support for RollbackConfig Signed-off-by: Joffrey F --- docker/api/service.py | 34 +++++++++++++++++++++++---- docker/models/services.py | 2 ++ docker/types/__init__.py | 4 ++-- docker/types/services.py | 24 +++++++++++++++++++ tests/integration/api_service_test.py | 21 +++++++++++++++++ 5 files changed, 78 insertions(+), 7 deletions(-) diff --git a/docker/api/service.py b/docker/api/service.py index 1dbe2697f1..8b956b63e1 100644 --- a/docker/api/service.py +++ b/docker/api/service.py @@ -2,7 +2,8 @@ from ..types import ServiceMode -def _check_api_features(version, task_template, update_config, endpoint_spec): +def _check_api_features(version, task_template, update_config, endpoint_spec, + rollback_config): def raise_version_error(param, min_version): raise errors.InvalidVersion( @@ -28,6 +29,14 @@ def raise_version_error(param, min_version): if 'Order' in update_config: raise_version_error('UpdateConfig.order', '1.29') + if rollback_config is not None: + if utils.version_lt(version, '1.28'): + raise_version_error('rollback_config', '1.28') + + if utils.version_lt(version, '1.29'): + if 'Order' in update_config: + raise_version_error('RollbackConfig.order', '1.29') + if endpoint_spec is not None: if utils.version_lt(version, '1.32') and 'Ports' in endpoint_spec: if any(p.get('PublishMode') for p in endpoint_spec['Ports']): @@ -105,7 +114,7 @@ class ServiceApiMixin(object): def create_service( self, task_template, name=None, labels=None, mode=None, update_config=None, networks=None, endpoint_config=None, - endpoint_spec=None + endpoint_spec=None, rollback_config=None ): """ Create a service. @@ -120,6 +129,8 @@ def create_service( or global). Defaults to replicated. update_config (UpdateConfig): Specification for the update strategy of the service. Default: ``None`` + rollback_config (RollbackConfig): Specification for the rollback + strategy of the service. Default: ``None`` networks (:py:class:`list`): List of network names or IDs to attach the service to. Default: ``None``. endpoint_spec (EndpointSpec): Properties that can be configured to @@ -135,7 +146,8 @@ def create_service( """ _check_api_features( - self._version, task_template, update_config, endpoint_spec + self._version, task_template, update_config, endpoint_spec, + rollback_config ) url = self._url('/services/create') @@ -166,6 +178,9 @@ def create_service( if update_config is not None: data['UpdateConfig'] = update_config + if rollback_config is not None: + data['RollbackConfig'] = rollback_config + return self._result( self._post_json(url, data=data, headers=headers), True ) @@ -342,7 +357,8 @@ def tasks(self, filters=None): def update_service(self, service, version, task_template=None, name=None, labels=None, mode=None, update_config=None, networks=None, endpoint_config=None, - endpoint_spec=None, fetch_current_spec=False): + endpoint_spec=None, fetch_current_spec=False, + rollback_config=None): """ Update a service. @@ -360,6 +376,8 @@ def update_service(self, service, version, task_template=None, name=None, or global). Defaults to replicated. update_config (UpdateConfig): Specification for the update strategy of the service. Default: ``None``. + rollback_config (RollbackConfig): Specification for the rollback + strategy of the service. Default: ``None`` networks (:py:class:`list`): List of network names or IDs to attach the service to. Default: ``None``. endpoint_spec (EndpointSpec): Properties that can be configured to @@ -376,7 +394,8 @@ def update_service(self, service, version, task_template=None, name=None, """ _check_api_features( - self._version, task_template, update_config, endpoint_spec + self._version, task_template, update_config, endpoint_spec, + rollback_config ) if fetch_current_spec: @@ -422,6 +441,11 @@ def update_service(self, service, version, task_template=None, name=None, else: data['UpdateConfig'] = current.get('UpdateConfig') + if rollback_config is not None: + data['RollbackConfig'] = rollback_config + else: + data['RollbackConfig'] = current.get('RollbackConfig') + if networks is not None: converted_networks = utils.convert_service_networks(networks) if utils.version_lt(self._version, '1.25'): diff --git a/docker/models/services.py b/docker/models/services.py index 7fbd165102..fa029f36ea 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -183,6 +183,8 @@ def create(self, image, command=None, **kwargs): containers to terminate before forcefully killing them. update_config (UpdateConfig): Specification for the update strategy of the service. Default: ``None`` + rollback_config (RollbackConfig): Specification for the rollback + strategy of the service. Default: ``None`` user (str): User to run commands as. workdir (str): Working directory for commands to run. tty (boolean): Whether a pseudo-TTY should be allocated. diff --git a/docker/types/__init__.py b/docker/types/__init__.py index 0b0d847fe9..64512333df 100644 --- a/docker/types/__init__.py +++ b/docker/types/__init__.py @@ -5,7 +5,7 @@ from .networks import EndpointConfig, IPAMConfig, IPAMPool, NetworkingConfig from .services import ( ConfigReference, ContainerSpec, DNSConfig, DriverConfig, EndpointSpec, - Mount, Placement, Privileges, Resources, RestartPolicy, SecretReference, - ServiceMode, TaskTemplate, UpdateConfig + Mount, Placement, Privileges, Resources, RestartPolicy, RollbackConfig, + SecretReference, ServiceMode, TaskTemplate, UpdateConfig ) from .swarm import SwarmSpec, SwarmExternalCA diff --git a/docker/types/services.py b/docker/types/services.py index a883f3ff7d..c66d41a167 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -414,6 +414,30 @@ def __init__(self, parallelism=0, delay=None, failure_action='continue', self['Order'] = order +class RollbackConfig(UpdateConfig): + """ + Used to specify the way containe rollbacks should be performed by a service + + Args: + parallelism (int): Maximum number of tasks to be rolled back in one + iteration (0 means unlimited parallelism). Default: 0 + delay (int): Amount of time between rollbacks, in nanoseconds. + failure_action (string): Action to take if a rolled back task fails to + run, or stops running during the rollback. Acceptable values are + ``continue``, ``pause`` or ``rollback``. + Default: ``continue`` + monitor (int): Amount of time to monitor each rolled back task for + failures, in nanoseconds. + max_failure_ratio (float): The fraction of tasks that may fail during + a rollback before the failure action is invoked, specified as a + floating point number between 0 and 1. Default: 0 + order (string): Specifies the order of operations when rolling out a + rolled back task. Either ``start_first`` or ``stop_first`` are + accepted. + """ + pass + + class RestartConditionTypesEnum(object): _values = ( 'none', diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index ba2ed91f72..a53ca1c836 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -312,6 +312,27 @@ def test_create_service_with_update_config_monitor(self): assert update_config['Monitor'] == uc['Monitor'] assert update_config['MaxFailureRatio'] == uc['MaxFailureRatio'] + @requires_api_version('1.28') + def test_create_service_with_rollback_config(self): + container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + task_tmpl = docker.types.TaskTemplate(container_spec) + rollback_cfg = docker.types.RollbackConfig( + parallelism=10, delay=5, failure_action='pause', + monitor=300000000, max_failure_ratio=0.4 + ) + name = self.get_service_name() + svc_id = self.client.create_service( + task_tmpl, rollback_config=rollback_cfg, name=name + ) + svc_info = self.client.inspect_service(svc_id) + assert 'RollbackConfig' in svc_info['Spec'] + rc = svc_info['Spec']['RollbackConfig'] + assert rollback_cfg['Parallelism'] == rc['Parallelism'] + assert rollback_cfg['Delay'] == rc['Delay'] + assert rollback_cfg['FailureAction'] == rc['FailureAction'] + assert rollback_cfg['Monitor'] == rc['Monitor'] + assert rollback_cfg['MaxFailureRatio'] == rc['MaxFailureRatio'] + def test_create_service_with_restart_policy(self): container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) policy = docker.types.RestartPolicy( From 05fa0be8ef5541462b59da9658c617c48bd60924 Mon Sep 17 00:00:00 2001 From: Joffrey F Date: Thu, 9 Aug 2018 17:10:07 -0700 Subject: [PATCH 11/11] 3.5.0 release Signed-off-by: Joffrey F --- docker/version.py | 2 +- docs/change-log.md | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docker/version.py b/docker/version.py index d45137440c..022daffdd2 100644 --- a/docker/version.py +++ b/docker/version.py @@ -1,2 +1,2 @@ -version = "3.4.1" +version = "3.5.0" version_info = tuple([int(d) for d in version.split("-")[0].split(".")]) diff --git a/docs/change-log.md b/docs/change-log.md index 2bd11a743f..1b2d620fb8 100644 --- a/docs/change-log.md +++ b/docs/change-log.md @@ -1,6 +1,33 @@ Change log ========== +3.5.0 +----- + +[List of PRs / issues for this release](https://github.com/docker/docker-py/milestone=53?closed=1) + +### Deprecation warning + +* Support for Python 3.3 will be dropped in the 4.0.0 release + +### Features + +* Updated dependencies to ensure support for Python 3.7 environments +* Added support for the `uts_mode` parameter in `HostConfig` +* The `UpdateConfig` constructor now allows `rollback` as a valid + value for `failure_action` +* Added support for `rollback_config` in `APIClient.create_service`, + `APIClient.update_service`, `DockerClient.services.create` and + `Service.update`. + +### Bugfixes + +* Credential helpers are now properly leveraged by the `build` method +* Fixed a bug that caused placement preferences to be ignored when provided + to `DockerClient.services.create` +* Fixed a bug that caused a `user` value of `0` to be ignored in + `APIClient.create_container` and `DockerClient.containers.create` + 3.4.1 -----