Repository: docker/docker-py

Issue:
Support IPv6 addresses in DOCKER_HOST
Raised in https://github.com/docker/compose/issues/2879.

See https://github.com/docker/docker/pull/16950 for the Engine implementation.

Comments:

Patch:
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index 4d218692..1cfc8acc 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -22,8 +22,8 @@ import tarfile
 import tempfile
 import warnings
 from distutils.version import StrictVersion
-from fnmatch import fnmatch
 from datetime import datetime
+from fnmatch import fnmatch
 
 import requests
 import six
@@ -33,6 +33,10 @@ from .. import errors
 from .. import tls
 from .types import Ulimit, LogConfig
 
+if six.PY2:
+    from urllib import splitnport
+else:
+    from urllib.parse import splitnport
 
 DEFAULT_HTTP_HOST = "127.0.0.1"
 DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"
@@ -387,7 +391,6 @@ def parse_repository_tag(repo_name):
 # Protocol translation: tcp -> http, unix -> http+unix
 def parse_host(addr, is_win32=False, tls=False):
     proto = "http+unix"
-    host = DEFAULT_HTTP_HOST
     port = None
     path = ''
 
@@ -427,32 +430,27 @@ def parse_host(addr, is_win32=False, tls=False):
             )
         proto = "https" if tls else "http"
 
-    if proto != "http+unix" and ":" in addr:
-        host_parts = addr.split(':')
-        if len(host_parts) != 2:
-            raise errors.DockerException(
-                "Invalid bind address format: {0}".format(addr)
-            )
-        if host_parts[0]:
-            host = host_parts[0]
+    if proto in ("http", "https"):
+        address_parts = addr.split('/', 1)
+        host = address_parts[0]
+        if len(address_parts) == 2:
+            path = '/' + address_parts[1]
+        host, port = splitnport(host)
 
-        port = host_parts[1]
-        if '/' in port:
-            port, path = port.split('/', 1)
-            path = '/{0}'.format(path)
-        try:
-            port = int(port)
-        except Exception:
+        if port is None:
             raise errors.DockerException(
                 "Invalid port: {0}".format(addr)
             )
 
-    elif proto in ("http", "https") and ':' not in addr:
-        raise errors.DockerException(
-            "Bind address needs a port: {0}".format(addr))
+        if not host:
+            host = DEFAULT_HTTP_HOST
     else:
         host = addr
 
+    if proto in ("http", "https") and port == -1:
+        raise errors.DockerException(
+            "Bind address needs a port: {0}".format(addr))
+
     if proto == "http+unix" or proto == 'npipe':
         return "{0}://{1}".format(proto, host)
     return "{0}://{1}:{2}{3}".format(proto, host, port, path)


Test patch:
diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py
index 68484fe5..0f7a58c9 100644
--- a/tests/unit/utils_test.py
+++ b/tests/unit/utils_test.py
@@ -404,10 +404,18 @@ class ParseHostTest(base.BaseTestCase):
             'https://kokia.jp:2375': 'https://kokia.jp:2375',
             'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock',
             'unix://': 'http+unix://var/run/docker.sock',
+            '12.234.45.127:2375/docker/engine': (
+                'http://12.234.45.127:2375/docker/engine'
+            ),
             'somehost.net:80/service/swarm': (
                 'http://somehost.net:80/service/swarm'
             ),
             'npipe:////./pipe/docker_engine': 'npipe:////./pipe/docker_engine',
+            '[fd12::82d1]:2375': 'http://[fd12::82d1]:2375',
+            'https://[fd12:5672::12aa]:1090': 'https://[fd12:5672::12aa]:1090',
+            '[fd12::82d1]:2375/docker/engine': (
+                'http://[fd12::82d1]:2375/docker/engine'
+            ),
         }
 
         for host in invalid_hosts:
@@ -415,7 +423,7 @@ class ParseHostTest(base.BaseTestCase):
                 parse_host(host, None)
 
         for host, expected in valid_hosts.items():
-            self.assertEqual(parse_host(host, None), expected, msg=host)
+            assert parse_host(host, None) == expected
 
     def test_parse_host_empty_value(self):
         unix_socket = 'http+unix://var/run/docker.sock'