summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PKG-INFO2
-rw-r--r--README.md45
-rw-r--r--docker/auth/auth.py18
-rw-r--r--docker/client.py88
-rw-r--r--docker/ssladapter/ssladapter.py20
-rw-r--r--docker/utils/utils.py27
-rw-r--r--docker/version.py2
-rw-r--r--docker_py.egg-info/PKG-INFO2
-rw-r--r--docker_py.egg-info/requires.txt8
-rw-r--r--setup.py17
10 files changed, 182 insertions, 47 deletions
diff --git a/PKG-INFO b/PKG-INFO
index 1ec1ecc..f380295 100644
--- a/PKG-INFO
+++ b/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/README.md b/README.md
index 9795f62..52139b2 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/setup.py b/setup.py
index 0055cd6..b63cc92 100644
--- a/setup.py
+++ b/setup.py
@@ -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=[