diff --git a/docker/client.py b/docker/client.py index 532f035b..cd275d42 100644 --- a/docker/client.py +++ b/docker/client.py @@ -39,16 +39,9 @@ class Client(requests.Session): def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, timeout=DEFAULT_TIMEOUT_SECONDS): super(Client, self).__init__() - if base_url is None: - base_url = "http+unix://var/run/docker.sock" - if 'unix:///' in base_url: + base_url = utils.parse_host(base_url) + if 'http+unix:///' in base_url: base_url = base_url.replace('unix:/', 'unix:') - if base_url.startswith('unix:'): - base_url = "http+" + base_url - if base_url.startswith('tcp:'): - base_url = base_url.replace('tcp:', 'http:') - if base_url.endswith('/'): - base_url = base_url[:-1] self.base_url = base_url self._version = version self._timeout = timeout diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py index 86ddd35b..5d2c1b87 100644 --- a/docker/utils/__init__.py +++ b/docker/utils/__init__.py @@ -1,4 +1,4 @@ from .utils import ( compare_version, convert_port_bindings, convert_volume_binds, - mkbuildcontext, ping, tar, parse_repository_tag + mkbuildcontext, ping, tar, parse_repository_tag, parse_host ) # flake8: noqa diff --git a/docker/utils/utils.py b/docker/utils/utils.py index 036a504b..f741ee27 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -20,6 +20,11 @@ from distutils.version import StrictVersion import requests import six +from .. import errors + +DEFAULT_HTTP_HOST = "127.0.0.1" +DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock" + def mkbuildcontext(dockerfile): f = tempfile.NamedTemporaryFile() @@ -139,9 +144,71 @@ def parse_repository_tag(repo): column_index = repo.rfind(':') if column_index < 0: return repo, None - tag = repo[column_index+1:] + tag = repo[column_index + 1:] slash_index = tag.find('/') if slash_index < 0: return repo[:column_index], tag return repo, None + + +# Based on utils.go:ParseHost http://tinyurl.com/nkahcfh +# fd:// protocol unsupported (for obvious reasons) +# Added support for http and https +# Protocol translation: tcp -> http, unix -> http+unix +def parse_host(addr): + proto = "http+unix" + host = DEFAULT_HTTP_HOST + port = None + if not addr or addr.strip() == 'unix://': + return DEFAULT_UNIX_SOCKET + + addr = addr.strip() + if addr.startswith('http://'): + addr = addr.replace('http://', 'tcp://') + if addr.startswith('http+unix://'): + addr = addr.replace('http+unix://', 'unix://') + + if addr == 'tcp://': + raise errors.DockerException("Invalid bind address format: %s" % addr) + elif addr.startswith('unix://'): + addr = addr[7:] + elif addr.startswith('tcp://'): + proto = "http" + addr = addr[6:] + elif addr.startswith('https://'): + proto = "https" + addr = addr[8:] + elif addr.startswith('fd://'): + raise errors.DockerException("fd protocol is not implemented") + else: + if "://" in addr: + raise errors.DockerException( + "Invalid bind address protocol: %s" % addr + ) + proto = "http" + + if proto != "http+unix" and ":" in addr: + host_parts = addr.split(':') + if len(host_parts) != 2: + raise errors.DockerException( + "Invalid bind address format: %s" % addr + ) + if host_parts[0]: + host = host_parts[0] + + try: + port = int(host_parts[1]) + except Exception: + raise errors.DockerException( + "Invalid port: %s", addr + ) + + elif proto in ("http", "https") and ':' not in addr: + raise errors.DockerException("Bind address needs a port: %s" % addr) + else: + host = addr + + if proto == "http+unix": + return "%s://%s" % (proto, host) + return "%s://%s:%d" % (proto, host, port) diff --git a/tests/test.py b/tests/test.py index 7c3fdee3..adc7a59a 100644 --- a/tests/test.py +++ b/tests/test.py @@ -746,14 +746,14 @@ class DockerClientTest(unittest.TestCase): assert c.base_url == "http+unix://socket" def test_url_compatibility_http(self): - c = docker.Client(base_url="http://hostname") + c = docker.Client(base_url="http://hostname:1234") - assert c.base_url == "http://hostname" + assert c.base_url == "http://hostname:1234" def test_url_compatibility_tcp(self): - c = docker.Client(base_url="tcp://hostname") + c = docker.Client(base_url="tcp://hostname:1234") - assert c.base_url == "http://hostname" + assert c.base_url == "http://hostname:1234" def test_logs(self): try: diff --git a/tests/utils_test.py b/tests/utils_test.py index 809336bc..277781b8 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -1,9 +1,11 @@ import unittest -from docker.utils import parse_repository_tag +from docker.errors import DockerException +from docker.utils import parse_repository_tag, parse_host class UtilsTest(unittest.TestCase): + longMessage = True def test_parse_repository_tag(self): self.assertEqual(parse_repository_tag("root"), @@ -19,6 +21,37 @@ class UtilsTest(unittest.TestCase): self.assertEqual(parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag")) + def test_parse_host(self): + invalid_hosts = [ + '0.0.0.0', + 'tcp://', + 'udp://127.0.0.1', + 'udp://127.0.0.1:2375', + ] + + valid_hosts = { + '0.0.0.1:5555': 'http://0.0.0.1:5555', + ':6666': 'http://127.0.0.1:6666', + 'tcp://:7777': 'http://127.0.0.1:7777', + 'http://:7777': 'http://127.0.0.1:7777', + 'https://kokia.jp:2375': 'https://kokia.jp:2375', + '': 'http+unix://var/run/docker.sock', + None: 'http+unix://var/run/docker.sock', + 'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock', + 'unix://': 'http+unix://var/run/docker.sock' + } + + for host in invalid_hosts: + try: + parsed = parse_host(host) + self.fail('Expected to fail but success: %s -> %s' % ( + host, parsed + )) + except DockerException: + pass + + for host, expected in valid_hosts.items(): + self.assertEqual(parse_host(host), expected, msg=host) if __name__ == '__main__': unittest.main()