diff --git a/docker/api/network.py b/docker/api/network.py index a6429f4c..3f9a161f 100644 --- a/docker/api/network.py +++ b/docker/api/network.py @@ -1,6 +1,6 @@ import json -from ..utils import check_resource, minimum_version +from ..utils import check_resource, minimum_version, normalize_links class NetworkApiMixin(object): @@ -47,11 +47,13 @@ class NetworkApiMixin(object): @check_resource @minimum_version('1.21') - def connect_container_to_network(self, container, net_id, aliases=None): + def connect_container_to_network(self, container, net_id, + aliases=None, links=None): data = { "Container": container, "EndpointConfig": { "Aliases": aliases, + "Links": normalize_links(links) if links else None, }, } url = self._url("/networks/{0}/connect", net_id) diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index 16b08d18..ccc38191 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -4,7 +4,7 @@ from .utils import ( kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config, create_container_config, parse_bytes, ping_registry, parse_env_file, version_lt, version_gte, decode_json_header, split_command, - create_ipam_config, create_ipam_pool, parse_devices + create_ipam_config, create_ipam_pool, parse_devices, normalize_links, ) # flake8: noqa from .types import Ulimit, LogConfig # flake8: noqa diff --git a/docker/utils/utils.py b/docker/utils/utils.py index ab80971f..1ce1867c 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -674,12 +674,7 @@ def create_host_config(binds=None, port_bindings=None, lxc_conf=None, host_config['ExtraHosts'] = extra_hosts if links is not None: - if isinstance(links, dict): - links = six.iteritems(links) - - formatted_links = ['{0}:{1}'.format(k, v) for k, v in sorted(links)] - - host_config['Links'] = formatted_links + host_config['Links'] = normalize_links(links) if isinstance(lxc_conf, dict): formatted = [] @@ -731,6 +726,13 @@ def create_host_config(binds=None, port_bindings=None, lxc_conf=None, return host_config +def normalize_links(links): + if isinstance(links, dict): + links = six.iteritems(links) + + return ['{0}:{1}'.format(k, v) for k, v in sorted(links)] + + def create_networking_config(endpoints_config=None): networking_config = {} @@ -740,7 +742,7 @@ def create_networking_config(endpoints_config=None): return networking_config -def create_endpoint_config(version, aliases=None): +def create_endpoint_config(version, aliases=None, links=None): endpoint_config = {} if aliases: @@ -748,6 +750,11 @@ def create_endpoint_config(version, aliases=None): raise host_config_version_error('endpoint_config.aliases', '1.22') endpoint_config["Aliases"] = aliases + if links: + if version_lt(version, '1.22'): + raise host_config_version_error('endpoint_config.links', '1.22') + endpoint_config["Links"] = normalize_links(links) + return endpoint_config diff --git a/tests/helpers.py b/tests/helpers.py index eca50351..21036ace 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -168,3 +168,18 @@ class BaseTestCase(unittest.TestCase): .format(exitcode, output)) return container + + def create_and_start(self, image='busybox', command='top', **kwargs): + container = self.client.create_container( + image=image, command=command, **kwargs) + self.tmp_containers.append(container) + self.client.start(container) + return container + + def execute(self, container, cmd, exit_code=0, **kwargs): + exc = self.client.exec_create(container, cmd, **kwargs) + output = self.client.exec_start(exc) + actual_exit_code = self.client.exec_inspect(exc)['ExitCode'] + msg = "Expected `{}` to exit with code {} but returned {}:\n{}".format( + " ".join(cmd), exit_code, actual_exit_code, output) + assert actual_exit_code == exit_code, msg diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py index a6ec0893..51cc53ad 100644 --- a/tests/integration/network_test.py +++ b/tests/integration/network_test.py @@ -180,3 +180,52 @@ class TestNetworks(helpers.BaseTestCase): self.assertEqual( container_data['NetworkSettings']['Networks'][net_name]['Aliases'], ['foo', 'bar']) + + @requires_api_version('1.22') + def test_create_with_links(self): + net_name, net_id = self.create_network() + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name), + networking_config=self.client.create_networking_config({ + net_name: self.client.create_endpoint_config( + links=[('docker-py-test-upstream', 'bar')], + ), + }), + ) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Links'], + ['docker-py-test-upstream:bar']) + + self.create_and_start( + name='docker-py-test-upstream', + host_config=self.client.create_host_config(network_mode=net_name), + ) + + self.execute(container, ['nslookup', 'bar']) + + @requires_api_version('1.22') + def test_connect_with_links(self): + net_name, net_id = self.create_network() + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name)) + + self.client.disconnect_container_from_network(container, net_name) + self.client.connect_container_to_network( + container, net_name, + links=[('docker-py-test-upstream', 'bar')]) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Links'], + ['docker-py-test-upstream:bar']) + + self.create_and_start( + name='docker-py-test-upstream', + host_config=self.client.create_host_config(network_mode=net_name), + ) + + self.execute(container, ['nslookup', 'bar']) diff --git a/tests/unit/network_test.py b/tests/unit/network_test.py index 492bcc7a..5bba9db2 100644 --- a/tests/unit/network_test.py +++ b/tests/unit/network_test.py @@ -150,6 +150,7 @@ class NetworkTest(DockerClientTest): {'Id': container_id}, network_id, aliases=['foo', 'bar'], + links=[('baz', 'quux')] ) self.assertEqual( @@ -162,6 +163,7 @@ class NetworkTest(DockerClientTest): 'Container': container_id, 'EndpointConfig': { 'Aliases': ['foo', 'bar'], + 'Links': ['baz:quux'], }, })