Skip to content

Commit 7b8e0cd

Browse files
committed
Merge branch 'master' into momer-tls
Conflicts: docker/client.py
2 parents 72cb388 + ed2b458 commit 7b8e0cd

File tree

10 files changed

+165
-28
lines changed

10 files changed

+165
-28
lines changed

ChangeLog.md

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,41 @@ ChangeLog
44
0.3.2
55
-----
66

7-
_In development._
7+
* Default API version is now 1.12 (support for docker 1.0)
8+
* Added new methods `Client.get_image` and `Client.load_image`
9+
(`docker save` and `docker load`)
10+
* Added new method `Client.ping`
11+
* Added new method `Client.resize`
12+
* `Client.build` can now be provided with a custom context using the
13+
`custom_context` parameter.
14+
* Added support for `memswap_limit` parameter in `create_container`
15+
* Added support for `force` parameter in `remove_container`
16+
* Added support for `force` and `noprune` parameters in `remove_image`
17+
* Added support for `timestamps` parameter in `logs`
18+
* Added support for `dns_search` parameter in `start`
19+
* Added support for `network_mode` parameter in `start`
20+
* Added support for `size` parameter in `containers`
21+
* Added support for `volumes_from` and `dns` parameters in `start`. As of
22+
API version >= 1.10, these parameters no longer belong to `create_container`
23+
* `Client.logs` now uses the logs endpoint when API version is sufficient
24+
25+
### Bugfixes
26+
27+
* Fixed a bug in pull where the `repo:tag` notation wasn't interpreted
28+
properly
29+
* Fixed a bug in streaming methods with python 3 (unicode, bytes/str related)
30+
* Fixed a bug in `Client.start` where legacy notation for volumes wasn't
31+
supported anymore.
32+
33+
### Miscellaneous
34+
35+
* The client now raises `DockerException`s when appropriate. You can import
36+
`DockerException` (and its subclasses) from the `docker.errors` module to
37+
catch them if needed.
38+
* `docker.APIError` has been moved to the new `docker.errors` module as well.
39+
* `Client.insert` is deprecated in API version > 1.11
40+
* Improved integration tests should now run much faster.
41+
* There is now a single source of truth for the docker-py version number.
842

943
0.3.1
1044
-----

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ a Docker daemon, simply do:
2020

2121
```python
2222
c = docker.Client(base_url='unix://var/run/docker.sock',
23-
version='1.9',
23+
version='1.12',
2424
timeout=10)
2525
```
2626

@@ -400,4 +400,4 @@ client = docker.Client(base_url='<https_url>', tls=tls_config)
400400

