diff options
-rw-r--r-- | PKG-INFO | 2 | ||||
-rw-r--r-- | README.md | 45 | ||||
-rw-r--r-- | docker/auth/auth.py | 18 | ||||
-rw-r--r-- | docker/client.py | 88 | ||||
-rw-r--r-- | docker/ssladapter/ssladapter.py | 20 | ||||
-rw-r--r-- | docker/utils/utils.py | 27 | ||||
-rw-r--r-- | docker/version.py | 2 | ||||
-rw-r--r-- | docker_py.egg-info/PKG-INFO | 2 | ||||
-rw-r--r-- | docker_py.egg-info/requires.txt | 8 | ||||
-rw-r--r-- | setup.py | 17 |
10 files changed, 182 insertions, 47 deletions
@@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: docker-py -Version: 0.4.0 +Version: 0.5.0 Summary: Python client for Docker. Home-page: UNKNOWN Author: UNKNOWN @@ -1,7 +1,7 @@ docker-py ========= -[![Build Status](https://travis-ci.org/dotcloud/docker-py.png)](https://travis-ci.org/dotcloud/docker-py) +[![Build Status](https://travis-ci.org/docker/docker-py.png)](https://travis-ci.org/docker/docker-py) An API client for docker written in Python @@ -78,9 +78,17 @@ to those for the `docker run` command except it doesn't support the attach options (`-a`). See "Port bindings" and "Using volumes" below for more information on how to create port bindings and volume mappings. +`command` is the command to be run in the container. String or list types are +accepted. + The `environment` variable accepts a dictionary or a list of strings in the following format `["PASSWORD=xxx"]` or `{"PASSWORD": "xxx"}`. +The `mem_limit` variable accepts float values (which represent the memory limit +of the created container in bytes) or a string with a units identification char +('100000b', 1000k', 128m', '1g'). If a string is specified without a units +character, bytes are assumed as an intended unit. + `volumes_from` and `dns` arguments raise TypeError exception if they are used against v1.10 of docker remote API. Those arguments should be passed to `start()` instead. @@ -193,7 +201,7 @@ c.pull(repository, tag=None, stream=False) Identical to the `docker pull` command. ```python -c.push(repository, stream=False) +c.push(repository, tag=None, stream=False) ``` Identical to the `docker push` command. @@ -224,7 +232,8 @@ Identical to the `docker search` command. ```python c.start(container, binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, - dns=None, dns_search=None, volumes_from=None, network_mode=None) + dns=None, dns_search=None, volumes_from=None, network_mode=None, + restart_policy=None, cap_add=None, cap_drop=None) ``` Similar to the `docker start` command, but doesn't support attach @@ -250,6 +259,36 @@ docker bridge, 'none': no networking for this container, 'container:[name|id]': reuses another container network stack), 'host': use the host network stack inside the container. +`restart_policy` is available since v1.2.0 and sets the RestartPolicy for how a container should or should not be +restarted on exit. By default the policy is set to no meaning do not restart the container when it exits. +The user may specify the restart policy as a dictionary for example: +for example: +``` +{ + "MaximumRetryCount": 0, + "Name": "always" +} +``` +for always restarting the container on exit or can specify to restart the container to restart on failure and can limit +number of restarts. +for example: +``` +{ + "MaximumRetryCount": 5, + "Name": "on-failure" +} +``` + +`cap_add` and `cap_drop` are available since v1.2.0 and can be used to add or drop certain capabilities. +The user may specify the capabilities as an array for example: +``` +[ + "SYS_ADMIN", + "MKNOD" +] +``` + + ```python c.stop(container, timeout=10) ``` diff --git a/docker/auth/auth.py b/docker/auth/auth.py index 0bd386d..c13eed4 100644 --- a/docker/auth/auth.py +++ b/docker/auth/auth.py @@ -34,17 +34,22 @@ def swap_protocol(url): return url -def expand_registry_url(hostname): +def expand_registry_url(hostname, insecure=False): if hostname.startswith('http:') or hostname.startswith('https:'): if '/' not in hostname[9:]: hostname = hostname + '/v1/' return hostname if utils.ping('https://' + hostname + '/v1/_ping'): return 'https://' + hostname + '/v1/' - return 'http://' + hostname + '/v1/' + elif insecure: + return 'http://' + hostname + '/v1/' + else: + raise errors.DockerException( + "HTTPS endpoint unresponsive and insecure mode isn't enabled." + ) -def resolve_repository_name(repo_name): +def resolve_repository_name(repo_name, insecure=False): if '://' in repo_name: raise errors.InvalidRepository( 'Repository name cannot contain a scheme ({0})'.format(repo_name)) @@ -56,11 +61,12 @@ def resolve_repository_name(repo_name): raise errors.InvalidRepository( 'Invalid repository name ({0})'.format(repo_name)) - if 'index.docker.io' in parts[0]: + if 'index.docker.io' in parts[0] or 'registry.hub.docker.com' in parts[0]: raise errors.InvalidRepository( - 'Invalid repository name, try "{0}" instead'.format(parts[1])) + 'Invalid repository name, try "{0}" instead'.format(parts[1]) + ) - return expand_registry_url(parts[0]), parts[1] + return expand_registry_url(parts[0], insecure), parts[1] def resolve_authconfig(authconfig, registry=None): diff --git a/docker/client.py b/docker/client.py index 0a12b66..49e85dd 100644 --- a/docker/client.py +++ b/docker/client.py @@ -13,6 +13,7 @@ # limitations under the License. import json +import os import re import shlex import struct @@ -110,6 +111,41 @@ class Client(requests.Session): '{0}={1}'.format(k, v) for k, v in environment.items() ] + if isinstance(mem_limit, six.string_types): + if len(mem_limit) == 0: + mem_limit = 0 + else: + units = {'b': 1, + 'k': 1024, + 'm': 1024 * 1024, + 'g': 1024 * 1024 * 1024} + suffix = mem_limit[-1].lower() + + # Check if the variable is a string representation of an int + # without a units part. Assuming that the units are bytes. + if suffix.isdigit(): + digits_part = mem_limit + suffix = 'b' + else: + digits_part = mem_limit[:-1] + + if suffix in units.keys() or suffix.isdigit(): + try: + digits = int(digits_part) + except ValueError: + message = ('Failed converting the string value for' + ' mem_limit ({0}) to a number.') + formatted_message = message.format(digits_part) + raise errors.DockerException(formatted_message) + + mem_limit = digits * units[suffix] + else: + message = ('The specified value for mem_limit parameter' + ' ({0}) should specify the units. The postfix' + ' should be one of the `b` `k` `m` `g`' + ' characters') + raise errors.DockerException(message.format(mem_limit)) + if isinstance(ports, list): exposed_ports = {} for port_definition in ports: @@ -122,6 +158,9 @@ class Client(requests.Session): exposed_ports['{0}/{1}'.format(port, proto)] = {} ports = exposed_ports + if isinstance(volumes, six.string_types): + volumes = [volumes, ] + if isinstance(volumes, list): volumes_dict = {} for vol in volumes: @@ -356,7 +395,12 @@ class Client(requests.Session): 'git://', 'github.com/')): remote = path else: - context = utils.tar(path) + dockerignore = os.path.join(path, '.dockerignore') + exclude = None + if os.path.exists(dockerignore): + with open(dockerignore, 'r') as f: + exclude = list(filter(bool, f.read().split('\n'))) + context = utils.tar(path, exclude=exclude) if utils.compare_version('1.8', self._version) >= 0: stream = True @@ -459,6 +503,9 @@ class Client(requests.Session): cpu_shares=None, working_dir=None, domainname=None, memswap_limit=0): + if isinstance(volumes, six.string_types): + volumes = [volumes, ] + config = self._container_config( image, command, hostname, user, detach, stdin_open, tty, mem_limit, ports, environment, dns, volumes, volumes_from, network_disabled, @@ -666,10 +713,13 @@ class Client(requests.Session): return h_ports - def pull(self, repository, tag=None, stream=False): + def pull(self, repository, tag=None, stream=False, + insecure_registry=False): if not tag: repository, tag = utils.parse_repository_tag(repository) - registry, repo_name = auth.resolve_repository_name(repository) + registry, repo_name = auth.resolve_repository_name( + repository, insecure=insecure_registry + ) if repo_name.count(":") == 1: repository, tag = repository.rsplit(":", 1) @@ -700,9 +750,17 @@ class Client(requests.Session): else: return self._result(response) - def push(self, repository, stream=False): - registry, repo_name = auth.resolve_repository_name(repository) + def push(self, repository, tag=None, stream=False, + insecure_registry=False): + if not tag: + repository, tag = utils.parse_repository_tag(repository) + registry, repo_name = auth.resolve_repository_name( + repository, insecure=insecure_registry + ) u = self._url("/images/{0}/push".format(repository)) + params = { + 'tag': tag + } headers = {} if utils.compare_version('1.5', self._version) >= 0: @@ -718,9 +776,10 @@ class Client(requests.Session): if authcfg: headers['X-Registry-Auth'] = auth.encode_header(authcfg) - response = self._post_json(u, None, headers=headers, stream=stream) + response = self._post_json(u, None, headers=headers, + stream=stream, params=params) else: - response = self._post_json(u, None, stream=stream) + response = self._post_json(u, None, stream=stream, params=params) return stream and self._stream_helper(response) \ or self._result(response) @@ -753,7 +812,8 @@ class Client(requests.Session): def start(self, container, binds=None, port_bindings=None, lxc_conf=None, publish_all_ports=False, links=None, privileged=False, - dns=None, dns_search=None, volumes_from=None, network_mode=None): + dns=None, dns_search=None, volumes_from=None, network_mode=None, + restart_policy=None, cap_add=None, cap_drop=None): if isinstance(container, dict): container = container.get('Id') @@ -806,13 +866,21 @@ class Client(requests.Session): if volumes_from is not None: warnings.warn(warning_message.format('volumes_from'), DeprecationWarning) - if dns_search: start_config['DnsSearch'] = dns_search if network_mode: start_config['NetworkMode'] = network_mode + if restart_policy: + start_config['RestartPolicy'] = restart_policy + + if cap_add: + start_config['CapAdd'] = cap_add + + if cap_drop: + start_config['CapDrop'] = cap_drop + url = self._url("/containers/{0}/start".format(container)) res = self._post_json(url, data=start_config) self._raise_for_status(res) @@ -832,7 +900,7 @@ class Client(requests.Session): params = {'t': timeout} url = self._url("/containers/{0}/stop".format(container)) res = self._post(url, params=params, - timeout=max(timeout, self._timeout)) + timeout=(timeout + self._timeout)) self._raise_for_status(res) def tag(self, image, repository, tag=None, force=False): diff --git a/docker/ssladapter/ssladapter.py b/docker/ssladapter/ssladapter.py index 99dc36a..a4c11de 100644 --- a/docker/ssladapter/ssladapter.py +++ b/docker/ssladapter/ssladapter.py @@ -21,13 +21,13 @@ class SSLAdapter(HTTPAdapter): def init_poolmanager(self, connections, maxsize, block=False): urllib_ver = urllib3.__version__.split('-')[0] - if urllib3 and urllib_ver != 'dev' and \ - StrictVersion(urllib_ver) <= StrictVersion('1.5'): - self.poolmanager = PoolManager(num_pools=connections, - maxsize=maxsize, - block=block) - else: - self.poolmanager = PoolManager(num_pools=connections, - maxsize=maxsize, - block=block, - ssl_version=self.ssl_version) + kwargs = { + 'num_pools': connections, + 'maxsize': maxsize, + 'block': block + } + if urllib3 and urllib_ver == 'dev' and \ + StrictVersion(urllib_ver) > StrictVersion('1.5'): + kwargs['ssl_version'] = self.ssl_version + + self.poolmanager = PoolManager(**kwargs) diff --git a/docker/utils/utils.py b/docker/utils/utils.py index f741ee2..fb05a1e 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -13,9 +13,11 @@ # limitations under the License. import io +import os import tarfile import tempfile from distutils.version import StrictVersion +from fnmatch import fnmatch import requests import six @@ -47,10 +49,29 @@ def mkbuildcontext(dockerfile): return f -def tar(path): +def fnmatch_any(relpath, patterns): + return any([fnmatch(relpath, pattern) for pattern in patterns]) + + +def tar(path, exclude=None): f = tempfile.NamedTemporaryFile() t = tarfile.open(mode='w', fileobj=f) - t.add(path, arcname='.') + for dirpath, dirnames, filenames in os.walk(path): + relpath = os.path.relpath(dirpath, path) + if relpath == '.': + relpath = '' + if exclude is None: + fnames = filenames + else: + dirnames[:] = [d for d in dirnames + if not fnmatch_any(os.path.join(relpath, d), + exclude)] + fnames = [name for name in filenames + if not fnmatch_any(os.path.join(relpath, name), + exclude)] + for name in fnames: + arcname = os.path.join(relpath, name) + t.add(os.path.join(path, arcname), arcname=arcname) t.close() f.seek(0) return f @@ -80,7 +101,7 @@ def compare_version(v1, v2): def ping(url): try: - res = requests.get(url) + res = requests.get(url, timeout=3) except Exception: return False else: diff --git a/docker/version.py b/docker/version.py index 5140fa1..819a300 100644 --- a/docker/version.py +++ b/docker/version.py @@ -1 +1 @@ -version = "0.4.0" +version = "0.5.0" diff --git a/docker_py.egg-info/PKG-INFO b/docker_py.egg-info/PKG-INFO index 1ec1ecc..f380295 100644 --- a/docker_py.egg-info/PKG-INFO +++ b/docker_py.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: docker-py -Version: 0.4.0 +Version: 0.5.0 Summary: Python client for Docker. Home-page: UNKNOWN Author: UNKNOWN diff --git a/docker_py.egg-info/requires.txt b/docker_py.egg-info/requires.txt index 9c4587f..4d13811 100644 --- a/docker_py.egg-info/requires.txt +++ b/docker_py.egg-info/requires.txt @@ -1,5 +1,3 @@ -requests==2.2.1 -six>=1.3.0 -websocket-client==0.11.0 -mock==1.0.1 -coverage==3.7.1
\ No newline at end of file +requests >= 2.2.1 +six >= 1.3.0 +websocket-client >= 0.11.0
\ No newline at end of file @@ -6,17 +6,19 @@ from setuptools import setup ROOT_DIR = os.path.dirname(__file__) SOURCE_DIR = os.path.join(ROOT_DIR) -if sys.version_info[0] == 3: - requirements_file = './requirements3.txt' -else: - requirements_file = './requirements.txt' +requirements = [ + 'requests >= 2.2.1', + 'six >= 1.3.0', +] + +if sys.version_info[0] < 3: + requirements.append('websocket-client >= 0.11.0') exec(open('docker/version.py').read()) with open('./test-requirements.txt') as test_reqs_txt: test_requirements = [line for line in test_reqs_txt] -with open(requirements_file) as requirements_txt: - requirements = [line for line in requirements_txt] + setup( name="docker-py", @@ -24,7 +26,8 @@ setup( description="Python client for Docker.", packages=['docker', 'docker.auth', 'docker.unixconn', 'docker.utils', 'docker.ssladapter'], - install_requires=requirements + test_requirements, + install_requires=requirements, + tests_require=test_requirements, zip_safe=False, test_suite='tests', classifiers=[ |