[Python-modules-commits] [python-docker] 01/05: Import python-docker_1.10.3.orig.tar.gz

Ondřej Nový onovy at moszumanska.debian.org
Fri Oct 7 21:31:53 UTC 2016


This is an automated email from the git hooks/post-receive script.

onovy pushed a commit to branch master
in repository python-docker.

commit 5d8d4569fb7b4e869d9c88fa0f7f703c38f8131e
Author: Ondřej Nový <onovy at debian.org>
Date:   Fri Oct 7 22:56:59 2016 +0200

    Import python-docker_1.10.3.orig.tar.gz
---
 LICENSE                                        |  13 +-
 MANIFEST.in                                    |   1 +
 PKG-INFO                                       |   2 +-
 docker/__init__.py                             |  14 --
 docker/api/__init__.py                         |   4 +-
 docker/api/build.py                            |   6 +-
 docker/api/container.py                        |  25 ++-
 docker/api/exec_api.py                         |  14 +-
 docker/api/image.py                            | 216 ++++++++++++-------------
 docker/api/network.py                          |  35 +++-
 docker/api/service.py                          | 105 ++++++++++++
 docker/api/swarm.py                            |  78 +++++++++
 docker/api/volume.py                           |  13 +-
 docker/auth/auth.py                            |  98 ++++++++---
 docker/client.py                               |  70 +++++---
 docker/constants.py                            |   3 +-
 docker/errors.py                               |  13 --
 docker/transport/npipeconn.py                  |  10 +-
 docker/transport/npipesocket.py                |   2 +-
 docker/transport/unixconn.py                   |  33 ++--
 docker/types/__init__.py                       |   7 +
 docker/types/base.py                           |   7 +
 docker/{utils/types.py => types/containers.py} |   8 +-
 docker/types/services.py                       | 181 +++++++++++++++++++++
 docker/types/swarm.py                          |  40 +++++
 docker/utils/__init__.py                       |  14 +-
 docker/utils/decorators.py                     |   4 +-
 docker/utils/socket.py                         |  68 ++++++++
 docker/utils/types.py                          |  97 +----------
 docker/utils/utils.py                          | 145 ++++++++++++-----
 docker/version.py                              |   2 +-
 docker_py.egg-info/PKG-INFO                    |   2 +-
 docker_py.egg-info/SOURCES.txt                 |  10 ++
 docker_py.egg-info/requires.txt                |   3 +-
 requirements.txt                               |   3 +-
 setup.cfg                                      |   3 +
 setup.py                                       |   6 +-
 tests/helpers.py                               |  55 -------
 tests/integration/container_test.py            |  48 ++++--
 tests/integration/exec_test.py                 |  28 +---
 tests/integration/image_test.py                |  42 +++++
 tests/integration/network_test.py              |  78 ++++++++-
 tests/integration/service_test.py              | 189 ++++++++++++++++++++++
 tests/integration/swarm_test.py                | 145 +++++++++++++++++
 tests/unit/api_test.py                         |  73 ++++++---
 tests/unit/auth_test.py                        |  26 ++-
 tests/unit/build_test.py                       |  61 ++++++-
 tests/unit/client_test.py                      |   8 +
 tests/unit/container_test.py                   | 117 +++++++++++++-
 tests/unit/exec_test.py                        |  32 +++-
 tests/unit/fake_api.py                         |  50 +-----
 tests/unit/image_test.py                       |  26 +++
 tests/unit/network_test.py                     |   2 +-
 tests/unit/utils_test.py                       |  96 ++++++++++-
 tests/unit/volume_test.py                      |  16 ++
 55 files changed, 1882 insertions(+), 565 deletions(-)

diff --git a/LICENSE b/LICENSE
index d645695..75191a4 100644
--- a/LICENSE
+++ b/LICENSE
@@ -176,18 +176,7 @@
 
    END OF TERMS AND CONDITIONS
 
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
+   Copyright 2016 Docker, Inc.
 
    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
diff --git a/MANIFEST.in b/MANIFEST.in
index ee6cdbb..f492931 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,3 +1,4 @@
+include setup.cfg
 include test-requirements.txt
 include requirements.txt
 include README.md
