diff options
author | Ondřej Nový <onovy@debian.org> | 2016-10-07 22:56:59 +0200 |
---|---|---|
committer | Ondřej Nový <onovy@debian.org> | 2016-10-07 22:56:59 +0200 |
commit | 5d8d4569fb7b4e869d9c88fa0f7f703c38f8131e (patch) | |
tree | caa16dd2da7ca381a4cfe7d34bb69b9936422cdc /docker/api | |
parent | 49556cb01423a89a6d2ce7a58b1f5cb64dde0ff3 (diff) |
Import python-docker_1.10.3.orig.tar.gz
Diffstat (limited to 'docker/api')
-rw-r--r-- | docker/api/__init__.py | 4 | ||||
-rw-r--r-- | docker/api/build.py | 6 | ||||
-rw-r--r-- | docker/api/container.py | 25 | ||||
-rw-r--r-- | docker/api/exec_api.py | 14 | ||||
-rw-r--r-- | docker/api/image.py | 216 | ||||
-rw-r--r-- | docker/api/network.py | 35 | ||||
-rw-r--r-- | docker/api/service.py | 105 | ||||
-rw-r--r-- | docker/api/swarm.py | 78 | ||||
-rw-r--r-- | docker/api/volume.py | 13 |
9 files changed, 369 insertions, 127 deletions
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') |