From f53e615e0fd4b7becf9d72c73b8a9e021d473f62 Mon Sep 17 00:00:00 2001 From: Felipe Ruhland Date: Wed, 7 Apr 2021 21:44:24 +0200 Subject: [PATCH 1/2] Update API and Engine versions The Makefile and `docker/constants.py` were with old versions, so I updated them to the current one Signed-off-by: Felipe Ruhland --- Makefile | 4 ++-- docker/constants.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 60d99842c..78a0d334e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ -TEST_API_VERSION ?= 1.39 -TEST_ENGINE_VERSION ?= 19.03.13 +TEST_API_VERSION ?= 1.41 +TEST_ENGINE_VERSION ?= 20.10.05 .PHONY: all all: test diff --git a/docker/constants.py b/docker/constants.py index 43fce6138..9cd58b675 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -1,7 +1,7 @@ import sys from .version import version -DEFAULT_DOCKER_API_VERSION = '1.39' +DEFAULT_DOCKER_API_VERSION = '1.41' MINIMUM_DOCKER_API_VERSION = '1.21' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 From 7ac8b56730c70e3b61ad628e7512082a4468e4f3 Mon Sep 17 00:00:00 2001 From: Felipe Ruhland Date: Wed, 7 Apr 2021 22:11:52 +0200 Subject: [PATCH 2/2] Add `CapabilityAdd` and `CapabilityDrop` to ContainerSpec Docker Engine v1.41 added `CapAdd` and `CapDrop` as part of the ContainerSpec, and `docker-py` should do the same. ``` GET /services now returns CapAdd and CapDrop as part of the ContainerSpec. GET /services/{id} now returns CapAdd and CapDrop as part of the ContainerSpec. POST /services/create now accepts CapAdd and CapDrop as part of the ContainerSpec. POST /services/{id}/update now accepts CapAdd and CapDrop as part of the ContainerSpec. GET /tasks now returns CapAdd and CapDrop as part of the ContainerSpec. GET /tasks/{id} now returns CapAdd and CapDrop as part of the ContainerSpec. ``` I added capabilities on docstrings, `service.create` init method and create tests for that. That change was mention in issue #2802. Signed-off-by: Felipe Ruhland --- docker/models/services.py | 6 ++++ docker/types/services.py | 19 +++++++++++- tests/integration/api_service_test.py | 30 ++++++++++++++++++ tests/integration/models_services_test.py | 38 +++++++++++++++++++++++ 4 files changed, 92 insertions(+), 1 deletion(-) diff --git a/docker/models/services.py b/docker/models/services.py index a29ff1326..200dd333c 100644 --- a/docker/models/services.py +++ b/docker/models/services.py @@ -213,6 +213,10 @@ def create(self, image, command=None, **kwargs): to the service. privileges (Privileges): Security options for the service's containers. + cap_add (:py:class:`list`): A list of kernel capabilities to add to + the default set for the container. + cap_drop (:py:class:`list`): A list of kernel capabilities to drop + from the default set for the container. Returns: :py:class:`Service`: The created service. @@ -277,6 +281,8 @@ def list(self, **kwargs): # kwargs to copy straight over to ContainerSpec CONTAINER_SPEC_KWARGS = [ 'args', + 'cap_add', + 'cap_drop', 'command', 'configs', 'dns_config', diff --git a/docker/types/services.py b/docker/types/services.py index 29498e971..8e87c7b43 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -112,13 +112,18 @@ class ContainerSpec(dict): containers. Only used for Windows containers. init (boolean): Run an init inside the container that forwards signals and reaps processes. + cap_add (:py:class:`list`): A list of kernel capabilities to add to the + default set for the container. + cap_drop (:py:class:`list`): A list of kernel capabilities to drop from + the default set for the container. """ def __init__(self, image, command=None, args=None, hostname=None, env=None, workdir=None, user=None, labels=None, mounts=None, stop_grace_period=None, secrets=None, tty=None, groups=None, open_stdin=None, read_only=None, stop_signal=None, healthcheck=None, hosts=None, dns_config=None, configs=None, - privileges=None, isolation=None, init=None): + privileges=None, isolation=None, init=None, cap_add=None, + cap_drop=None): self['Image'] = image if isinstance(command, six.string_types): @@ -188,6 +193,18 @@ def __init__(self, image, command=None, args=None, hostname=None, env=None, if init is not None: self['Init'] = init + if cap_add is not None: + if not isinstance(cap_add, list): + raise TypeError('cap_add must be a list') + + self['CapabilityAdd'] = cap_add + + if cap_drop is not None: + if not isinstance(cap_drop, list): + raise TypeError('cap_drop must be a list') + + self['CapabilityDrop'] = cap_drop + class Mount(dict): """ diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index 1bee46e56..57077e629 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -1358,3 +1358,33 @@ def _update_service(self, svc_id, *args, **kwargs): self.client.update_service(*args, **kwargs) else: raise + + @requires_api_version('1.41') + def test_create_service_cap_add(self): + name = self.get_service_name() + container_spec = docker.types.ContainerSpec( + TEST_IMG, ['echo', 'hello'], cap_add=['CAP_SYSLOG'] + ) + task_tmpl = docker.types.TaskTemplate(container_spec) + svc_id = self.client.create_service(task_tmpl, name=name) + assert self.client.inspect_service(svc_id) + services = self.client.services(filters={'name': name}) + assert len(services) == 1 + assert services[0]['ID'] == svc_id['ID'] + spec = services[0]['Spec']['TaskTemplate']['ContainerSpec'] + assert 'CAP_SYSLOG' in spec['CapabilityAdd'] + + @requires_api_version('1.41') + def test_create_service_cap_drop(self): + name = self.get_service_name() + container_spec = docker.types.ContainerSpec( + TEST_IMG, ['echo', 'hello'], cap_drop=['CAP_SYSLOG'] + ) + task_tmpl = docker.types.TaskTemplate(container_spec) + svc_id = self.client.create_service(task_tmpl, name=name) + assert self.client.inspect_service(svc_id) + services = self.client.services(filters={'name': name}) + assert len(services) == 1 + assert services[0]['ID'] == svc_id['ID'] + spec = services[0]['Spec']['TaskTemplate']['ContainerSpec'] + assert 'CAP_SYSLOG' in spec['CapabilityDrop'] diff --git a/tests/integration/models_services_test.py b/tests/integration/models_services_test.py index 36caa8513..982842b32 100644 --- a/tests/integration/models_services_test.py +++ b/tests/integration/models_services_test.py @@ -333,3 +333,41 @@ def test_force_update_service_using_shorthand_method(self): assert service.force_update() service.reload() assert service.version > initial_version + + @helpers.requires_api_version('1.41') + def test_create_cap_add(self): + client = docker.from_env(version=TEST_API_VERSION) + name = helpers.random_name() + service = client.services.create( + name=name, + labels={'foo': 'bar'}, + image="alpine", + command="sleep 300", + container_labels={'container': 'label'}, + cap_add=["CAP_SYSLOG"] + ) + assert service.name == name + assert service.attrs['Spec']['Labels']['foo'] == 'bar' + container_spec = service.attrs['Spec']['TaskTemplate']['ContainerSpec'] + assert "alpine" in container_spec['Image'] + assert container_spec['Labels'] == {'container': 'label'} + assert "CAP_SYSLOG" in container_spec["CapabilityAdd"] + + @helpers.requires_api_version('1.41') + def test_create_cap_drop(self): + client = docker.from_env(version=TEST_API_VERSION) + name = helpers.random_name() + service = client.services.create( + name=name, + labels={'foo': 'bar'}, + image="alpine", + command="sleep 300", + container_labels={'container': 'label'}, + cap_drop=["CAP_SYSLOG"] + ) + assert service.name == name + assert service.attrs['Spec']['Labels']['foo'] == 'bar' + container_spec = service.attrs['Spec']['TaskTemplate']['ContainerSpec'] + assert "alpine" in container_spec['Image'] + assert container_spec['Labels'] == {'container': 'label'} + assert "CAP_SYSLOG" in container_spec["CapabilityDrop"]