Kaydet (Commit) 7b8e0cd7 authored tarafından Joffrey F's avatar Joffrey F

Merge branch 'master' into momer-tls

Conflicts:
	docker/client.py
...@@ -4,7 +4,41 @@ ChangeLog ...@@ -4,7 +4,41 @@ ChangeLog
0.3.2 0.3.2
----- -----
_In development._ * Default API version is now 1.12 (support for docker 1.0)
* Added new methods `Client.get_image` and `Client.load_image`
(`docker save` and `docker load`)
* Added new method `Client.ping`
* Added new method `Client.resize`
* `Client.build` can now be provided with a custom context using the
`custom_context` parameter.
* Added support for `memswap_limit` parameter in `create_container`
* Added support for `force` parameter in `remove_container`
* Added support for `force` and `noprune` parameters in `remove_image`
* Added support for `timestamps` parameter in `logs`
* Added support for `dns_search` parameter in `start`
* Added support for `network_mode` parameter in `start`
* Added support for `size` parameter in `containers`
* Added support for `volumes_from` and `dns` parameters in `start`. As of
API version >= 1.10, these parameters no longer belong to `create_container`
* `Client.logs` now uses the logs endpoint when API version is sufficient
### Bugfixes
* Fixed a bug in pull where the `repo:tag` notation wasn't interpreted
properly
* Fixed a bug in streaming methods with python 3 (unicode, bytes/str related)
* Fixed a bug in `Client.start` where legacy notation for volumes wasn't
supported anymore.
### Miscellaneous
* The client now raises `DockerException`s when appropriate. You can import
`DockerException` (and its subclasses) from the `docker.errors` module to
catch them if needed.
* `docker.APIError` has been moved to the new `docker.errors` module as well.
* `Client.insert` is deprecated in API version > 1.11
* Improved integration tests should now run much faster.
* There is now a single source of truth for the docker-py version number.
0.3.1 0.3.1
----- -----
......
...@@ -20,7 +20,7 @@ a Docker daemon, simply do: ...@@ -20,7 +20,7 @@ a Docker daemon, simply do:
```python ```python
c = docker.Client(base_url='unix://var/run/docker.sock', c = docker.Client(base_url='unix://var/run/docker.sock',
version='1.9', version='1.12',
timeout=10) timeout=10)
``` ```
...@@ -400,4 +400,4 @@ client = docker.Client(base_url='<https_url>', tls=tls_config) ...@@ -400,4 +400,4 @@ client = docker.Client(base_url='<https_url>', tls=tls_config)
Equivalent CLI options: Equivalent CLI options:
`docker --tlsverify --tlscert /path/to/client-cert.pem `docker --tlsverify --tlscert /path/to/client-cert.pem
--tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...` --tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...`
\ No newline at end of file
...@@ -41,21 +41,13 @@ class Client(requests.Session): ...@@ -41,21 +41,13 @@ class Client(requests.Session):
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False): timeout=DEFAULT_TIMEOUT_SECONDS, tls=False):
super(Client, self).__init__() super(Client, self).__init__()
base_url = utils.parse_host(base_url)
if base_url is None: if 'http+unix:///' in base_url:
base_url = "http+unix://var/run/docker.sock" base_url = base_url.replace('unix:/', 'unix:')
if tls and not base_url.startswith('https://'): if tls and not base_url.startswith('https://'):
raise errors.TLSParameterError( raise errors.TLSParameterError(
'If using TLS, the base_url argument must begin with ' 'If using TLS, the base_url argument must begin with '
'"https://".') '"https://".')
if '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.base_url = base_url
self._version = version self._version = version
self._timeout = timeout self._timeout = timeout
...@@ -240,7 +232,7 @@ class Client(requests.Session): ...@@ -240,7 +232,7 @@ class Client(requests.Session):
# Because Docker introduced newlines at the end of chunks in v0.9, # Because Docker introduced newlines at the end of chunks in v0.9,
# and only on some API endpoints, we have to cater for both cases. # and only on some API endpoints, we have to cater for both cases.
size_line = socket.readline() size_line = socket.readline()
if size_line == '\r\n': if size_line == '\r\n' or size_line == '\n':
size_line = socket.readline() size_line = socket.readline()
size = int(size_line, 16) size = int(size_line, 16)
......
from .utils import ( from .utils import (
compare_version, convert_port_bindings, convert_volume_binds, compare_version, convert_port_bindings, convert_volume_binds,
mkbuildcontext, ping, tar, parse_repository_tag mkbuildcontext, ping, tar, parse_repository_tag, parse_host
) # flake8: noqa ) # flake8: noqa
...@@ -20,6 +20,11 @@ from distutils.version import StrictVersion ...@@ -20,6 +20,11 @@ from distutils.version import StrictVersion
import requests import requests
import six import six
from .. import errors
DEFAULT_HTTP_HOST = "127.0.0.1"
DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"
def mkbuildcontext(dockerfile): def mkbuildcontext(dockerfile):
f = tempfile.NamedTemporaryFile() f = tempfile.NamedTemporaryFile()
...@@ -139,9 +144,71 @@ def parse_repository_tag(repo): ...@@ -139,9 +144,71 @@ def parse_repository_tag(repo):
column_index = repo.rfind(':') column_index = repo.rfind(':')
if column_index < 0: if column_index < 0:
return repo, None return repo, None
tag = repo[column_index+1:] tag = repo[column_index + 1:]
slash_index = tag.find('/') slash_index = tag.find('/')
if slash_index < 0: if slash_index < 0:
return repo[:column_index], tag return repo[:column_index], tag
return repo, None 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)
version = "0.3.2-dev" version = "0.3.3-dev"
...@@ -37,6 +37,7 @@ setup( ...@@ -37,6 +37,7 @@ setup(
'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Topic :: Utilities', 'Topic :: Utilities',
'License :: OSI Approved :: Apache Software License', 'License :: OSI Approved :: Apache Software License',
], ],
......
...@@ -17,6 +17,7 @@ import base64 ...@@ -17,6 +17,7 @@ import base64
import json import json
import io import io
import os import os
import shutil
import signal import signal
import tempfile import tempfile
import unittest import unittest
...@@ -27,15 +28,19 @@ import six ...@@ -27,15 +28,19 @@ import six
# FIXME: missing tests for # FIXME: missing tests for
# export; history; import_image; insert; port; push; tag; get; load # export; history; import_image; insert; port; push; tag; get; load
DEFAULT_BASE_URL = os.environ.get('DOCKER_HOST')
class BaseTestCase(unittest.TestCase): class BaseTestCase(unittest.TestCase):
tmp_imgs = [] tmp_imgs = []
tmp_containers = [] tmp_containers = []
tmp_folders = []
def setUp(self): def setUp(self):
self.client = docker.Client(timeout=5) self.client = docker.Client(base_url=DEFAULT_BASE_URL, timeout=5)
self.tmp_imgs = [] self.tmp_imgs = []
self.tmp_containers = [] self.tmp_containers = []
self.tmp_folders = []
def tearDown(self): def tearDown(self):
for img in self.tmp_imgs: for img in self.tmp_imgs:
...@@ -49,6 +54,8 @@ class BaseTestCase(unittest.TestCase): ...@@ -49,6 +54,8 @@ class BaseTestCase(unittest.TestCase):
self.client.remove_container(container) self.client.remove_container(container)
except docker.errors.APIError: except docker.errors.APIError:
pass pass
for folder in self.tmp_folders:
shutil.rmtree(folder)
######################### #########################
# INFORMATION TESTS # # INFORMATION TESTS #
...@@ -108,7 +115,7 @@ class TestListContainers(BaseTestCase): ...@@ -108,7 +115,7 @@ class TestListContainers(BaseTestCase):
def runTest(self): def runTest(self):
res0 = self.client.containers(all=True) res0 = self.client.containers(all=True)
size = len(res0) size = len(res0)
res1 = self.client.create_container('busybox:latest', 'true;') res1 = self.client.create_container('busybox:latest', 'true')
self.assertIn('Id', res1) self.assertIn('Id', res1)
self.client.start(res1['Id']) self.client.start(res1['Id'])
self.tmp_containers.append(res1['Id']) self.tmp_containers.append(res1['Id'])
...@@ -118,7 +125,7 @@ class TestListContainers(BaseTestCase): ...@@ -118,7 +125,7 @@ class TestListContainers(BaseTestCase):
self.assertEqual(len(retrieved), 1) self.assertEqual(len(retrieved), 1)
retrieved = retrieved[0] retrieved = retrieved[0]
self.assertIn('Command', retrieved) self.assertIn('Command', retrieved)
self.assertEqual(retrieved['Command'], u'true;') self.assertEqual(retrieved['Command'], u'true')
self.assertIn('Image', retrieved) self.assertIn('Image', retrieved)
self.assertRegexpMatches(retrieved['Image'], r'busybox:.*') self.assertRegexpMatches(retrieved['Image'], r'busybox:.*')
self.assertIn('Status', retrieved) self.assertIn('Status', retrieved)
...@@ -138,7 +145,8 @@ class TestCreateContainer(BaseTestCase): ...@@ -138,7 +145,8 @@ class TestCreateContainer(BaseTestCase):
class TestCreateContainerWithBinds(BaseTestCase): class TestCreateContainerWithBinds(BaseTestCase):
def runTest(self): def runTest(self):
mount_dest = '/mnt' mount_dest = '/mnt'
mount_origin = '/tmp' mount_origin = tempfile.mkdtemp()
self.tmp_folders.append(mount_origin)
filename = 'shared.txt' filename = 'shared.txt'
shared_file = os.path.join(mount_origin, filename) shared_file = os.path.join(mount_origin, filename)
...@@ -850,6 +858,7 @@ class TestRunShlex(BaseTestCase): ...@@ -850,6 +858,7 @@ class TestRunShlex(BaseTestCase):
class TestLoadConfig(BaseTestCase): class TestLoadConfig(BaseTestCase):
def runTest(self): def runTest(self):
folder = tempfile.mkdtemp() folder = tempfile.mkdtemp()
self.tmp_folders.append(folder)
f = open(os.path.join(folder, '.dockercfg'), 'w') f = open(os.path.join(folder, '.dockercfg'), 'w')
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
f.write('auth = {0}\n'.format(auth_)) f.write('auth = {0}\n'.format(auth_))
...@@ -867,6 +876,7 @@ class TestLoadConfig(BaseTestCase): ...@@ -867,6 +876,7 @@ class TestLoadConfig(BaseTestCase):
class TestLoadJSONConfig(BaseTestCase): class TestLoadJSONConfig(BaseTestCase):
def runTest(self): def runTest(self):
folder = tempfile.mkdtemp() folder = tempfile.mkdtemp()
self.tmp_folders.append(folder)
f = open(os.path.join(folder, '.dockercfg'), 'w') f = open(os.path.join(folder, '.dockercfg'), 'w')
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
email_ = 'sakuya@scarlet.net' email_ = 'sakuya@scarlet.net'
...@@ -902,6 +912,6 @@ class TestConnectionTimeout(unittest.TestCase): ...@@ -902,6 +912,6 @@ class TestConnectionTimeout(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
c = docker.Client() c = docker.Client(base_url=DEFAULT_BASE_URL)
c.pull('busybox') c.pull('busybox')
unittest.main() unittest.main()
...@@ -746,14 +746,14 @@ class DockerClientTest(unittest.TestCase): ...@@ -746,14 +746,14 @@ class DockerClientTest(unittest.TestCase):
assert c.base_url == "http+unix://socket" assert c.base_url == "http+unix://socket"
def test_url_compatibility_http(self): 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): 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): def test_logs(self):
try: try:
......
import unittest 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): class UtilsTest(unittest.TestCase):
longMessage = True
def test_parse_repository_tag(self): def test_parse_repository_tag(self):
self.assertEqual(parse_repository_tag("root"), self.assertEqual(parse_repository_tag("root"),
...@@ -19,6 +21,37 @@ class UtilsTest(unittest.TestCase): ...@@ -19,6 +21,37 @@ class UtilsTest(unittest.TestCase):
self.assertEqual(parse_repository_tag("url:5000/repo:tag"), self.assertEqual(parse_repository_tag("url:5000/repo: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__': if __name__ == '__main__':
unittest.main() unittest.main()
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment