summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoffrey F <f.joffrey@gmail.com>2014-12-19 09:38:43 -0800
committerJoffrey F <f.joffrey@gmail.com>2014-12-19 09:38:43 -0800
commit9b166eea7555bd4d09d9c7959a1c73b9ac19070c (patch)
tree2f4719e88a84b5b8d40cd4fc842a2063ba589134
parent53beba75f44eaa2d0aff047b61f9fd4b2eb04139 (diff)
parent2218dbaa6772838020e2836ac5dd2fddd4f3f574 (diff)
downloaddocker-py-0.7.0.tar.gz
Merge pull request #436 from docker/0.7.0-release0.7.0
0.7.0 release
-rw-r--r--docker/client.py87
-rw-r--r--docker/utils/utils.py27
-rw-r--r--docker/version.py2
-rw-r--r--docs/api.md5
-rw-r--r--docs/change_log.md37
-rw-r--r--docs/hostconfig.md1
-rw-r--r--tests/integration_test.py72
-rw-r--r--tests/test.py1
-rw-r--r--tests/utils_test.py7
9 files changed, 132 insertions, 107 deletions
diff --git a/docker/client.py b/docker/client.py
index 0863bdb..1aa9ed6 100644
--- a/docker/client.py
+++ b/docker/client.py
@@ -115,8 +115,8 @@ class Client(requests.Session):
command = shlex.split(str(command))
if isinstance(environment, dict):
environment = [
- (six.text_type('{0}={1}').format(k, v)
- for k, v in environment.items())
+ six.text_type('{0}={1}').format(k, v)
+ for k, v in six.iteritems(environment)
]
if isinstance(mem_limit, six.string_types):
@@ -911,63 +911,7 @@ class Client(requests.Session):
restart_policy=None, cap_add=None, cap_drop=None, devices=None,
extra_hosts=None):
- start_config = {}
-
- if isinstance(container, dict):
- container = container.get('Id')
-
- if isinstance(lxc_conf, dict):
- formatted = []
- for k, v in six.iteritems(lxc_conf):
- formatted.append({'Key': k, 'Value': str(v)})
- lxc_conf = formatted
-
- if lxc_conf:
- start_config['LxcConf'] = lxc_conf
-
- if binds:
- start_config['Binds'] = utils.convert_volume_binds(binds)
-
- if port_bindings:
- start_config['PortBindings'] = utils.convert_port_bindings(
- port_bindings
- )
-
- if publish_all_ports:
- start_config['PublishAllPorts'] = publish_all_ports
-
- if links:
- if isinstance(links, dict):
- links = six.iteritems(links)
-
- formatted_links = [
- '{0}:{1}'.format(k, v) for k, v in sorted(links)
- ]
-
- start_config['Links'] = formatted_links
-
- if extra_hosts:
- if isinstance(extra_hosts, dict):
- extra_hosts = six.iteritems(extra_hosts)
-
- formatted_extra_hosts = [
- '{0}:{1}'.format(k, v) for k, v in sorted(extra_hosts)
- ]
-
- start_config['ExtraHosts'] = formatted_extra_hosts
-
- if privileged:
- start_config['Privileged'] = privileged
-
- if utils.compare_version('1.10', self._version) >= 0:
- if dns is not None:
- start_config['Dns'] = dns
- if volumes_from is not None:
- if isinstance(volumes_from, six.string_types):
- volumes_from = volumes_from.split(',')
- start_config['VolumesFrom'] = volumes_from
- else:
-
+ if utils.compare_version('1.10', self._version) < 0:
if dns is not None:
raise errors.APIError(
'dns is only supported for API version >= 1.10'
@@ -976,23 +920,18 @@ class Client(requests.Session):
raise errors.APIError(
'volumes_from is only supported for API version >= 1.10'
)
- if dns_search:
- start_config['DnsSearch'] = dns_search
-
- if network_mode:
- start_config['NetworkMode'] = network_mode
-
- if restart_policy:
- start_config['RestartPolicy'] = restart_policy
-
- if cap_add:
- start_config['CapAdd'] = cap_add
- if cap_drop:
- start_config['CapDrop'] = cap_drop
+ start_config = utils.create_host_config(
+ binds=binds, port_bindings=port_bindings, lxc_conf=lxc_conf,
+ publish_all_ports=publish_all_ports, links=links, dns=dns,
+ privileged=privileged, dns_search=dns_search, cap_add=cap_add,
+ cap_drop=cap_drop, volumes_from=volumes_from, devices=devices,
+ network_mode=network_mode, restart_policy=restart_policy,
+ extra_hosts=extra_hosts
+ )
- if devices:
- start_config['Devices'] = utils.parse_devices(devices)
+ if isinstance(container, dict):
+ container = container.get('Id')
url = self._url("/containers/{0}/start".format(container))
if not start_config:
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index 98d59c4..a554952 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -300,12 +300,16 @@ def create_host_config(
binds=None, port_bindings=None, lxc_conf=None,
publish_all_ports=False, links=None, privileged=False,
dns=None, dns_search=None, volumes_from=None, network_mode=None,
- restart_policy=None, cap_add=None, cap_drop=None, devices=None
+ restart_policy=None, cap_add=None, cap_drop=None, devices=None,
+ extra_hosts=None
):
- host_config = {
- 'Privileged': privileged,
- 'PublishAllPorts': publish_all_ports,
- }
+ host_config = {}
+
+ if privileged:
+ host_config['Privileged'] = privileged
+
+ if publish_all_ports:
+ host_config['PublishAllPorts'] = publish_all_ports
if dns_search:
host_config['DnsSearch'] = dns_search
@@ -341,7 +345,14 @@ def create_host_config(
port_bindings
)
- host_config['PublishAllPorts'] = publish_all_ports
+ if extra_hosts:
+ if isinstance(extra_hosts, dict):
+ extra_hosts = [
+ '{0}:{1}'.format(k, v)
+ for k, v in sorted(six.iteritems(extra_hosts))
+ ]
+
+ host_config['ExtraHosts'] = extra_hosts
if links:
if isinstance(links, dict):
@@ -358,6 +369,8 @@ def create_host_config(
for k, v in six.iteritems(lxc_conf):
formatted.append({'Key': k, 'Value': str(v)})
lxc_conf = formatted
- host_config['LxcConf'] = lxc_conf
+
+ if lxc_conf:
+ host_config['LxcConf'] = lxc_conf
return host_config
diff --git a/docker/version.py b/docker/version.py
index ba6bd5d..7a0c9fe 100644
--- a/docker/version.py
+++ b/docker/version.py
@@ -1 +1 @@
-version = "0.6.1-dev"
+version = "0.7.0"
diff --git a/docs/api.md b/docs/api.md
index 64d9b3d..a4e7dd1 100644
--- a/docs/api.md
+++ b/docs/api.md
@@ -54,7 +54,7 @@ correct value (e.g `gzip`).
* nocache (bool): Don't use the cache when set to `True`
* rm (bool): Remove intermediate containers
* stream (bool): Return a blocking generator you can iterate over to retrieve
-build output as it happens
+ build output as it happens
* timeout (int): HTTP timeout
* custom_context (bool): Optional if using `fileobj`
* encoding (str): The encoding for a stream. Set to `gzip` for compressing
@@ -385,6 +385,8 @@ Nearly identical to the `docker login` command, but non-interactive.
* email (str): The email for the registry account
* registry (str): URL to the registry. Ex:`https://index.docker.io/v1/`
* reauth (bool): Whether refresh existing authentication on the docker server.
+* dockercfg_path (str): Use a custom path for the .dockercfg file
+ (default `$HOME/.dockercfg`)
**Returns** (dict): The response from the login request
@@ -636,6 +638,7 @@ from. Optionally a single string joining container id's with commas
`['on-failure', 'always']`
* cap_add (list of str): See note above
* cap_drop (list of str): See note above
+* extra_hosts (dict): custom host-to-IP mappings (host:ip)
```python
>>> from docker import Client
diff --git a/docs/change_log.md b/docs/change_log.md
index e3d5a22..91c99d3 100644
--- a/docs/change_log.md
+++ b/docs/change_log.md
@@ -1,6 +1,43 @@
Change Log
==========
+0.7.0
+-----
+
+### Breaking changes
+
+* Passing `dns` or `volumes_from` in `Client.start` with API version < 1.10
+ will now raise an exception (previously only triggered a warning)
+
+### Features
+
+* Added support for `host_config` in `Client.create_container`
+* Added utility method `docker.utils.create_host_config` to help build a
+ proper `HostConfig` dictionary.
+* Added support for the `pull` parameter in `Client.build`
+* Added support for the `forcerm` parameter in `Client.build`
+* Added support for `extra_hosts` in `Client.start`
+* Added support for a custom `timeout` in `Client.wait`
+* Added support for custom `.dockercfg` loading in `Client.login`
+ (`dockercfg_path` argument)
+
+### Bugfixes
+
+* Fixed a bug where some output wouldn't be streamed properly in streaming
+ chunked responses
+* Fixed a bug where the `devices` param didn't recognize the proper delimiter
+* `Client.login` now properly expands the `registry` URL if provided.
+* Fixed a bug where unicode characters in passed for `environment` in
+ `create_container` would break.
+
+### Miscellaneous
+
+* Several unit tests and integration tests improvements.
+* `Client` constructor now enforces passing the `version` parameter as a
+ string.
+* Build context files are now ordered by filename when creating the archive
+ (for consistency with docker mainline behavior)
+
0.6.0
-----
* **This version introduces breaking changes!**
diff --git a/docs/hostconfig.md b/docs/hostconfig.md
index 07a75b1..2d7f10f 100644
--- a/docs/hostconfig.md
+++ b/docs/hostconfig.md
@@ -81,6 +81,7 @@ for example:
* restart_policy (dict): "Name" param must be one of `['on-failure', 'always']`
* cap_add (list of str): Add kernel capabilities
* cap_drop (list of str): Drop kernel capabilities
+* extra_hosts (dict): custom host-to-IP mappings (host:ip)
**Returns** (dict) HostConfig dictionary
diff --git a/tests/integration_test.py b/tests/integration_test.py
index 92ec882..801dd82 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -35,6 +35,7 @@ DEFAULT_BASE_URL = os.environ.get('DOCKER_HOST')
warnings.simplefilter('error')
create_host_config = docker.utils.create_host_config
+compare_version = docker.utils.compare_version
class BaseTestCase(unittest.TestCase):
@@ -43,6 +44,9 @@ class BaseTestCase(unittest.TestCase):
tmp_folders = []
def setUp(self):
+ if six.PY2:
+ self.assertRegex = self.assertRegexpMatches
+ self.assertCountEqual = self.assertItemsEqual
self.client = docker.Client(base_url=DEFAULT_BASE_URL, timeout=5)
self.tmp_imgs = []
self.tmp_containers = []
@@ -62,6 +66,7 @@ class BaseTestCase(unittest.TestCase):
pass
for folder in self.tmp_folders:
shutil.rmtree(folder)
+ self.client.close()
#########################
# INFORMATION TESTS #
@@ -134,7 +139,7 @@ class TestListContainers(BaseTestCase):
self.assertIn('Command', retrieved)
self.assertEqual(retrieved['Command'], six.text_type('true'))
self.assertIn('Image', retrieved)
- self.assertRegexpMatches(retrieved['Image'], r'busybox:.*')
+ self.assertRegex(retrieved['Image'], r'busybox:.*')
self.assertIn('Status', retrieved)
#####################
@@ -178,6 +183,8 @@ class TestCreateContainerWithBinds(BaseTestCase):
logs = self.client.logs(container_id)
os.unlink(shared_file)
+ if six.PY3:
+ logs = logs.decode('utf-8')
self.assertIn(filename, logs)
@@ -208,6 +215,8 @@ class TestStartContainerWithBinds(BaseTestCase):
logs = self.client.logs(container_id)
os.unlink(shared_file)
+ if six.PY3:
+ logs = logs.decode('utf-8')
self.assertIn(filename, logs)
@@ -517,12 +526,12 @@ class TestPort(BaseTestCase):
def runTest(self):
port_bindings = {
- 1111: ('127.0.0.1', '4567'),
- 2222: ('127.0.0.1', '4568')
+ '1111': ('127.0.0.1', '4567'),
+ '2222': ('127.0.0.1', '4568')
}
container = self.client.create_container(
- 'busybox', ['sleep', '60'], ports=port_bindings.keys(),
+ 'busybox', ['sleep', '60'], ports=list(port_bindings.keys()),
host_config=create_host_config(port_bindings=port_bindings)
)
id = container['Id']
@@ -546,12 +555,12 @@ class TestStartWithPortBindings(BaseTestCase):
def runTest(self):
port_bindings = {
- 1111: ('127.0.0.1', '4567'),
- 2222: ('127.0.0.1', '4568')
+ '1111': ('127.0.0.1', '4567'),
+ '2222': ('127.0.0.1', '4568')
}
container = self.client.create_container(
- 'busybox', ['sleep', '60'], ports=port_bindings.keys()
+ 'busybox', ['sleep', '60'], ports=list(port_bindings.keys())
)
id = container['Id']
@@ -668,7 +677,7 @@ class TestCreateContainerWithVolumesFrom(BaseTestCase):
self.client.start(container3_id)
info = self.client.inspect_container(res2['Id'])
- self.assertItemsEqual(info['HostConfig']['VolumesFrom'], vol_names)
+ self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names)
class TestCreateContainerWithLinks(BaseTestCase):
@@ -713,6 +722,8 @@ class TestCreateContainerWithLinks(BaseTestCase):
self.assertEqual(self.client.wait(container3_id), 0)
logs = self.client.logs(container3_id)
+ if six.PY3:
+ logs = logs.decode('utf-8')
self.assertIn('{0}_NAME='.format(link_env_prefix1), logs)
self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix1), logs)
self.assertIn('{0}_NAME='.format(link_env_prefix2), logs)
@@ -749,7 +760,7 @@ class TestStartContainerWithVolumesFrom(BaseTestCase):
self.client.start(container3_id, volumes_from=vol_names)
info = self.client.inspect_container(res2['Id'])
- self.assertItemsEqual(info['HostConfig']['VolumesFrom'], vol_names)
+ self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names)
class TestStartContainerWithLinks(BaseTestCase):
@@ -793,6 +804,8 @@ class TestStartContainerWithLinks(BaseTestCase):
self.assertEqual(self.client.wait(container3_id), 0)
logs = self.client.logs(container3_id)
+ if six.PY3:
+ logs = logs.decode('utf-8')
self.assertIn('{0}_NAME='.format(link_env_prefix1), logs)
self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix1), logs)
self.assertIn('{0}_NAME='.format(link_env_prefix2), logs)
@@ -939,6 +952,7 @@ class TestRemoveLink(BaseTestCase):
class TestPull(BaseTestCase):
def runTest(self):
+ self.client.close()
self.client = docker.Client(base_url=DEFAULT_BASE_URL, timeout=10)
try:
self.client.remove_image('busybox')
@@ -947,7 +961,7 @@ class TestPull(BaseTestCase):
res = self.client.pull('busybox')
self.assertEqual(type(res), six.text_type)
self.assertGreaterEqual(
- self.client.images('busybox'), 1
+ len(self.client.images('busybox')), 1
)
img_info = self.client.inspect_image('busybox')
self.assertIn('Id', img_info)
@@ -955,6 +969,7 @@ class TestPull(BaseTestCase):
class TestPullStream(BaseTestCase):
def runTest(self):
+ self.client.close()
self.client = docker.Client(base_url=DEFAULT_BASE_URL, timeout=10)
try:
self.client.remove_image('busybox')
@@ -962,9 +977,11 @@ class TestPullStream(BaseTestCase):
pass
stream = self.client.pull('busybox', stream=True)
for chunk in stream:
+ if six.PY3:
+ chunk = chunk.decode('utf-8')
json.loads(chunk) # ensure chunk is a single, valid JSON blob
self.assertGreaterEqual(
- self.client.images('busybox'), 1
+ len(self.client.images('busybox')), 1
)
img_info = self.client.inspect_image('busybox')
self.assertIn('Id', img_info)
@@ -1013,7 +1030,7 @@ class TestRemoveImage(BaseTestCase):
class TestBuild(BaseTestCase):
def runTest(self):
- if self.client._version >= 1.8:
+ if compare_version(self.client._version, '1.8') < 0:
return
script = io.BytesIO('\n'.join([
'FROM busybox',
@@ -1055,6 +1072,8 @@ class TestBuildStream(BaseTestCase):
stream = self.client.build(fileobj=script, stream=True)
logs = ''
for chunk in stream:
+ if six.PY3:
+ chunk = chunk.decode('utf-8')
json.loads(chunk) # ensure chunk is a single, valid JSON blob
logs += chunk
self.assertNotEqual(logs, '')
@@ -1075,13 +1094,15 @@ class TestBuildFromStringIO(BaseTestCase):
stream = self.client.build(fileobj=script, stream=True)
logs = ''
for chunk in stream:
+ if six.PY3:
+ chunk = chunk.decode('utf-8')
logs += chunk
self.assertNotEqual(logs, '')
class TestBuildWithAuth(BaseTestCase):
def runTest(self):
- if self.client._version < 1.9:
+ if compare_version(self.client._version, '1.9') >= 0:
return
k = 'K4104GON3P4Q6ZUJFZRRC2ZQTBJ5YT0UMZD7TGT7ZVIR8Y05FAH2TJQI6Y90SMIB'
@@ -1097,6 +1118,8 @@ class TestBuildWithAuth(BaseTestCase):
stream = self.client.build(fileobj=script, stream=True)
logs = ''
for chunk in stream:
+ if six.PY3:
+ chunk = chunk.decode('utf-8')
logs += chunk
self.assertNotEqual(logs, '')
@@ -1105,7 +1128,7 @@ class TestBuildWithAuth(BaseTestCase):
class TestBuildWithDockerignore(Cleanup, BaseTestCase):
def runTest(self):
- if self.client._version < 1.8:
+ if compare_version(self.client._version, '1.8') >= 0:
return
base_dir = tempfile.mkdtemp()
@@ -1136,6 +1159,8 @@ class TestBuildWithDockerignore(Cleanup, BaseTestCase):
stream = self.client.build(path=base_dir, stream=True)
logs = ''
for chunk in stream:
+ if six.PY3:
+ chunk = chunk.decode('utf-8')
logs += chunk
self.assertFalse('node_modules' in logs)
self.assertTrue('not-ignored' in logs)
@@ -1171,16 +1196,17 @@ class TestLoadConfig(BaseTestCase):
def runTest(self):
folder = tempfile.mkdtemp()
self.tmp_folders.append(folder)
- f = open(os.path.join(folder, '.dockercfg'), 'w')
+ cfg_path = os.path.join(folder, '.dockercfg')
+ f = open(cfg_path, 'w')
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
f.write('auth = {0}\n'.format(auth_))
f.write('email = sakuya@scarlet.net')
f.close()
- cfg = docker.auth.load_config(folder)
+ cfg = docker.auth.load_config(cfg_path)
self.assertNotEqual(cfg[docker.auth.INDEX_URL], None)
cfg = cfg[docker.auth.INDEX_URL]
- self.assertEqual(cfg['username'], b'sakuya')
- self.assertEqual(cfg['password'], b'izayoi')
+ self.assertEqual(cfg['username'], 'sakuya')
+ self.assertEqual(cfg['password'], 'izayoi')
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
self.assertEqual(cfg.get('Auth'), None)
@@ -1189,17 +1215,18 @@ class TestLoadJSONConfig(BaseTestCase):
def runTest(self):
folder = tempfile.mkdtemp()
self.tmp_folders.append(folder)
+ cfg_path = os.path.join(folder, '.dockercfg')
f = open(os.path.join(folder, '.dockercfg'), 'w')
auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii')
email_ = 'sakuya@scarlet.net'
- f.write('{{"{}": {{"auth": "{}", "email": "{}"}}}}\n'.format(
+ f.write('{{"{0}": {{"auth": "{1}", "email": "{2}"}}}}\n'.format(
docker.auth.INDEX_URL, auth_, email_))
f.close()
- cfg = docker.auth.load_config(folder)
+ cfg = docker.auth.load_config(cfg_path)
self.assertNotEqual(cfg[docker.auth.INDEX_URL], None)
cfg = cfg[docker.auth.INDEX_URL]
- self.assertEqual(cfg['username'], b'sakuya')
- self.assertEqual(cfg['password'], b'izayoi')
+ self.assertEqual(cfg['username'], 'sakuya')
+ self.assertEqual(cfg['password'], 'izayoi')
self.assertEqual(cfg['email'], 'sakuya@scarlet.net')
self.assertEqual(cfg.get('Auth'), None)
@@ -1248,4 +1275,5 @@ class UnixconnTestCase(unittest.TestCase):
if __name__ == '__main__':
c = docker.Client(base_url=DEFAULT_BASE_URL)
c.pull('busybox')
+ c.close()
unittest.main()
diff --git a/tests/test.py b/tests/test.py
index 2a22b3c..bd56d64 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -733,7 +733,6 @@ class DockerClientTest(Cleanup, unittest.TestCase):
args = fake_request.call_args
self.assertEqual(args[0][0], url_prefix + 'containers/create')
data = json.loads(args[1]['data'])
- self.assertEqual(data['HostConfig']['PublishAllPorts'], False)
port_bindings = data['HostConfig']['PortBindings']
self.assertTrue('1111/tcp' in port_bindings)
self.assertTrue('2222/tcp' in port_bindings)
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 9023b08..2708302 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -5,7 +5,8 @@ import unittest
from docker.client import Client
from docker.errors import DockerException
from docker.utils import (
- parse_repository_tag, parse_host, convert_filters, kwargs_from_env
+ parse_repository_tag, parse_host, convert_filters, kwargs_from_env,
+ create_host_config
)
@@ -95,6 +96,10 @@ class UtilsTest(unittest.TestCase):
for filters, expected in tests:
self.assertEqual(convert_filters(filters), expected)
+ def test_create_host_config(self):
+ empty_config = create_host_config()
+ self.assertEqual(empty_config, {})
+
if __name__ == '__main__':
unittest.main()