[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