diff --git a/PKG-INFO b/PKG-INFO
index 79c1598..e468347 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: docker-py
-Version: 1.9.0
+Version: 1.10.3
 Summary: Python client for Docker.
 Home-page: https://github.com/docker/docker-py/
 Author: UNKNOWN
diff --git a/docker/__init__.py b/docker/__init__.py
index 84d0734..ad53805 100644
--- a/docker/__init__.py
+++ b/docker/__init__.py
@@ -1,17 +1,3 @@
-# Copyright 2013 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.
-
 from .version import version, version_info
 
 __version__ = version
diff --git a/docker/api/__init__.py b/docker/api/__init__.py
index 9e74428..bc7e93c 100644
--- a/docker/api/__init__.py
+++ b/docker/api/__init__.py
@@ -4,5 +4,7 @@ from .container import ContainerApiMixin
 from .daemon import DaemonApiMixin
 from .exec_api import ExecApiMixin
 from .image import ImageApiMixin
-from .volume import VolumeApiMixin
 from .network import NetworkApiMixin
+from .service import ServiceApiMixin
+from .swarm import SwarmApiMixin
+from .volume import VolumeApiMixin
diff --git a/docker/api/build.py b/docker/api/build.py
index 971a50e..7403716 100644
--- a/docker/api/build.py
+++ b/docker/api/build.py
@@ -18,7 +18,8 @@ class BuildApiMixin(object):
               custom_context=False, encoding=None, pull=False,
               forcerm=False, dockerfile=None, container_limits=None,
               decode=False, buildargs=None, gzip=False):
-        remote = context = headers = None
+        remote = context = None
+        headers = {}
         container_limits = container_limits or {}
         if path is None and fileobj is None:
             raise TypeError("Either path or fileobj needs to be provided.")
@@ -134,8 +135,7 @@ class BuildApiMixin(object):
                     ', '.join(repr(k) for k in self._auth_configs.keys())
                 )
             )
-            if headers is None:
-                headers = {}
+
             if utils.compare_version('1.19', self._version) >= 0:
                 headers['X-Registry-Config'] = auth.encode_header(
                     self._auth_configs
diff --git a/docker/api/container.py b/docker/api/container.py
index 9cc14db..b8507d8 100644
--- a/docker/api/container.py
+++ b/docker/api/container.py
@@ -15,12 +15,18 @@ class ContainerApiMixin(object):
             'logs': logs and 1 or 0,
             'stdout': stdout and 1 or 0,
             'stderr': stderr and 1 or 0,
-            'stream': stream and 1 or 0,
+            'stream': stream and 1 or 0
         }
+
+        headers = {
+            'Connection': 'Upgrade',
+            'Upgrade': 'tcp'
+        }
+
         u = self._url("/containers/{0}/attach", container)
-        response = self._post(u, params=params, stream=stream)
+        response = self._post(u, headers=headers, params=params, stream=stream)
 
-        return self._get_result(container, stream, response)
+        return self._read_from_socket(response, stream)
 
     @utils.check_resource
     def attach_socket(self, container, params=None, ws=False):
@@ -34,9 +40,18 @@ class ContainerApiMixin(object):
         if ws:
             return self._attach_websocket(container, params)
 
+        headers = {
+            'Connection': 'Upgrade',
+            'Upgrade': 'tcp'
+        }
+
         u = self._url("/containers/{0}/attach", container)
-        return self._get_raw_response_socket(self.post(
-            u, None, params=self._attach_params(params), stream=True))
+        return self._get_raw_response_socket(
+            self.post(
+                u, None, params=self._attach_params(params), stream=True,
+                headers=headers
+            )
+        )
 
     @utils.check_resource
     def commit(self, container, repository=None, tag=None, message=None,
diff --git a/docker/api/exec_api.py b/docker/api/exec_api.py
index f0e4afa..6e49996 100644
--- a/docker/api/exec_api.py
+++ b/docker/api/exec_api.py
@@ -56,8 +56,6 @@ class ExecApiMixin(object):
     def exec_start(self, exec_id, detach=False, tty=False, stream=False,
                    socket=False):
         # we want opened socket if socket == True
-        if socket:
-            stream = True
         if isinstance(exec_id, dict):
             exec_id = exec_id.get('Id')
 
@@ -66,10 +64,18 @@ class ExecApiMixin(object):
             'Detach': detach
         }
 