401401
Equivalent CLI options:
402402
`docker --tlsverify --tlscert /path/to/client-cert.pem
403-
--tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...`
403+
--tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...`

docker/client.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -41,21 +41,13 @@ class Client(requests.Session):
4141
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
4242
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False):
4343
super(Client, self).__init__()
44-
45-
if base_url is None:
46-
base_url = "http+unix://var/run/docker.sock"
44+
base_url = utils.parse_host(base_url)
45+
if 'http+unix:///' in base_url:
46+
base_url = base_url.replace('unix:/', 'unix:')
4747
if tls and not base_url.startswith('https://'):
4848
raise errors.TLSParameterError(
4949
'If using TLS, the base_url argument must begin with '
5050
'"https://".')
51-
if 'unix:///' in base_url:
52-
base_url = base_url.replace('unix:/', 'unix:')
53-
if base_url.startswith('unix:'):
54-
base_url = "http+" + base_url
55-
if base_url.startswith('tcp:'):
56-
base_url = base_url.replace('tcp:', 'http:')
57-
if base_url.endswith('/'):
58-
base_url = base_url[:-1]
5951
self.base_url = base_url
6052
self._version = version
6153
self._timeout = timeout
@@ -240,7 +232,7 @@ def _stream_helper(self, response):
240232
# Because Docker introduced newlines at the end of chunks in v0.9,
241233
# and only on some API endpoints, we have to cater for both cases.
242234
size_line = socket.readline()
243-
if size_line == '\r\n':
235+
if size_line == '\r\n' or size_line == '\n':
244236
size_line = socket.readline()
245237

246238
size = int(size_line, 16)

docker/utils/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
from .utils import (
22
compare_version, convert_port_bindings, convert_volume_binds,
3-
mkbuildcontext, ping, tar, parse_repository_tag
3+
mkbuildcontext, ping, tar, parse_repository_tag, parse_host
44
) # flake8: noqa

docker/utils/utils.py

Lines changed: 68 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@
2020
import requests
2121
import six
2222

23+
from .. import errors
24+
25+
DEFAULT_HTTP_HOST = "127.0.0.1"
26+
DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"
27+
2328

2429
def mkbuildcontext(dockerfile):
2530
f = tempfile.NamedTemporaryFile()
@@ -139,9 +144,71 @@ def parse_repository_tag(repo):
139144
column_index = repo.rfind(':')
140145
if column_index < 0:
141146
return repo, None
142-
tag = repo[column_index+1:]
147+
tag = repo[column_index + 1:]
143148
slash_index = tag.find('/')
144149
if slash_index < 0:
145150
return repo[:column_index], tag
146151

147152
return repo, None
153+
154+
155+
# Based on utils.go:ParseHost http://tinyurl.com/nkahcfh
156+
# fd:// protocol unsupported (for obvious reasons)
157+
# Added support for http and https
158+
# Protocol translation: tcp -> http, unix -> http+unix
159+
def parse_host(addr):
160+
proto = "http+unix"
161+
host = DEFAULT_HTTP_HOST
162+
port = None
163+
if not addr or addr.strip() == 'unix://':
164+
return DEFAULT_UNIX_SOCKET
165+
166+
addr = addr.strip()
167+
if addr.startswith('http://'):
168+
addr = addr.replace('http://', 'tcp://')
169+
if addr.startswith('http+unix://'):
170+
addr = addr.replace('http+unix://', 'unix://')
171+
172+
if addr == 'tcp://':
173+
raise errors.DockerException("Invalid bind address format: %s" % addr)
174+
elif addr.startswith('unix://'):
175+
addr = addr[7:]
176+
elif addr.startswith('tcp://'):
177+
proto = "http"
178+
addr = addr[6:]
179+
elif addr.startswith('https://'):
180+
proto = "https"
181+
addr = addr[8:]
182+
elif addr.startswith('fd://'):
183+
raise errors.DockerException("fd protocol is not implemented")
184+
else:
185+
if "://" in addr:
186+
raise errors.DockerException(
187+
"Invalid bind address protocol: %s" % addr
188+
)
189+
proto = "http"
190+
191+
if proto != "http+unix" and ":" in addr:
192+
host_parts = addr.split(':')
193+
if len(host_parts) != 2:
194+
raise errors.DockerException(
195+
"Invalid bind address format: %s" % addr
196+
)
197+
if host_parts[0]:
198+
host = host_parts[0]
199+
200+
try:
201+
port = int(host_parts[1])
202+
except Exception:
203+
raise errors.DockerException(
204+
"Invalid port: %s", addr
205+
)
206+
207+
elif proto in ("http", "https") and ':' not in addr:
208+
raise errors.DockerException("Bind address needs a port: %s" % addr)
209+
else:
210+
host = addr
211+
212+
if proto == "http+unix":
213+
return "%s://%s" % (proto, host)
214+
return "%s://%s:%d" % (proto, host, port)

docker/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
version = "0.3.2-dev"
1+
version = "0.3.3-dev"

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
'Programming Language :: Python :: 2.7',
3838
'Programming Language :: Python :: 3.2',
3939
'Programming Language :: Python :: 3.3',
40+
'Programming Language :: Python :: 3.4',
4041
'Topic :: Utilities',
4142
'License :: OSI Approved :: Apache Software License',
4243
],

tests/integration_test.py

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import json
1818
import io
1919
import os
20+
import shutil
2021
import signal
2122
import tempfile
2223
import unittest
@@ -27,15 +28,19 @@
2728
# FIXME: missing tests for
2829
# export; history; import_image; insert; port; push; tag; get; load
2930

31+
DEFAULT_BASE_URL = os.environ.get('DOCKER_HOST')
32+
3033

3134
class BaseTestCase(unittest.TestCase):
3235
tmp_imgs = []
3336
tmp_containers = []
37+
tmp_folders = []
3438

3539
def setUp(self):
36-
self.client = docker.Client(timeout=5)
40+
self.client = docker.Client(base_url=DEFAULT_BASE_URL, timeout=5)
3741
self.tmp_imgs = []
3842
self.tmp_containers = []
43+
self.tmp_folders = []
3944

4045
def tearDown(self):
4146
for img in self.tmp_imgs:
@@ -49,6 +54,8 @@ def tearDown(self):
4954
self.client.remove_container(container)
5055
except docker.errors.APIError:
5156
pass
57+
for folder in self.tmp_folders:
58+
shutil.rmtree(folder)
5259

5360
#########################
5461
# INFORMATION TESTS #
@@ -108,7 +115,7 @@ class TestListContainers(BaseTestCase):
108115
def runTest(self):
109116
res0 = self.client.containers(all=True)
110117
size = len(res0)
111-
res1 = self.client.create_container('busybox:latest', 'true;')
118+
res1 = self.client.create_container('busybox:latest', 'true')
112119
self.assertIn('Id', res1)
113120
self.client.start(res1['Id'])
114121
self.tmp_containers.append(res1['Id'])
@@ -118,7 +125,7 @@ def runTest(self):
118125
self.assertEqual(len(retrieved), 1)
119126
retrieved = retrieved[0]
120127
self.assertIn('Command', retrieved)
121-
self.assertEqual(retrieved['Command'], u'true;')
128+
self.assertEqual(retrieved['Command'], u'true')
122129
self.assertIn('Image', retrieved)
123130
self.assertRegexpMatches(retrieved['Image'], r'busybox:.*')
124131
self.assertIn('Status', retrieved)
@@ -138,7 +145,8 @@ def runTest(self):
138145
class TestCreateContainerWithBinds(BaseTestCase):
139146
def runTest(self):
140147
mount_dest = '/mnt'
141-
mount_origin = '/tmp'
148+
mount_origin = tempfile.mkdtemp()
149+
self.tmp_folders.append(mount_origin)
142150

143151
filename = 'shared.txt'
144152
shared_file = os.path.join(mount_origin, filename)
@@ -850,6 +858,7 @@ def runTest(self):
850858
class TestLoadConfig(BaseTestCase):
851859
def runTest(self):
852860
folder = tempfile.mkdtemp()
861+
self.tmp_folders.append(folder)
853862
f = open(os.path.join(folder, '.dockercfg'), 'w')
854863
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
855864
f.write('auth = {0}\n'.format(auth_))
@@ -867,6 +876,7 @@ def runTest(self):
867876
class TestLoadJSONConfig(BaseTestCase):
868877
def runTest(self):
869878
folder = tempfile.mkdtemp()
879+
self.tmp_folders.append(folder)
870880
f = open(os.path.join(folder, '.dockercfg'), 'w')
871881
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
872882
email_ = '[email protected]'
@@ -902,6 +912,6 @@ def runTest(self):
902912

903913

904914
if __name__ == '__main__':
905-
c = docker.Client()
915+
c = docker.Client(base_url=DEFAULT_BASE_URL)
906916
c.pull('busybox')
907917
unittest.main()

tests/test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -746,14 +746,14 @@ def test_url_compatibility_http_unix_triple_slash(self):
746746
assert c.base_url == "http+unix://socket"
747747

748748
def test_url_compatibility_http(self):
749-
c = docker.Client(base_url="http://hostname")
749+
c = docker.Client(base_url="http://hostname:1234")
750750

751-
assert c.base_url == "http://hostname"
751+
assert c.base_url == "http://hostname:1234"
752752

753753
def test_url_compatibility_tcp(self):
754-
c = docker.Client(base_url="tcp://hostname")
754+
c = docker.Client(base_url="tcp://hostname:1234")
755755

756-
assert c.base_url == "http://hostname"
756+
assert c.base_url == "http://hostname:1234"
757757

758758
def test_logs(self):
759759
try:

tests/utils_test.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import unittest
22

3-
from docker.utils import parse_repository_tag
3+
from docker.errors import DockerException
4+
from docker.utils import parse_repository_tag, parse_host
45

56

67
class UtilsTest(unittest.TestCase):
8+
longMessage = True
79

810
def test_parse_repository_tag(self):
911
self.assertEqual(parse_repository_tag("root"),
@@ -19,6 +21,37 @@ def test_parse_repository_tag(self):
1921
self.assertEqual(parse_repository_tag("url:5000/repo:tag"),
2022
("url:5000/repo", "tag"))
2123

24+
def test_parse_host(self):
25+
invalid_hosts = [
26+
'0.0.0.0',
27+
'tcp://',
28+
'udp://127.0.0.1',
29+
'udp://127.0.0.1:2375',
30+
]
31+
32+
valid_hosts = {
33+
'0.0.0.1:5555': 'http://0.0.0.1:5555',
34+
':6666': 'http://127.0.0.1:6666',
35+
'tcp://:7777': 'http://127.0.0.1:7777',
36+
'http://:7777': 'http://127.0.0.1:7777',
37+
'https://kokia.jp:2375': 'https://kokia.jp:2375',
38+
'': 'http+unix://var/run/docker.sock',
39+
None: 'http+unix://var/run/docker.sock',
40+
'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock',
41+
'unix://': 'http+unix://var/run/docker.sock'
42+
}
43+
44+
for host in invalid_hosts:
45+
try:
46+
parsed = parse_host(host)
47+
self.fail('Expected to fail but success: %s -> %s' % (
48+
host, parsed
49+
))
50+
except DockerException:
51+
pass
52+
53+
for host, expected in valid_hosts.items():
54+
self.assertEqual(parse_host(host), expected, msg=host)
2255

2356
if __name__ == '__main__':
2457
unittest.main()

0 commit comments

Comments
 (0)