Repository: docker/docker-py

Issue:
swarm services: extra_hosts set in wrong format
Dear all,
we encounter issues with setting `extra_hosts` for swarm services on docker-py 2.6.1

```
docker version
Client:
 Version:      17.06.2-ce
 API version:  1.30
 Go version:   go1.8.3
 Git commit:   cec0b72
 Built:        Tue Sep  5 19:59:06 2017
 OS/Arch:      linux/amd64

Server:
 Version:      17.06.2-ce
 API version:  1.30 (minimum version 1.12)
 Go version:   go1.8.3
 Git commit:   cec0b72
 Built:        Tue Sep  5 20:00:25 2017
 OS/Arch:      linux/amd64
 Experimental: false
```
**What we do**
```
extra_hosts = dict()
extra_hosts.update({'domain.com': '8.8.8.8'})
client.services.create(name='myservice', image='nginx', hosts=extra_hosts)
```

**Result**
The entry is not found in `/etc/hosts` in the container.
The attribute `Hosts` of the service's `ContainerSpec` shows the following syntax: `domain.com:8.8.8.8`
However, the syntax should have been `8.8.8.8 domain.com`

**Analysis**
docker-py uses this line to format `extra_hosts` (https://github.com/docker/docker-py/blob/2.6.1/docker/utils/utils.py#L569):
```
 '{}:{}'.format(k, v) for k, v in sorted(six.iteritems(extra_hosts))
```

According to swarm's API (https://github.com/moby/moby/blob/d91c5f42eb37c6f88cec4021c10c0a1ded1785c3/api/types/swarm/container.go#L65), the ContainerSpec of a TaskSpec expects `extra_hosts` in the format described per http://man7.org/linux/man-pages/man5/hosts.5.html which is:
```
IP HostnameA
```

**Workaround**
Currently, we compensate this as follows:
```
create_service_kwargs = docker.models.services._get_create_service_kwargs(...)
create_service_kwargs['task_template']['ContainerSpec']['Hosts'] = ['8.8.8.8 domain.com']
client.api.create_service(**create_service_kwargs)
```

Maybe you can comment on this.

Thanks and Regards,
Stephen


Discussion:
Hi, I'm currently an undergraduate student taking a course on virtualization. For our final project, my group of two is required to find open-source issues to work on in our last few weeks. This issue sounded manageable for the time frame and our skill level. Is anybody already working on this and does this sound reasonable?
Thank you
Yes, this should not be too complicated. Go ahead!

However, first an thorough analysis should be done, to discover whether our assumptions hold true.
Docker commenting on this would be nice also.

Resolving patch:
diff --git a/docker/types/services.py b/docker/types/services.py
--- a/docker/types/services.py
+++ b/docker/types/services.py
@@ -137,7 +137,7 @@ def __init__(self, image, command=None, args=None, hostname=None, env=None,
         if labels is not None:
             self['Labels'] = labels
         if hosts is not None:
-            self['Hosts'] = format_extra_hosts(hosts)
+            self['Hosts'] = format_extra_hosts(hosts, task=True)
 
         if mounts is not None:
             parsed_mounts = []
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -564,7 +564,13 @@ def format_env(key, value):
     return [format_env(*var) for var in six.iteritems(environment)]
 
 
-def format_extra_hosts(extra_hosts):
+def format_extra_hosts(extra_hosts, task=False):
+    # Use format dictated by Swarm API if container is part of a task
+    if task:
+        return [
+            '{} {}'.format(v, k) for k, v in sorted(six.iteritems(extra_hosts))
+        ]
+
     return [
         '{}:{}'.format(k, v) for k, v in sorted(six.iteritems(extra_hosts))
     ]


Test patch:
diff --git a/tests/integration/api_service_test.py b/tests/integration/api_service_test.py
--- a/tests/integration/api_service_test.py
+++ b/tests/integration/api_service_test.py
@@ -588,8 +588,8 @@ def test_create_service_with_hosts(self):
         assert 'Hosts' in svc_info['Spec']['TaskTemplate']['ContainerSpec']
         hosts = svc_info['Spec']['TaskTemplate']['ContainerSpec']['Hosts']
         assert len(hosts) == 2
-        assert 'foobar:127.0.0.1' in hosts
-        assert 'baz:8.8.8.8' in hosts
+        assert '127.0.0.1 foobar' in hosts
+        assert '8.8.8.8 baz' in hosts
 
     @requires_api_version('1.25')
     def test_create_service_with_hostname(self):