+        headers = {} if detach else {
+            'Connection': 'Upgrade',
+            'Upgrade': 'tcp'
+        }
+
         res = self._post_json(
-            self._url('/exec/{0}/start', exec_id), data=data, stream=stream
+            self._url('/exec/{0}/start', exec_id),
+            headers=headers,
+            data=data,
+            stream=True
         )
 
         if socket:
             return self._get_raw_response_socket(res)
-        return self._get_result_tty(stream, res, tty)
+        return self._read_from_socket(res, stream)
diff --git a/docker/api/image.py b/docker/api/image.py
index 3e66347..7f25f9d 100644
--- a/docker/api/image.py
+++ b/docker/api/image.py
@@ -1,4 +1,5 @@
 import logging
+import os
 import six
 import warnings
 
@@ -42,87 +43,79 @@ class ImageApiMixin(object):
             return [x['Id'] for x in res]
         return res
 
-    def import_image(self, src=None, repository=None, tag=None, image=None):
-        if src:
-            if isinstance(src, six.string_types):
-                try:
-                    result = self.import_image_from_file(
-                        src, repository=repository, tag=tag)
-                except IOError:
-                    result = self.import_image_from_url(
-                        src, repository=repository, tag=tag)
-            else:
-                result = self.import_image_from_data(
-                    src, repository=repository, tag=tag)
-        elif image:
-            result = self.import_image_from_image(
-                image, repository=repository, tag=tag)
-        else:
-            raise Exception("Must specify a src or image")
-
-        return result
-
-    def import_image_from_data(self, data, repository=None, tag=None):
-        u = self._url("/images/create")
-        params = {
-            'fromSrc': '-',
-            'repo': repository,
-            'tag': tag
-        }
-        headers = {
-            'Content-Type': 'application/tar',
-        }
-        return self._result(
-            self._post(u, data=data, params=params, headers=headers))
+    def import_image(self, src=None, repository=None, tag=None, image=None,
+                     changes=None, stream_src=False):
+        if not (src or image):
+            raise errors.DockerException(
+                'Must specify src or image to import from'
+            )
+        u = self._url('/images/create')
 
-    def import_image_from_file(self, filename, repository=None, tag=None):
-        u = self._url("/images/create")
-        params = {
-            'fromSrc': '-',
-            'repo': repository,
-            'tag': tag
-        }
-        headers = {
-            'Content-Type': 'application/tar',
-        }
-        with open(filename, 'rb') as f:
+        params = _import_image_params(
+            repository, tag, image,
+            src=(src if isinstance(src, six.string_types) else None),
+            changes=changes
+        )
+        headers = {'Content-Type': 'application/tar'}
+
+        if image or params.get('fromSrc') != '-':  # from image or URL
+            return self._result(
+                self._post(u, data=None, params=params)
+            )
+        elif isinstance(src, six.string_types):  # from file path
+            with open(src, 'rb') as f:
+                return self._result(
+                    self._post(
+                        u, data=f, params=params, headers=headers, timeout=None
+                    )
+                )
+        else:  # from raw data
+            if stream_src:
+                headers['Transfer-Encoding'] = 'chunked'
             return self._result(
-                self._post(u, data=f, params=params, headers=headers,
-                           timeout=None))
+                self._post(u, data=src, params=params, headers=headers)
+            )
 
-    def import_image_from_stream(self, stream, repository=None, tag=None):
-        u = self._url("/images/create")
-        params = {
-            'fromSrc': '-',
-            'repo': repository,
-            'tag': tag
-        }
-        headers = {
-            'Content-Type': 'application/tar',
-            'Transfer-Encoding': 'chunked',
-        }
+    def import_image_from_data(self, data, repository=None, tag=None,
+                               changes=None):
+        u = self._url('/images/create')
+        params = _import_image_params(
+            repository, tag, src='-', changes=changes
+        )
+        headers = {'Content-Type': 'application/tar'}
         return self._result(
-            self._post(u, data=stream, params=params, headers=headers))
+            self._post(
+                u, data=data, params=params, headers=headers, timeout=None
+            )
+        )
+        return self.import_image(
+            src=data, repository=repository, tag=tag, changes=changes
+        )
 
