diff options
author | Joffrey F <joffrey@docker.com> | 2018-01-31 12:29:26 -0800 |
---|---|---|
committer | Joffrey F <joffrey@docker.com> | 2018-01-31 12:42:11 -0800 |
commit | 5347c168d022836bd19f63c5527c59169ae22f53 (patch) | |
tree | a89da7f120f21332ab645995d97de70fc594d056 | |
parent | 0750337f6a77bdfb46579184ac4950a212e5d048 (diff) | |
download | docker-py-1878-publishmode.tar.gz |
Add support for publish mode for endpointspec ports1878-publishmode
Signed-off-by: Joffrey F <joffrey@docker.com>
-rw-r--r-- | docker/api/service.py | 15 | ||||
-rw-r--r-- | docker/types/services.py | 14 | ||||
-rw-r--r-- | tests/integration/api_service_test.py | 21 | ||||
-rw-r--r-- | tests/unit/dockertypes_test.py | 75 |
4 files changed, 118 insertions, 7 deletions
diff --git a/docker/api/service.py b/docker/api/service.py index 4f7123e..051d34f 100644 --- a/docker/api/service.py +++ b/docker/api/service.py @@ -3,7 +3,7 @@ from .. import auth, errors, utils from ..types import ServiceMode -def _check_api_features(version, task_template, update_config): +def _check_api_features(version, task_template, update_config, endpoint_spec): def raise_version_error(param, min_version): raise errors.InvalidVersion( @@ -23,6 +23,11 @@ def _check_api_features(version, task_template, update_config): if 'Order' in update_config: raise_version_error('UpdateConfig.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']): + raise_version_error('EndpointSpec.Ports[].mode', '1.32') + if task_template is not None: if 'ForceUpdate' in task_template and utils.version_lt( version, '1.25'): @@ -125,7 +130,9 @@ class ServiceApiMixin(object): ) endpoint_spec = endpoint_config - _check_api_features(self._version, task_template, update_config) + _check_api_features( + self._version, task_template, update_config, endpoint_spec + ) url = self._url('/services/create') headers = {} @@ -370,7 +377,9 @@ class ServiceApiMixin(object): ) endpoint_spec = endpoint_config - _check_api_features(self._version, task_template, update_config) + _check_api_features( + self._version, task_template, update_config, endpoint_spec + ) if fetch_current_spec: inspect_defaults = True diff --git a/docker/types/services.py b/docker/types/services.py index ef1ca69..d530e61 100644 --- a/docker/types/services.py +++ b/docker/types/services.py @@ -450,8 +450,9 @@ class EndpointSpec(dict): ``'vip'`` if not provided. ports (dict): Exposed ports that this service is accessible on from the outside, in the form of ``{ published_port: target_port }`` or - ``{ published_port: (target_port, protocol) }``. Ports can only be - provided if the ``vip`` resolution mode is used. + ``{ published_port: <port_config_tuple> }``. Port config tuple format + is ``(target_port [, protocol [, publish_mode]])``. + Ports can only be provided if the ``vip`` resolution mode is used. """ def __init__(self, mode=None, ports=None): if ports: @@ -477,8 +478,15 @@ def convert_service_ports(ports): if isinstance(v, tuple): port_spec['TargetPort'] = v[0] - if len(v) == 2: + if len(v) >= 2 and v[1] is not None: port_spec['Protocol'] = v[1] + if len(v) == 3: + port_spec['PublishMode'] = v[2] + if len(v) > 3: + raise ValueError( + 'Service port configuration can have at most 3 elements: ' + '(target_port, protocol, mode)' + ) else: port_spec['TargetPort'] = v diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py index 7620cb4..5cc3fc1 100644 --- a/tests/integration/api_service_test.py +++ b/tests/integration/api_service_test.py @@ -353,7 +353,6 @@ class ServiceTest(BaseAPIIntegrationTest): task_tmpl, name=name, endpoint_spec=endpoint_spec ) svc_info = self.client.inspect_service(svc_id) - print(svc_info) ports = svc_info['Spec']['EndpointSpec']['Ports'] for port in ports: if port['PublishedPort'] == 12562: @@ -370,6 +369,26 @@ class ServiceTest(BaseAPIIntegrationTest): assert len(ports) == 3 + @requires_api_version('1.32') + def test_create_service_with_endpoint_spec_host_publish_mode(self): + container_spec = docker.types.ContainerSpec(BUSYBOX, ['true']) + task_tmpl = docker.types.TaskTemplate(container_spec) + name = self.get_service_name() + endpoint_spec = docker.types.EndpointSpec(ports={ + 12357: (1990, None, 'host'), + }) + svc_id = self.client.create_service( + task_tmpl, name=name, endpoint_spec=endpoint_spec + ) + svc_info = self.client.inspect_service(svc_id) + ports = svc_info['Spec']['EndpointSpec']['Ports'] + assert len(ports) == 1 + port = ports[0] + assert port['PublishedPort'] == 12357 + assert port['TargetPort'] == 1990 + assert port['Protocol'] == 'tcp' + assert port['PublishMode'] == 'host' + def test_create_service_with_env(self): container_spec = docker.types.ContainerSpec( BUSYBOX, ['true'], env={'DOCKER_PY_TEST': 1} diff --git a/tests/unit/dockertypes_test.py b/tests/unit/dockertypes_test.py index 93c1397..71dae7e 100644 --- a/tests/unit/dockertypes_test.py +++ b/tests/unit/dockertypes_test.py @@ -11,6 +11,7 @@ from docker.types import ( ContainerConfig, ContainerSpec, EndpointConfig, HostConfig, IPAMConfig, IPAMPool, LogConfig, Mount, ServiceMode, Ulimit, ) +from docker.types.services import convert_service_ports try: from unittest import mock @@ -423,3 +424,77 @@ class MountTest(unittest.TestCase): assert mount['Source'] == "C:/foo/bar" assert mount['Target'] == "/baz" assert mount['Type'] == 'bind' + + +class ServicePortsTest(unittest.TestCase): + def test_convert_service_ports_simple(self): + ports = {8080: 80} + assert convert_service_ports(ports) == [{ + 'Protocol': 'tcp', + 'PublishedPort': 8080, + 'TargetPort': 80, + }] + + def test_convert_service_ports_with_protocol(self): + ports = {8080: (80, 'udp')} + + assert convert_service_ports(ports) == [{ + 'Protocol': 'udp', + 'PublishedPort': 8080, + 'TargetPort': 80, + }] + + def test_convert_service_ports_with_protocol_and_mode(self): + ports = {8080: (80, 'udp', 'ingress')} + + assert convert_service_ports(ports) == [{ + 'Protocol': 'udp', + 'PublishedPort': 8080, + 'TargetPort': 80, + 'PublishMode': 'ingress', + }] + + def test_convert_service_ports_invalid(self): + ports = {8080: ('way', 'too', 'many', 'items', 'here')} + + with pytest.raises(ValueError): + convert_service_ports(ports) + + def test_convert_service_ports_no_protocol_and_mode(self): + ports = {8080: (80, None, 'host')} + + assert convert_service_ports(ports) == [{ + 'Protocol': 'tcp', + 'PublishedPort': 8080, + 'TargetPort': 80, + 'PublishMode': 'host', + }] + + def test_convert_service_ports_multiple(self): + ports = { + 8080: (80, None, 'host'), + 9999: 99, + 2375: (2375,) + } + + converted_ports = convert_service_ports(ports) + assert { + 'Protocol': 'tcp', + 'PublishedPort': 8080, + 'TargetPort': 80, + 'PublishMode': 'host', + } in converted_ports + + assert { + 'Protocol': 'tcp', + 'PublishedPort': 9999, + 'TargetPort': 99, + } in converted_ports + + assert { + 'Protocol': 'tcp', + 'PublishedPort': 2375, + 'TargetPort': 2375, + } in converted_ports + + assert len(converted_ports) == 3 |