From fb81c37ca643876dff1e3f9a4feee5882a32cc1e Mon Sep 17 00:00:00 2001 From: Sam Wing Date: Thu, 15 Jan 2015 12:58:17 -0800 Subject: [PATCH 1/4] added the extra_hosts option to the yml configuration which exposes the --add-host flag from the docker client Signed-off-by: Sam Wing --- compose/config.py | 2 ++ compose/service.py | 14 ++++++++++++++ docs/yml.md | 17 +++++++++++++++++ tests/integration/service_test.py | 14 ++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/compose/config.py b/compose/config.py index f87da1d8c..87d610c82 100644 --- a/compose/config.py +++ b/compose/config.py @@ -15,6 +15,7 @@ DOCKER_CONFIG_KEYS = [ 'entrypoint', 'env_file', 'environment', + 'extra_hosts', 'hostname', 'image', 'links', @@ -41,6 +42,7 @@ ALLOWED_KEYS = DOCKER_CONFIG_KEYS + [ DOCKER_CONFIG_HINTS = { 'cpu_share': 'cpu_shares', + 'add_host': 'extra_hosts', 'link': 'links', 'port': 'ports', 'privilege': 'privileged', diff --git a/compose/service.py b/compose/service.py index 5afaa30fa..dfc7a71f0 100644 --- a/compose/service.py +++ b/compose/service.py @@ -23,6 +23,7 @@ DOCKER_START_KEYS = [ 'dns', 'dns_search', 'env_file', + 'extra_hosts', 'net', 'pid', 'privileged', @@ -448,6 +449,8 @@ class Service(object): restart = parse_restart_spec(options.get('restart', None)) + extra_hosts = build_extra_hosts(options.get('extra_hosts', None)) + return create_host_config( links=self._get_links(link_to_self=one_off), port_bindings=port_bindings, @@ -460,6 +463,7 @@ class Service(object): restart_policy=restart, cap_add=cap_add, cap_drop=cap_drop, + extra_hosts=extra_hosts, pid_mode=pid ) @@ -619,3 +623,13 @@ def split_port(port): external_ip, external_port, internal_port = parts return internal_port, (external_ip, external_port or None) + + +def build_extra_hosts(extra_hosts_config): + if extra_hosts_config is None: + return None + + if isinstance(extra_hosts_config, list): + return dict(r.split(':') for r in extra_hosts_config) + else: + return dict([extra_hosts_config.split(':')]) diff --git a/docs/yml.md b/docs/yml.md index c375648df..8756b2020 100644 --- a/docs/yml.md +++ b/docs/yml.md @@ -87,6 +87,23 @@ external_links: - project_db_1:postgresql ``` +### extra_hosts + +Add hostname mappings. Use the same values as the docker client `--add-hosts` parameter. + +``` +extra_hosts: + - docker: 162.242.195.82 + - fig: 50.31.209.229 +``` + +An entry with the ip address and hostname will be created in `/etc/hosts` inside containers for this service, e.g: + +``` +162.242.195.82 docker +50.31.209.229 fig +``` + ### ports Expose ports. Either specify both ports (`HOST:CONTAINER`), or just the container diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 4abd4a909..5bc877d03 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -107,6 +107,20 @@ class ServiceTest(DockerClientTestCase): service.start_container(container) self.assertEqual(container.inspect()['Config']['CpuShares'], 73) + def test_create_container_with_extra_hosts_list(self): + extra_hosts = ['docker:162.242.195.82', 'fig:50.31.209.229'] + service = self.create_service('db', extra_hosts=extra_hosts) + container = service.create_container() + service.start_container(container) + self.assertEqual(container.get('HostConfig.ExtraHosts'), extra_hosts) + + def test_create_container_with_extra_hosts_string(self): + extra_hosts = 'docker:162.242.195.82' + service = self.create_service('db', extra_hosts=extra_hosts) + container = service.create_container() + service.start_container(container) + self.assertEqual(container.get('HostConfig.ExtraHosts'), [extra_hosts]) + def test_create_container_with_specified_volume(self): host_path = '/tmp/host-path' container_path = '/container-path' From 8098b65576b5c8e69705d533580c1d37a09632ad Mon Sep 17 00:00:00 2001 From: Thomas Desvenain Date: Wed, 21 Jan 2015 20:33:51 +0100 Subject: [PATCH 2/4] Fix when pyyaml has interpreted line as a dictionary Added unit tests in build_extra_hosts + fix Signed-off-by: CJ --- compose/service.py | 18 ++++++++++++++---- tests/integration/service_test.py | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 4 deletions(-) diff --git a/compose/service.py b/compose/service.py index dfc7a71f0..f2dbd746e 100644 --- a/compose/service.py +++ b/compose/service.py @@ -629,7 +629,17 @@ def build_extra_hosts(extra_hosts_config): if extra_hosts_config is None: return None - if isinstance(extra_hosts_config, list): - return dict(r.split(':') for r in extra_hosts_config) - else: - return dict([extra_hosts_config.split(':')]) + if isinstance(extra_hosts_config, basestring): + extra_hosts_config = [extra_hosts_config] + + extra_hosts_dict = {} + for extra_hosts_line in extra_hosts_config: + if isinstance(extra_hosts_line, dict): + # already interpreted as a dict (depends on pyyaml version) + extra_hosts_dict.update(extra_hosts_line) + else: + # not already interpreted as a dict + host, ip = extra_hosts_line.split(':') + extra_hosts_dict.update({host.strip(): ip.strip()}) + + return extra_hosts_dict diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index 5bc877d03..f71d609da 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -6,6 +6,7 @@ import mock from compose import Service from compose.service import CannotBeScaledError +from compose.service import build_extra_hosts from compose.container import Container from docker.errors import APIError from .testcases import DockerClientTestCase @@ -107,6 +108,28 @@ class ServiceTest(DockerClientTestCase): service.start_container(container) self.assertEqual(container.inspect()['Config']['CpuShares'], 73) + def test_build_extra_hosts(self): + # string + self.assertEqual(build_extra_hosts("www.example.com: 192.168.0.17"), + {'www.example.com': '192.168.0.17'}) + + # list of strings + self.assertEqual(build_extra_hosts( + ["www.example.com: 192.168.0.17"]), + {'www.example.com': '192.168.0.17'}) + self.assertEqual(build_extra_hosts( + ["www.example.com: 192.168.0.17", + "api.example.com: 192.168.0.18"]), + {'www.example.com': '192.168.0.17', + 'api.example.com': '192.168.0.18'}) + # list of dictionaries + self.assertEqual(build_extra_hosts( + [{'www.example.com': '192.168.0.17'}, + {'api.example.com': '192.168.0.18'} + ]), + {'www.example.com': '192.168.0.17', + 'api.example.com': '192.168.0.18'}) + def test_create_container_with_extra_hosts_list(self): extra_hosts = ['docker:162.242.195.82', 'fig:50.31.209.229'] service = self.create_service('db', extra_hosts=extra_hosts) From 25ee3f0033f349df9692cac943065b32be1fa4e2 Mon Sep 17 00:00:00 2001 From: CJ Date: Tue, 24 Mar 2015 18:25:09 +0800 Subject: [PATCH 3/4] Remove extra s from --add-host linting... six.string_types list-of-strings in examples disallow extra_hosts support for list-of-dicts A more thorough sets of tests for extra_hosts Provide better examples As per @aanand's [comment](https://github.com/docker/compose/pull/1158/files#r28326312) I think it'd be better to check `if not isinstance(extra_hosts_line, six.string_types)` and raise an error saying `extra_hosts_config must be either a list of strings or a string->string mapping`. We shouldn't need to do anything special with the list-of-dicts case. order result to work with assert use set() instead of sort() Signed-off-by: CJ --- compose/service.py | 31 +++++++++------- docs/yml.md | 10 ++--- tests/integration/service_test.py | 61 +++++++++++++++++++++---------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/compose/service.py b/compose/service.py index f2dbd746e..e668dc493 100644 --- a/compose/service.py +++ b/compose/service.py @@ -626,20 +626,25 @@ def split_port(port): def build_extra_hosts(extra_hosts_config): - if extra_hosts_config is None: - return None + if not extra_hosts_config: + return {} - if isinstance(extra_hosts_config, basestring): - extra_hosts_config = [extra_hosts_config] - - extra_hosts_dict = {} - for extra_hosts_line in extra_hosts_config: - if isinstance(extra_hosts_line, dict): - # already interpreted as a dict (depends on pyyaml version) - extra_hosts_dict.update(extra_hosts_line) - else: - # not already interpreted as a dict + if isinstance(extra_hosts_config, list): + extra_hosts_dict = {} + for extra_hosts_line in extra_hosts_config: + if not isinstance(extra_hosts_line, six.string_types): + raise ConfigError( + "extra_hosts_config \"%s\" must be either a list of strings or a string->string mapping," % + extra_hosts_config + ) host, ip = extra_hosts_line.split(':') extra_hosts_dict.update({host.strip(): ip.strip()}) + extra_hosts_config = extra_hosts_dict - return extra_hosts_dict + if isinstance(extra_hosts_config, dict): + return extra_hosts_config + + raise ConfigError( + "extra_hosts_config \"%s\" must be either a list of strings or a string->string mapping," % + extra_hosts_config + ) diff --git a/docs/yml.md b/docs/yml.md index 8756b2020..82aeed128 100644 --- a/docs/yml.md +++ b/docs/yml.md @@ -89,19 +89,19 @@ external_links: ### extra_hosts -Add hostname mappings. Use the same values as the docker client `--add-hosts` parameter. +Add hostname mappings. Use the same values as the docker client `--add-host` parameter. ``` extra_hosts: - - docker: 162.242.195.82 - - fig: 50.31.209.229 + - "somehost:162.242.195.82" + - "otherhost:50.31.209.229" ``` An entry with the ip address and hostname will be created in `/etc/hosts` inside containers for this service, e.g: ``` -162.242.195.82 docker -50.31.209.229 fig +162.242.195.82 somehost +50.31.209.229 otherhost ``` ### ports diff --git a/tests/integration/service_test.py b/tests/integration/service_test.py index f71d609da..3fbf546c0 100644 --- a/tests/integration/service_test.py +++ b/tests/integration/service_test.py @@ -5,8 +5,11 @@ from os import path import mock from compose import Service -from compose.service import CannotBeScaledError -from compose.service import build_extra_hosts +from compose.service import ( + CannotBeScaledError, + build_extra_hosts, + ConfigError, +) from compose.container import Container from docker.errors import APIError from .testcases import DockerClientTestCase @@ -110,39 +113,59 @@ class ServiceTest(DockerClientTestCase): def test_build_extra_hosts(self): # string - self.assertEqual(build_extra_hosts("www.example.com: 192.168.0.17"), - {'www.example.com': '192.168.0.17'}) + self.assertRaises(ConfigError, lambda: build_extra_hosts("www.example.com: 192.168.0.17")) # list of strings self.assertEqual(build_extra_hosts( - ["www.example.com: 192.168.0.17"]), - {'www.example.com': '192.168.0.17'}) + ["www.example.com:192.168.0.17"]), + {'www.example.com': '192.168.0.17'}) self.assertEqual(build_extra_hosts( - ["www.example.com: 192.168.0.17", - "api.example.com: 192.168.0.18"]), - {'www.example.com': '192.168.0.17', - 'api.example.com': '192.168.0.18'}) + ["www.example.com: 192.168.0.17"]), + {'www.example.com': '192.168.0.17'}) + self.assertEqual(build_extra_hosts( + ["www.example.com: 192.168.0.17", + "static.example.com:192.168.0.19", + "api.example.com: 192.168.0.18"]), + {'www.example.com': '192.168.0.17', + 'static.example.com': '192.168.0.19', + 'api.example.com': '192.168.0.18'}) + # list of dictionaries + self.assertRaises(ConfigError, lambda: build_extra_hosts( + [{'www.example.com': '192.168.0.17'}, + {'api.example.com': '192.168.0.18'}])) + + # dictionaries self.assertEqual(build_extra_hosts( - [{'www.example.com': '192.168.0.17'}, - {'api.example.com': '192.168.0.18'} - ]), - {'www.example.com': '192.168.0.17', - 'api.example.com': '192.168.0.18'}) + {'www.example.com': '192.168.0.17', + 'api.example.com': '192.168.0.18'}), + {'www.example.com': '192.168.0.17', + 'api.example.com': '192.168.0.18'}) def test_create_container_with_extra_hosts_list(self): - extra_hosts = ['docker:162.242.195.82', 'fig:50.31.209.229'] + extra_hosts = ['somehost:162.242.195.82', 'otherhost:50.31.209.229'] service = self.create_service('db', extra_hosts=extra_hosts) container = service.create_container() service.start_container(container) - self.assertEqual(container.get('HostConfig.ExtraHosts'), extra_hosts) + self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts)) def test_create_container_with_extra_hosts_string(self): - extra_hosts = 'docker:162.242.195.82' + extra_hosts = 'somehost:162.242.195.82' + service = self.create_service('db', extra_hosts=extra_hosts) + self.assertRaises(ConfigError, lambda: service.create_container()) + + def test_create_container_with_extra_hosts_list_of_dicts(self): + extra_hosts = [{'somehost': '162.242.195.82'}, {'otherhost': '50.31.209.229'}] + service = self.create_service('db', extra_hosts=extra_hosts) + self.assertRaises(ConfigError, lambda: service.create_container()) + + def test_create_container_with_extra_hosts_dicts(self): + extra_hosts = {'somehost': '162.242.195.82', 'otherhost': '50.31.209.229'} + extra_hosts_list = ['somehost:162.242.195.82', 'otherhost:50.31.209.229'] service = self.create_service('db', extra_hosts=extra_hosts) container = service.create_container() service.start_container(container) - self.assertEqual(container.get('HostConfig.ExtraHosts'), [extra_hosts]) + self.assertEqual(set(container.get('HostConfig.ExtraHosts')), set(extra_hosts_list)) def test_create_container_with_specified_volume(self): host_path = '/tmp/host-path' From 86a08c00f247131bfebcf750f37866eb0fcf9458 Mon Sep 17 00:00:00 2001 From: CJ Date: Mon, 27 Apr 2015 14:07:21 +0800 Subject: [PATCH 4/4] See https://github.com/docker/compose/pull/1158#discussion_r29063218 Signed-off-by: CJ --- compose/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compose/config.py b/compose/config.py index 87d610c82..95948ef8a 100644 --- a/compose/config.py +++ b/compose/config.py @@ -43,6 +43,8 @@ ALLOWED_KEYS = DOCKER_CONFIG_KEYS + [ DOCKER_CONFIG_HINTS = { 'cpu_share': 'cpu_shares', 'add_host': 'extra_hosts', + 'hosts': 'extra_hosts', + 'extra_host': 'extra_hosts', 'link': 'links', 'port': 'ports', 'privilege': 'privileged',