-    def import_image_from_url(self, url, repository=None, tag=None):
-        u = self._url("/images/create")
-        params = {
-            'fromSrc': url,
-            'repo': repository,
-            'tag': tag
-        }
-        return self._result(
-            self._post(u, data=None, params=params))
+    def import_image_from_file(self, filename, repository=None, tag=None,
+                               changes=None):
+        return self.import_image(
+            src=filename, repository=repository, tag=tag, changes=changes
+        )
 
-    def import_image_from_image(self, image, repository=None, tag=None):
-        u = self._url("/images/create")
-        params = {
-            'fromImage': image,
-            'repo': repository,
-            'tag': tag
-        }
-        return self._result(
-            self._post(u, data=None, params=params))
+    def import_image_from_stream(self, stream, repository=None, tag=None,
+                                 changes=None):
+        return self.import_image(
+            src=stream, stream_src=True, repository=repository, tag=tag,
+            changes=changes
+        )
+
+    def import_image_from_url(self, url, repository=None, tag=None,
+                              changes=None):
+        return self.import_image(
+            src=url, repository=repository, tag=tag, changes=changes
+        )
+
+    def import_image_from_image(self, image, repository=None, tag=None,
+                                changes=None):
+        return self.import_image(
+            image=image, repository=repository, tag=tag, changes=changes
+        )
 
     @utils.check_resource
     def insert(self, image, url, path):
@@ -166,28 +159,10 @@ class ImageApiMixin(object):
         headers = {}
 
         if utils.compare_version('1.5', self._version) >= 0:
-            # If we don't have any auth data so far, try reloading the config
-            # file one more time in case anything showed up in there.
             if auth_config is None:
-                log.debug('Looking for auth config')
-                if not self._auth_configs:
-                    log.debug(
-                        "No auth config in memory - loading from filesystem"
-                    )
-                    self._auth_configs = auth.load_config()
-                authcfg = auth.resolve_authconfig(self._auth_configs, registry)
-                # Do not fail here if no authentication exists for this
-                # specific registry as we can have a readonly pull. Just
-                # put the header if we can.
-                if authcfg:
-                    log.debug('Found auth config')
-                    # auth_config needs to be a dict in the format used by
-                    # auth.py username , password, serveraddress, email
-                    headers['X-Registry-Auth'] = auth.encode_header(
-                        authcfg
-                    )
-                else:
-                    log.debug('No auth config found')
+                header = auth.get_config_header(self, registry)
+                if header:
+                    headers['X-Registry-Auth'] = header
             else:
                 log.debug('Sending supplied auth config')
                 headers['X-Registry-Auth'] = auth.encode_header(auth_config)
@@ -205,7 +180,7 @@ class ImageApiMixin(object):
         return self._result(response)
 
     def push(self, repository, tag=None, stream=False,
-             insecure_registry=False, decode=False):
+             insecure_registry=False, auth_config=None, decode=False):
         if insecure_registry:
             warnings.warn(
                 INSECURE_REGISTRY_DEPRECATION_WARNING.format('push()'),
@@ -222,17 +197,13 @@ class ImageApiMixin(object):
         headers = {}
 
         if utils.compare_version('1.5', self._version) >= 0:
-            # If we don't have any auth data so far, try reloading the config
-            # file one more time in case anything showed up in there.
-            if not self._auth_configs:
-                self._auth_configs = auth.load_config()
-            authcfg = auth.resolve_authconfig(self._auth_configs, registry)
-
-            # Do not fail here if no authentication exists for this specific
-            # registry as we can have a readonly pull. Just put the header if
-            # we can.
-            if authcfg:
-                headers['X-Registry-Auth'] = auth.encode_header(authcfg)
+            if auth_config is None:
+                header = auth.get_config_header(self, registry)
+                if header:
+                    headers['X-Registry-Auth'] = header
+            else:
+                log.debug('Sending supplied auth config')
+                headers['X-Registry-Auth'] = auth.encode_header(auth_config)
 
         response = self._post_json(
             u, None, headers=headers, stream=stream, params=params
@@ -268,3 +239,32 @@ class ImageApiMixin(object):
         res = self._post(url, params=params)
         self._raise_for_status(res)
         return res.status_code == 201
+
+
+def is_file(src):
+    try:
+        return (
+            isinstance(src, six.string_types) and
+            os.path.isfile(src)
+        )
+    except TypeError:  # a data string will make isfile() raise a TypeError
+        return False
+
+
+def _import_image_params(repo, tag, image=None, src=None,
+                         changes=None):
+    params = {
+        'repo': repo,
+        'tag': tag,
+    }
+    if image:
+        params['fromImage'] = image
+    elif src and not is_file(src):
+        params['fromSrc'] = src
+    else:
+        params['fromSrc'] = '-'
+
+    if changes:
+        params['changes'] = changes
+
+    return params
diff --git a/docker/api/network.py b/docker/api/network.py
index a35f0a4..0ee0dab 100644
--- a/docker/api/network.py
+++ b/docker/api/network.py
@@ -22,7 +22,8 @@ class NetworkApiMixin(object):
 
     @minimum_version('1.21')
     def create_network(self, name, driver=None, options=None, ipam=None,
-                       check_duplicate=None, internal=False):
+                       check_duplicate=None, internal=False, labels=None,
+                       enable_ipv6=False):
         if options is not None and not isinstance(options, dict):
             raise TypeError('options must be a dictionary')
 
@@ -34,6 +35,22 @@ class NetworkApiMixin(object):
             'CheckDuplicate': check_duplicate
         }
 
