ソースを参照

Update docker-py

Using commit:
https://github.com/aanand/docker-py/commit/b31bb4d879c8ecc37491edb9f56369c513577918
Aanand Prasad 11 年 前
コミット
5166b2c1a8

+ 1 - 1
fig/packages/docker/__init__.py

@@ -15,4 +15,4 @@
 __title__ = 'docker-py'
 __version__ = '0.3.0'
 
-from .client import Client, APIError  # flake8: noqa
+from .client import Client # flake8: noqa

+ 9 - 6
fig/packages/docker/auth/auth.py

@@ -20,6 +20,7 @@ import os
 from fig.packages import six
 
 from ..utils import utils
+from .. import errors
 
 INDEX_URL = 'https://index.docker.io/v1/'
 DOCKER_CONFIG_FILENAME = '.dockercfg'
@@ -45,18 +46,19 @@ def expand_registry_url(hostname):
 
 def resolve_repository_name(repo_name):
     if '://' in repo_name:
-        raise ValueError('Repository name cannot contain a '
-                         'scheme ({0})'.format(repo_name))
+        raise errors.InvalidRepository(
+            'Repository name cannot contain a scheme ({0})'.format(repo_name))
     parts = repo_name.split('/', 1)
     if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost':
         # This is a docker index repo (ex: foo/bar or ubuntu)
         return INDEX_URL, repo_name
     if len(parts) < 2:
-        raise ValueError('Invalid repository name ({0})'.format(repo_name))
+        raise errors.InvalidRepository(
+            'Invalid repository name ({0})'.format(repo_name))
 
     if 'index.docker.io' in parts[0]:
-        raise ValueError('Invalid repository name,'
-                         'try "{0}" instead'.format(parts[1]))
+        raise errors.InvalidRepository(
+            'Invalid repository name, try "{0}" instead'.format(parts[1]))
 
     return expand_registry_url(parts[0]), parts[1]
 
@@ -147,7 +149,8 @@ def load_config(root=None):
             data.append(line.strip().split(' = ')[1])
         if len(data) < 2:
             # Not enough data
-            raise Exception('Invalid or empty configuration file!')
+            raise errors.InvalidConfigFile(
+                'Invalid or empty configuration file!')
 
         username, password = decode_auth(data[0])
         conf[INDEX_URL] = {

+ 25 - 41
fig/packages/docker/client.py

@@ -24,6 +24,7 @@ from fig.packages import six
 from .auth import auth
 from .unixconn import unixconn
 from .utils import utils
+from . import errors
 
 if not six.PY3:
     import websocket
@@ -33,41 +34,6 @@ DEFAULT_TIMEOUT_SECONDS = 60
 STREAM_HEADER_SIZE_BYTES = 8
 
 
-class APIError(requests.exceptions.HTTPError):
-    def __init__(self, message, response, explanation=None):
-        # requests 1.2 supports response as a keyword argument, but
-        # requests 1.1 doesn't
-        super(APIError, self).__init__(message)
-        self.response = response
-
-        self.explanation = explanation
-
-        if self.explanation is None and response.content:
-            self.explanation = response.content.strip()
-
-    def __str__(self):
-        message = super(APIError, self).__str__()
-
-        if self.is_client_error():
-            message = '%s Client Error: %s' % (
-                self.response.status_code, self.response.reason)
-
-        elif self.is_server_error():
-            message = '%s Server Error: %s' % (
-                self.response.status_code, self.response.reason)
-
-        if self.explanation:
-            message = '%s ("%s")' % (message, self.explanation)
-
-        return message
-
-    def is_client_error(self):
-        return 400 <= self.response.status_code < 500
-
-    def is_server_error(self):
-        return 500 <= self.response.status_code < 600
-
-
 class Client(requests.Session):
     def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
                  timeout=DEFAULT_TIMEOUT_SECONDS):
@@ -112,7 +78,7 @@ class Client(requests.Session):
         try:
             response.raise_for_status()
         except requests.exceptions.HTTPError as e:
-            raise APIError(e, response, explanation=explanation)
+            raise errors.APIError(e, response, explanation=explanation)
 
     def _result(self, response, json=False, binary=False):
         assert not (json and binary)
@@ -239,9 +205,23 @@ class Client(requests.Session):
 
     def _stream_helper(self, response):
         """Generator for data coming from a chunked-encoded HTTP response."""
-        for line in response.iter_lines(chunk_size=32):
-            if line:
-                yield line
+        socket_fp = self._get_raw_response_socket(response)
+        socket_fp.setblocking(1)
+        socket = socket_fp.makefile()
+        while True:
+            # 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.
+            size_line = socket.readline()
+            if size_line == '\r\n':
+                size_line = socket.readline()
+
+            size = int(size_line, 16)
+            if size <= 0:
+                break
+            data = socket.readline()
+            if not data:
+                break
+            yield data
 
     def _multiplexed_buffer_helper(self, response):
         """A generator of multiplexed data blocks read from a buffered
@@ -341,7 +321,7 @@ class Client(requests.Session):
               nocache=False, rm=False, stream=False, timeout=None):
         remote = context = headers = None
         if path is None and fileobj is None:
-            raise Exception("Either path or fileobj needs to be provided.")
+            raise TypeError("Either path or fileobj needs to be provided.")
 
         if fileobj is not None:
             context = utils.mkbuildcontext(fileobj)
@@ -714,8 +694,12 @@ class Client(requests.Session):
         }
         if binds:
             bind_pairs = [
-                '{0}:{1}'.format(host, dest) for host, dest in binds.items()
+                '%s:%s:%s' % (
+                    h, d['bind'],
+                    'ro' if 'ro' in d and d['ro'] else 'rw'
+                ) for h, d in binds.items()
             ]
+
             start_config['Binds'] = bind_pairs
 
         if volumes_from and not isinstance(volumes_from, six.string_types):

+ 61 - 0
fig/packages/docker/errors.py

@@ -0,0 +1,61 @@
+#    Copyright 2014 dotCloud inc.
+#    Licensed under the Apache License, Version 2.0 (the "License");
+#    you may not use this file except in compliance with the License.
+#    You may obtain a copy of the License at
+#
+#        http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS,
+#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#    See the License for the specific language governing permissions and
+#    limitations under the License.
+
+import requests
+
+
+class APIError(requests.exceptions.HTTPError):
+    def __init__(self, message, response, explanation=None):
+        # requests 1.2 supports response as a keyword argument, but
+        # requests 1.1 doesn't
+        super(APIError, self).__init__(message)
+        self.response = response
+
+        self.explanation = explanation
+
+        if self.explanation is None and response.content:
+            self.explanation = response.content.strip()
+
+    def __str__(self):
+        message = super(APIError, self).__str__()
+
+        if self.is_client_error():
+            message = '%s Client Error: %s' % (
+                self.response.status_code, self.response.reason)
+
+        elif self.is_server_error():
+            message = '%s Server Error: %s' % (
+                self.response.status_code, self.response.reason)
+
+        if self.explanation:
+            message = '%s ("%s")' % (message, self.explanation)
+
+        return message
+
+    def is_client_error(self):
+        return 400 <= self.response.status_code < 500
+
+    def is_server_error(self):
+        return 500 <= self.response.status_code < 600
+
+
+class DockerException(Exception):
+    pass
+
+
+class InvalidRepository(DockerException):
+    pass
+
+
+class InvalidConfigFile(DockerException):
+    pass