+        if labels is not None:
+            if version_lt(self._version, '1.23'):
+                raise InvalidVersion(
+                    'network labels were introduced in API 1.23'
+                )
+            if not isinstance(labels, dict):
+                raise TypeError('labels must be a dictionary')
+            data["Labels"] = labels
+
+        if enable_ipv6:
+            if version_lt(self._version, '1.23'):
+                raise InvalidVersion(
+                    'enable_ipv6 was introduced in API 1.23'
+                )
+            data['EnableIPv6'] = True
+
         if internal:
             if version_lt(self._version, '1.22'):
                 raise InvalidVersion('Internal networks are not '
@@ -60,12 +77,13 @@ class NetworkApiMixin(object):
     @minimum_version('1.21')
     def connect_container_to_network(self, container, net_id,
                                      ipv4_address=None, ipv6_address=None,
-                                     aliases=None, links=None):
+                                     aliases=None, links=None,
+                                     link_local_ips=None):
         data = {
             "Container": container,
             "EndpointConfig": self.create_endpoint_config(
                 aliases=aliases, links=links, ipv4_address=ipv4_address,
-                ipv6_address=ipv6_address
+                ipv6_address=ipv6_address, link_local_ips=link_local_ips
             ),
         }
 
@@ -75,8 +93,15 @@ class NetworkApiMixin(object):
 
     @check_resource
     @minimum_version('1.21')
-    def disconnect_container_from_network(self, container, net_id):
-        data = {"container": container}
+    def disconnect_container_from_network(self, container, net_id,
+                                          force=False):
+        data = {"Container": container}
+        if force:
+            if version_lt(self._version, '1.22'):
+                raise InvalidVersion(
+                    'Forced disconnect was introduced in API 1.22'
+                )
+            data['Force'] = force
         url = self._url("/networks/{0}/disconnect", net_id)
         res = self._post_json(url, data=data)
         self._raise_for_status(res)
diff --git a/docker/api/service.py b/docker/api/service.py
new file mode 100644
index 0000000..baebbad
--- /dev/null
+++ b/docker/api/service.py
@@ -0,0 +1,105 @@
+from .. import errors
+from .. import utils
+from ..auth import auth
+
+
+class ServiceApiMixin(object):
+    @utils.minimum_version('1.24')
+    def create_service(
+            self, task_template, name=None, labels=None, mode=None,
+            update_config=None, networks=None, endpoint_config=None
+    ):
+        url = self._url('/services/create')
+        headers = {}
+        image = task_template.get('ContainerSpec', {}).get('Image', None)
+        if image is None:
+            raise errors.DockerException(
+                'Missing mandatory Image key in ContainerSpec'
+            )
+        registry, repo_name = auth.resolve_repository_name(image)
+        auth_header = auth.get_config_header(self, registry)
+        if auth_header:
+            headers['X-Registry-Auth'] = auth_header
+        data = {
+            'Name': name,
+            'Labels': labels,
+            'TaskTemplate': task_template,
+            'Mode': mode,
+            'UpdateConfig': update_config,
+            'Networks': networks,
+            'Endpoint': endpoint_config
+        }
+        return self._result(
+            self._post_json(url, data=data, headers=headers), True
+        )
+
+    @utils.minimum_version('1.24')
+    @utils.check_resource
+    def inspect_service(self, service):
+        url = self._url('/services/{0}', service)
+        return self._result(self._get(url), True)
+
+    @utils.minimum_version('1.24')
+    @utils.check_resource
+    def inspect_task(self, task):
+        url = self._url('/tasks/{0}', task)
+        return self._result(self._get(url), True)
+
+    @utils.minimum_version('1.24')
+    @utils.check_resource
+    def remove_service(self, service):
+        url = self._url('/services/{0}', service)
+        resp = self._delete(url)
+        self._raise_for_status(resp)
+        return True
+
+    @utils.minimum_version('1.24')
+    def services(self, filters=None):
+        params = {
+            'filters': utils.convert_filters(filters) if filters else None
+        }
+        url = self._url('/services')
+        return self._result(self._get(url, params=params), True)
+
+    @utils.minimum_version('1.24')
+    def tasks(self, filters=None):
+        params = {
+            'filters': utils.convert_filters(filters) if filters else None
+        }
+        url = self._url('/tasks')
+        return self._result(self._get(url, params=params), True)
+
+    @utils.minimum_version('1.24')
+    @utils.check_resource
+    def update_service(self, service, version, task_template=None, name=None,
+                       labels=None, mode=None, update_config=None,
+                       networks=None, endpoint_config=None):
+        url = self._url('/services/{0}/update', service)
+        data = {}
+        headers = {}
+        if name is not None:
+            data['Name'] = name
+        if labels is not None:
+            data['Labels'] = labels
+        if mode is not None:
+            data['Mode'] = mode
+        if task_template is not None:
+            image = task_template.get('ContainerSpec', {}).get('Image', None)
+            if image is not None:
+                registry, repo_name = auth.resolve_repository_name(image)
+                auth_header = auth.get_config_header(self, registry)
+                if auth_header:
+                    headers['X-Registry-Auth'] = auth_header
+            data['TaskTemplate'] = task_template
+        if update_config is not None:
+            data['UpdateConfig'] = update_config
+        if networks is not None:
+            data['Networks'] = networks
+        if endpoint_config is not None:
+            data['Endpoint'] = endpoint_config
+
+        resp = self._post_json(
+            url, data=data, params={'version': version}, headers=headers
+        )
+        self._raise_for_status(resp)
+        return True
diff --git a/docker/api/swarm.py b/docker/api/swarm.py
new file mode 100644
index 0000000..d099364
--- /dev/null
+++ b/docker/api/swarm.py
@@ -0,0 +1,78 @@
+from .. import utils
+import logging
+log = logging.getLogger(__name__)
+
+
+class SwarmApiMixin(object):
+
+    def create_swarm_spec(self, *args, **kwargs):
+        return utils.SwarmSpec(*args, **kwargs)
+
+    @utils.minimum_version('1.24')
+    def init_swarm(self, advertise_addr=None, listen_addr='0.0.0.0:2377',
+                   force_new_cluster=False, swarm_spec=None):
+        url = self._url('/swarm/init')
+        if swarm_spec is not None and not isinstance(swarm_spec, dict):
+            raise TypeError('swarm_spec must be a dictionary')
+        data = {
+            'AdvertiseAddr': advertise_addr,
+            'ListenAddr': listen_addr,
+            'ForceNewCluster': force_new_cluster,
+            'Spec': swarm_spec,
+        }
+        response = self._post_json(url, data=data)
+        self._raise_for_status(response)
+        return True
+
+    @utils.minimum_version('1.24')
+    def inspect_swarm(self):
+        url = self._url('/swarm')
+        return self._result(self._get(url), True)
+
+    @utils.check_resource
+    @utils.minimum_version('1.24')
+    def inspect_node(self, node_id):
+        url = self._url('/nodes/{0}', node_id)
+        return self._result(self._get(url), True)
+
+    @utils.minimum_version('1.24')
+    def join_swarm(self, remote_addrs, join_token, listen_addr=None,
+                   advertise_addr=None):
+        data = {
+            "RemoteAddrs": remote_addrs,
+            "ListenAddr": listen_addr,
+            "JoinToken": join_token,
+            "AdvertiseAddr": advertise_addr,
+        }
+        url = self._url('/swarm/join')
+        response = self._post_json(url, data=data)
+        self._raise_for_status(response)
+        return True
+
+    @utils.minimum_version('1.24')
+    def leave_swarm(self, force=False):
+        url = self._url('/swarm/leave')
+        response = self._post(url, params={'force': force})
+        self._raise_for_status(response)
+        return True
+
+    @utils.minimum_version('1.24')
+    def nodes(self, filters=None):
+        url = self._url('/nodes')
+        params = {}
+        if filters:
+            params['filters'] = utils.convert_filters(filters)
+
+        return self._result(self._get(url, params=params), True)
+
+    @utils.minimum_version('1.24')
+    def update_swarm(self, version, swarm_spec=None, rotate_worker_token=False,
+                     rotate_manager_token=False):
+        url = self._url('/swarm/update')
+        response = self._post_json(url, data=swarm_spec, params={
+            'rotateWorkerToken': rotate_worker_token,
+            'rotateManagerToken': rotate_manager_token,
+            'version': version
+        })
+        self._raise_for_status(response)
+        return True
diff --git a/docker/api/volume.py b/docker/api/volume.py
index bb8b39b..afc72cb 100644
--- a/docker/api/volume.py
+++ b/docker/api/volume.py
@@ -1,3 +1,4 @@
+from .. import errors
 from .. import utils
 
 
@@ -11,7 +12,7 @@ class VolumeApiMixin(object):
         return self._result(self._get(url, params=params), True)
 
     @utils.minimum_version('1.21')
-    def create_volume(self, name, driver=None, driver_opts=None):
+    def create_volume(self, name, driver=None, driver_opts=None, labels=None):
         url = self._url('/volumes/create')
         if driver_opts is not None and not isinstance(driver_opts, dict):
             raise TypeError('driver_opts must be a dictionary')
@@ -21,6 +22,16 @@ class VolumeApiMixin(object):
             'Driver': driver,
             'DriverOpts': driver_opts,
         }
+
+        if labels is not None:
+            if utils.compare_version('1.23', self._version) < 0:
+                raise errors.InvalidVersion(
+                    'volume labels were introduced in API 1.23'
+                )
+            if not isinstance(labels, dict):
+                raise TypeError('labels must be a dictionary')
+            data["Labels"] = labels
+
         return self._result(self._post_json(url, data=data), True)
 
     @utils.minimum_version('1.21')
diff --git a/docker/auth/auth.py b/docker/auth/auth.py
index d23e6f3..dc0baea 100644
--- a/docker/auth/auth.py
+++ b/docker/auth/auth.py
@@ -1,22 +1,9 @@
-# Copyright 2013 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 base64
 import json
 import logging
 import os
 
+import dockerpycreds
 import six
 
 from .. import errors
@@ -25,6 +12,7 @@ INDEX_NAME = 'docker.io'
 INDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME)
 DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json')
 LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg'
+TOKEN_USERNAME = '<token>'
 
 log = logging.getLogger(__name__)
 
@@ -51,6 +39,26 @@ def resolve_index_name(index_name):
     return index_name
 
 
+def get_config_header(client, registry):
+    log.debug('Looking for auth config')
+    if not client._auth_configs:
+        log.debug(
+            "No auth config in memory - loading from filesystem"
+        )
+        client._auth_configs = load_config()
+    authcfg = resolve_authconfig(client._auth_configs, registry)
+    # Do not fail here if no authentication exists for this
+    # specific registry as we can have a readonly pull. Just
+    # put the header if we can.
+    if authcfg:
+        log.debug('Found auth config')
+        # auth_config needs to be a dict in the format used by
+        # auth.py username , password, serveraddress, email
+        return encode_header(authcfg)
+    log.debug('No auth config found')
+    return None
+
+
 def split_repo_name(repo_name):
     parts = repo_name.split('/', 1)
     if len(parts) == 1 or (
@@ -68,6 +76,13 @@ def resolve_authconfig(authconfig, registry=None):
     with full URLs are stripped down to hostnames before checking for a match.
     Returns None if no match was found.
     """
+    if 'credsStore' in authconfig:
+        log.debug(
+            'Using credentials store "{0}"'.format(authconfig['credsStore'])
+        )
+        return _resolve_authconfig_credstore(
+            authconfig, registry, authconfig['credsStore']
+        )
     # Default to the public index server
     registry = resolve_index_name(registry) if registry else INDEX_NAME
     log.debug("Looking for auth entry for {0}".format(repr(registry)))
@@ -85,6 +100,35 @@ def resolve_authconfig(authconfig, registry=None):
     return None
 
 
+def _resolve_authconfig_credstore(authconfig, registry, credstore_name):
+    if not registry or registry == INDEX_NAME:
+        # The ecosystem is a little schizophrenic with index.docker.io VS
+        # docker.io - in that case, it seems the full URL is necessary.
+        registry = 'https://index.docker.io/v1/'
+    log.debug("Looking for auth entry for {0}".format(repr(registry)))
+    store = dockerpycreds.Store(credstore_name)
+    try:
+        data = store.get(registry)
+        res = {
+            'ServerAddress': registry,
+        }
+        if data['Username'] == TOKEN_USERNAME:
+            res['IdentityToken'] = data['Secret']
+        else:
+            res.update({
+                'Username': data['Username'],
+                'Password': data['Secret'],
+            })
+        return res
+    except dockerpycreds.CredentialsNotFound as e:
+        log.debug('No entry found')
+        return None
+    except dockerpycreds.StoreError as e:
+        raise errors.DockerException(
+            'Credentials store error: {0}'.format(repr(e))
+        )
+
+
 def convert_to_hostname(url):
     return url.replace('http://', '').replace('https://', '').split('/', 1)[0]
 
@@ -130,6 +174,15 @@ def parse_auth(entries, raise_on_error=False):
                     'Invalid configuration for registry {0}'.format(registry)
                 )
             return {}
+        if 'identitytoken' in entry:
+            log.debug('Found an IdentityToken entry for registry {0}'.format(
+                registry
+            ))
+            conf[registry] = {
+                'IdentityToken': entry['identitytoken']
+            }
+            continue  # Other values are irrelevant if we have a token, skip.
+
         if 'auth' not in entry:
             # Starting with engine v1.11 (API 1.23), an empty dictionary is
             # a valid value in the auths config.
@@ -138,13 +191,15 @@ def parse_auth(entries, raise_on_error=False):
                 'Auth data for {0} is absent. Client might be using a '
                 'credentials store instead.'
             )
-            return {}
+            conf[registry] = {}
+            continue
 
         username, password = decode_auth(entry['auth'])
         log.debug(
             'Found entry (registry={0}, username={1})'
             .format(repr(registry), repr(username))
         )
+
         conf[registry] = {
             'username': username,
             'password': password,
@@ -160,18 +215,24 @@ def find_config_file(config_path=None):
         os.path.basename(DOCKER_CONFIG_FILENAME)
     ) if os.environ.get('DOCKER_CONFIG') else None
 
-    paths = [
+    paths = filter(None, [
         config_path,  # 1
         environment_path,  # 2
         os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME),  # 3
         os.path.join(
             os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME
         )  # 4
-    ]
+    ])
+
+    log.debug("Trying paths: {0}".format(repr(paths)))
 
     for path in paths:
-        if path and os.path.exists(path):
+        if os.path.exists(path):
+            log.debug("Found file at path: {0}".format(path))
             return path
+
+    log.debug("No config file found")
+
     return None
 
 
@@ -186,7 +247,6 @@ def load_config(config_path=None):
     config_file = find_config_file(config_path)
 
     if not config_file:
-        log.debug("File doesn't exist")
         return {}
 
     try:
diff --git a/docker/client.py b/docker/client.py
index 81e9de9..47ad09e 100644
--- a/docker/client.py
+++ b/docker/client.py
@@ -1,19 +1,6 @@
... 2815 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-docker.git



More information about the Python-modules-commits mailing list