summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormicahhausler <micah.hausler@ambition.com>2014-10-29 15:21:18 -0400
committermicahhausler <micah.hausler@ambition.com>2014-10-29 15:21:18 -0400
commit1803c4b83dd2dd7cb04e07d5b784fa0c799525b0 (patch)
treef63725f6590db122f14f4b3563d6964a8c1bb322
parent1476ad302dd1b6622b7c53d3bea2270e658b0248 (diff)
parentcf9d75be0ac49066c3172da51c6d0d8d4bc9288d (diff)
downloaddocker-py-1803c4b83dd2dd7cb04e07d5b784fa0c799525b0.tar.gz
Fixed conflict
-rw-r--r--.gitignore7
-rw-r--r--MANIFEST.in3
-rw-r--r--Makefile15
-rw-r--r--README.md436
-rw-r--r--docker/client.py75
-rw-r--r--docker/utils/__init__.py2
-rw-r--r--docker/utils/utils.py46
-rw-r--r--docs-requirements.txt1
-rw-r--r--docs/api.md736
-rw-r--r--docs/change_log.md (renamed from ChangeLog.md)9
-rw-r--r--docs/contributing.md36
-rw-r--r--docs/favicon_whale.pngbin0 -> 1475 bytes
-rw-r--r--docs/host-devices.md13
-rw-r--r--docs/index.md16
-rw-r--r--docs/port-bindings.md38
-rw-r--r--docs/tls.md85
-rw-r--r--docs/volumes.md25
-rw-r--r--mkdocs.yml15
-rw-r--r--tests/fake_api.py36
-rw-r--r--tests/integration_test.py70
-rw-r--r--tests/test.py145
-rw-r--r--tests/testdata/certs/ca.pem0
-rw-r--r--tests/testdata/certs/cert.pem0
-rw-r--r--tests/testdata/certs/key.pem0
-rw-r--r--tests/utils_test.py32
25 files changed, 1394 insertions, 447 deletions
diff --git a/.gitignore b/.gitignore
index 318f9eb..9980377 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,10 @@ dist
.tox
.coverage
html/*
+
+# Compiled Documentation
+site/
+
+env/
+venv/
+.idea/
diff --git a/MANIFEST.in b/MANIFEST.in
index 040a4bc..f60f3f1 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,4 +2,5 @@ include test-requirements.txt
include requirements.txt
include requirements3.txt
include README.md
-include LICENSE \ No newline at end of file
+include LICENSE
+recursive-include tests *.py
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4cb0ffd
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,15 @@
+.PHONY: all build test integration-test unit-test
+
+all: test
+
+build:
+ docker build -t docker-py .
+
+test: unit-test integration-test
+
+unit-test: build
+ docker run docker-py python tests/test.py
+
+integration-test: build
+ docker run -v /var/run/docker.sock:/var/run/docker.sock docker-py python tests/integration_test.py
+
diff --git a/README.md b/README.md
index 52139b2..b80ddcb 100644
--- a/README.md
+++ b/README.md
@@ -6,440 +6,18 @@ docker-py
An API client for docker written in Python
Installation
-============
+------------
Our latest stable is always available on PyPi.
pip install docker-py
-API
-===
+Documentation
+------------
-To instantiate a `Client` class that will allow you to communicate with
-a Docker daemon, simply do:
+Full documentation is available in the `/docs/` directory.
-```python
-c = docker.Client(base_url='unix://var/run/docker.sock',
- version='1.12',
- timeout=10)
-```
-`base_url` refers to the protocol+hostname+port where the docker server
-is hosted. `version` is the version of the API the client will use and
-`timeout` specifies the HTTP request timeout, in seconds.
-
-```python
-c.build(path=None, tag=None, quiet=False, fileobj=None, nocache=False,
- rm=False, stream=False, timeout=None,
- custom_context=False, encoding=None):
-```
-
-Similar to the `docker build` command. Either `path` or `fileobj` needs
-to be set. `path` can be a local path (to a directory containing a
-Dockerfile) or a remote URL. `fileobj` must be a readable file-like
-object to a Dockerfile.
-
-If you have a tar file for the docker build context (including a dockerfile)
-already, pass a readable file-like object to `fileobj` and also pass
-`custom_context=True`. If the stream is compressed also, set `encoding` to
-the correct value (e.g `gzip`).
-
-```python
-c.commit(container, repository=None, tag=None, message=None, author=None,
- conf=None)
-```
-
-Identical to the `docker commit` command.
-
-```python
-c.containers(quiet=False, all=False, trunc=True, latest=False, since=None,
- before=None, limit=-1)
-```
-
-Identical to the `docker ps` command.
-
-```python
-c.copy(container, resource)
-```
-
-Identical to the `docker cp` command.
-
-```python
-c.create_container(image, command=None, hostname=None, user=None,
- detach=False, stdin_open=False, tty=False, mem_limit=0,
- ports=None, environment=None, dns=None, volumes=None,
- volumes_from=None, network_disabled=False, name=None,
- entrypoint=None, cpu_shares=None, working_dir=None,
- memswap_limit=0)
-```
-
-Creates a container that can then be `start`ed. Parameters are similar
-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.
-
-
-```python
-c.diff(container)
-```
-
-Identical to the `docker diff` command.
-
-```python
-c.export(container)
-```
-
-Identical to the `docker export` command.
-
-```python
-c.history(image)
-```
-
-Identical to the `docker history` command.
-
-```python
-c.images(name=None, quiet=False, all=False, viz=False)
-```
-
-Identical to the `docker images` command.
-
-```python
-c.import_image(src, data=None, repository=None, tag=None)
-```
-
-Identical to the `docker import` command. If `src` is a string or
-unicode string, it will be treated as a URL to fetch the image from. To
-import an image from the local machine, `src` needs to be a file-like
-object or bytes collection. To import from a tarball use your absolute
-path to your tarball. To load arbitrary data as tarball use whatever
-you want as src and your tarball content in data.
-
-```python
-c.info()
-```
-
-Identical to the `docker info` command.
-
-```python
-c.insert(image, url, path)
-```
-
-Identical to the `docker insert` command.
-
-```python
-c.inspect_container(container)
-```
-
-Identical to the `docker inspect` command, but only for containers.
-
-```python
-c.inspect_image(image_id)
-```
-
-Identical to the `docker inspect` command, but only for images.
-
-```python
-c.kill(container, signal=None)
-```
-
-Kill a container. Similar to the `docker kill` command.
-
-```python
-c.login(username, password=None, email=None, registry=None)
-```
-
-Identical to the `docker login` command (but non-interactive, obviously).
-
-```python
-c.logs(container, stdout=True, stderr=True, stream=False, timestamps=False)
-```
-
-Identical to the `docker logs` command. The `stream` parameter makes the
-`logs` function return a blocking generator you can iterate over to
-retrieve log output as it happens.
-
-```python
-c.attach(container, stdout=True, stderr=True, stream=False, logs=False)
-```
-
-The `logs` function is a wrapper around this one, which you can use
-instead if you want to fetch/stream container output without first
-retrieving the entire backlog.
-
-```python
-c.ping()
-```
-
-Hits the /_ping endpoint of the remote API and returns the result.
-An exception will be raised if the endpoint isn't responding.
-
-```python
-c.port(container, private_port)
-```
-
-Identical to the `docker port` command.
-
-```python
-c.pull(repository, tag=None, stream=False)
-```
-
-Identical to the `docker pull` command.
-
-```python
-c.push(repository, tag=None, stream=False)
-```
-
-Identical to the `docker push` command.
-
-````python
-c.remove_container(container, v=False, link=False)
-```
-
-Remove a container. Similar to the `docker rm` command.
-
-```python
-c.remove_image(image)
-```
-
-Remove an image. Similar to the `docker rmi` command.
-
-```python
-c.restart(container, timeout=10)
-```
-Restart a container. Similar to the `docker restart` command.
-
-```python
-c.search(term)
-```
-
-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,
- restart_policy=None, cap_add=None, cap_drop=None)
-```
-
-Similar to the `docker start` command, but doesn't support attach
-options. Use `docker logs` to recover `stdout`/`stderr`.
-
-`binds` allows to bind a directory in the host to the container. See
-"Using volumes" below for more information. `port_bindings` exposes
-container ports to the host. See "Port bindings" below for more
-information. `lxc_conf` allows to pass LXC configuration options using a
-dictionary. `privileged` starts the container in privileged mode.
-
-[Links](http://docs.docker.io/en/latest/use/working_with_links_names/)
-can be specified with the `links` argument. They can either be
-specified as a dictionary mapping name to alias or as a list of
-`(name, alias)` tuples.
-
-`dns` and `volumes_from` are only available if they are used with version v1.10
-of docker remote API. Otherwise they are ignored.
-
-`network_mode` is available since v1.11 and sets the Network mode for the
-container ('bridge': creates a new network stack for the container on the
-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)
-```
-
-Stops a container. Similar to the `docker stop` command.
-
-```python
-c.tag(image, repository, tag=None, force=False)
-```
-
-Identical to the `docker tag` command.
-
-```python
-c.top(container)
-```
-
-Identical to the `docker top` command.
-
-```python
-c.version()
-```
-
-Identical to the `docker version` command.
-
-```python
-c.wait(container)
-```
-
-Wait for a container and return its exit code. Similar to the `docker
-wait` command.
-
-
-Port bindings
-=============
-
-Port bindings is done in two parts. Firstly, by providing a list of ports to
-open inside the container in the `Client.create_container` method.
-
-```python
-c.create_container('busybox', 'ls', ports=[1111, 2222])
-```
-
-Bindings are then declared in the `Client.start` method.
-
-```python
-c.start(container_id, port_bindings={1111: 4567, 2222: None})
-```
-
-You can limit the host address on which the port will be exposed like such:
-
-```python
-c.start(container_id, port_bindings={1111: ('127.0.0.1', 4567)})
-```
-
-Or without host port assignment:
-
-```python
-c.start(container_id, port_bindings={1111: ('127.0.0.1',)})
-```
-
-If you wish to use UDP instead of TCP (default), you need to declare it
-like such in both the `create_container()` and `start()` calls:
-
-```python
-container_id = c.create_container('busybox', 'ls', ports=[(1111, 'udp'), 2222])
-c.start(container_id, port_bindings={'1111/udp': 4567, 2222: None})
-```
-
-
-Using volumes
-=============
-
-Similarly, volume declaration is done in two parts. First, you have to provide
-a list of mountpoints to the `Client.create_container` method.
-
-```python
-c.create_container('busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'])
-```
-
-Volume mappings are then declared inside the `Client.start` method like this:
-
-```python
-c.start(container_id, binds={
- '/home/user1/':
- {
- 'bind': '/mnt/vol2',
- 'ro': False
- },
- '/var/www':
- {
- 'bind': '/mnt/vol1',
- 'ro': True
- }
-})
-```
-
-Connection to daemon using HTTPS
-================================
-
-*These instructions are docker-py specific. Please refer to
-http://docs.docker.com/articles/https/ first.*
-
-* Authenticate server based on public/default CA pool
-
-```python
-client = docker.Client(base_url='<https_url>', tls=True)
-```
-
-Equivalent CLI options: `docker --tls ...`
-
-If you want to use TLS but don't want to verify the server certificate
-(for example when testing with a self-signed certificate):
-
-```python
-tls_config = docker.tls.TLSConfig(verify=False)
-client = docker.Client(base_url='<https_url>', tls=tls_config)
-```
-
-* Authenticate server based on given CA
-
-```python
-tls_config = docker.tls.TLSConfig(ca_cert='/path/to/ca.pem')
-client = docker.Client(base_url='<https_url>', tls=tls_config)
-```
-
-Equivalent CLI options: `docker --tlsverify --tlscacert /path/to/ca.pem ...`
-
-* Authenticate with client certificate, do not authenticate server
- based on given CA
-
-```python
-tls_config = docker.tls.TLSConfig(
- client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem')
-)
-client = docker.Client(base_url='<https_url>', tls=tls_config)
-```
-
-Equivalent CLI options:
-`docker --tls --tlscert /path/to/client-cert.pem
---tlskey /path/to/client-key.pem ...`
-
-* Authenticate with client certificate, authenticate server based on given CA
-
-```python
-tls_config = docker.tls.TLSConfig(
- client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem'),
- ca_cert='/path/to/ca.pem'
-)
-client = docker.Client(base_url='<https_url>', tls=tls_config)
-```
-
-Equivalent CLI options:
-`docker --tlsverify --tlscert /path/to/client-cert.pem
---tlskey /path/to/client-key.pem --tlscacert /path/to/ca.pem ...`
+License
+-------
+Docker is licensed under the Apache License, Version 2.0. See LICENSE for full license text
diff --git a/docker/client.py b/docker/client.py
index bfbb1c0..2c1ecde 100644
--- a/docker/client.py
+++ b/docker/client.py
@@ -34,7 +34,7 @@ from .tls import TLSConfig
if not six.PY3:
import websocket
-DEFAULT_DOCKER_API_VERSION = '1.12'
+DEFAULT_DOCKER_API_VERSION = '1.15'
DEFAULT_TIMEOUT_SECONDS = 60
STREAM_HEADER_SIZE_BYTES = 8
@@ -103,8 +103,8 @@ class Client(requests.Session):
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, entrypoint=None,
- cpu_shares=None, working_dir=None, domainname=None,
- memswap_limit=0):
+ cpu_shares=None, working_dir=None,
+ domainname=None, memswap_limit=0, cpuset=None):
if isinstance(command, six.string_types):
command = shlex.split(str(command))
if isinstance(environment, dict):
@@ -217,6 +217,7 @@ class Client(requests.Session):
'NetworkDisabled': network_disabled,
'Entrypoint': entrypoint,
'CpuShares': cpu_shares,
+ 'Cpuset': cpuset,
'WorkingDir': working_dir,
'MemorySwap': memswap_limit
}
@@ -519,8 +520,8 @@ class Client(requests.Session):
mem_limit=0, ports=None, environment=None, dns=None,
volumes=None, volumes_from=None,
network_disabled=False, name=None, entrypoint=None,
- cpu_shares=None, working_dir=None, domainname=None,
- memswap_limit=0):
+ cpu_shares=None, working_dir=None,
+ domainname=None, memswap_limit=0, cpuset=None):
if isinstance(volumes, six.string_types):
volumes = [volumes, ]
@@ -528,7 +529,8 @@ class Client(requests.Session):
config = self._container_config(
image, command, hostname, user, detach, stdin_open, tty, mem_limit,
ports, environment, dns, volumes, volumes_from, network_disabled,
- entrypoint, cpu_shares, working_dir, domainname, memswap_limit
+ entrypoint, cpu_shares, working_dir, domainname,
+ memswap_limit, cpuset
)
return self.create_container_from_config(config, name)
@@ -549,6 +551,48 @@ class Client(requests.Session):
def events(self):
return self._stream_helper(self.get(self._url('/events'), stream=True))
+ def execute(self, container, cmd, detach=False, stdout=True, stderr=True,
+ stream=False, tty=False):
+ if utils.compare_version('1.15', self._version) < 0:
+ raise Exception('Exec is not supported in API < 1.15!')
+ if isinstance(container, dict):
+ container = container.get('Id')
+ if isinstance(cmd, six.string_types):
+ cmd = shlex.split(str(cmd))
+
+ data = {
+ 'Container': container,
+ 'User': '',
+ 'Privileged': False,
+ 'Tty': tty,
+ 'AttachStdin': False,
+ 'AttachStdout': stdout,
+ 'AttachStderr': stderr,
+ 'Detach': detach,
+ 'Cmd': cmd
+ }
+
+ # create the command
+ url = self._url('/containers/{0}/exec'.format(container))
+ res = self._post_json(url, data=data)
+ self._raise_for_status(res)
+
+ # start the command
+ cmd_id = res.json().get('Id')
+ res = self._post_json(self._url('/exec/{0}/start'.format(cmd_id)),
+ data=data, stream=stream)
+ self._raise_for_status(res)
+ if stream:
+ return self._multiplexed_socket_stream_helper(res)
+ elif six.PY3:
+ return bytes().join(
+ [x for x in self._multiplexed_buffer_helper(res)]
+ )
+ else:
+ return str().join(
+ [x for x in self._multiplexed_buffer_helper(res)]
+ )
+
def export(self, container):
if isinstance(container, dict):
container = container.get('Id')
@@ -714,6 +758,13 @@ class Client(requests.Session):
logs=True
)
+ def pause(self, container):
+ if isinstance(container, dict):
+ container = container.get('Id')
+ url = self._url('/containers/{0}/pause'.format(container))
+ res = self._post(url)
+ self._raise_for_status(res)
+
def ping(self):
return self._result(self._get(self._url('/_ping')))
@@ -832,7 +883,7 @@ 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,
- restart_policy=None, cap_add=None, cap_drop=None):
+ restart_policy=None, cap_add=None, cap_drop=None, devices=None):
if isinstance(container, dict):
container = container.get('Id')
@@ -900,6 +951,9 @@ class Client(requests.Session):
if cap_drop:
start_config['CapDrop'] = cap_drop
+ if devices:
+ start_config['Devices'] = utils.parse_devices(devices)
+
url = self._url("/containers/{0}/start".format(container))
res = self._post_json(url, data=start_config)
self._raise_for_status(res)
@@ -940,6 +994,13 @@ class Client(requests.Session):
def version(self):
return self._result(self._get(self._url("/version")), True)
+ def unpause(self, container):
+ if isinstance(container, dict):
+ container = container.get('Id')
+ url = self._url('/containers/{0}/unpause'.format(container))
+ res = self._post(url)
+ self._raise_for_status(res)
+
def wait(self, container):
if isinstance(container, dict):
container = container.get('Id')
diff --git a/docker/utils/__init__.py b/docker/utils/__init__.py
index 5d2c1b8..82b1a96 100644
--- a/docker/utils/__init__.py
+++ b/docker/utils/__init__.py
@@ -1,4 +1,4 @@
from .utils import (
compare_version, convert_port_bindings, convert_volume_binds,
- mkbuildcontext, ping, tar, parse_repository_tag, parse_host
+ mkbuildcontext, ping, tar, parse_repository_tag, parse_host, kwargs_from_env
) # flake8: noqa
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
index fb05a1e..91e5e66 100644
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -14,6 +14,7 @@
import io
import os
+import os.path
import tarfile
import tempfile
from distutils.version import StrictVersion
@@ -23,6 +24,7 @@ import requests
import six
from .. import errors
+from .. import tls
DEFAULT_HTTP_HOST = "127.0.0.1"
DEFAULT_UNIX_SOCKET = "http+unix://var/run/docker.sock"
@@ -72,6 +74,10 @@ def tar(path, exclude=None):
for name in fnames:
arcname = os.path.join(relpath, name)
t.add(os.path.join(path, arcname), arcname=arcname)
+ for name in dirnames:
+ arcname = os.path.join(relpath, name)
+ t.add(os.path.join(path, arcname),
+ arcname=arcname, recursive=False)
t.close()
f.seek(0)
return f
@@ -233,3 +239,43 @@ def parse_host(addr):
if proto == "http+unix":
return "%s://%s" % (proto, host)
return "%s://%s:%d" % (proto, host, port)
+
+
+def parse_devices(devices):
+ device_list = []
+ for device in devices:
+ device_mapping = device.split(",")
+ if device_mapping:
+ path_on_host = device_mapping[0]
+ if len(device_mapping) > 1:
+ path_in_container = device_mapping[1]
+ else:
+ path_in_container = path_on_host
+ if len(device_mapping) > 2:
+ permissions = device_mapping[2]
+ else:
+ permissions = 'rwm'
+ device_list.append({"PathOnHost": path_on_host,
+ "PathInContainer": path_in_container,
+ "CgroupPermissions": permissions})
+ return device_list
+
+
+def kwargs_from_env(ssl_version=None, assert_hostname=None):
+ host = os.environ.get('DOCKER_HOST')
+ cert_path = os.environ.get('DOCKER_CERT_PATH')
+ tls_verify = os.environ.get('DOCKER_TLS_VERIFY')
+
+ params = {}
+ if host:
+ params['base_url'] = (host.replace('tcp://', 'https://')
+ if tls_verify else host)
+ if tls_verify and cert_path:
+ params['tls'] = tls.TLSConfig(
+ client_cert=(os.path.join(cert_path, 'cert.pem'),
+ os.path.join(cert_path, 'key.pem')),
+ ca_cert=os.path.join(cert_path, 'ca.pem'),
+ verify=True,
+ ssl_version=ssl_version,
+ assert_hostname=assert_hostname)
+ return params
diff --git a/docs-requirements.txt b/docs-requirements.txt
new file mode 100644
index 0000000..abc8d72
--- /dev/null
+++ b/docs-requirements.txt
@@ -0,0 +1 @@
+mkdocs==0.9
diff --git a/docs/api.md b/docs/api.md
new file mode 100644
index 0000000..73fec4d
--- /dev/null
+++ b/docs/api.md
@@ -0,0 +1,736 @@
+# Client API
+
+To instantiate a `Client` class that will allow you to communicate with a
+Docker daemon, simply do:
+
+```python
+from docker import Client
+c = Client(base_url='unix://var/run/docker.sock')
+```
+
+**Params**:
+
+* base_url (str): Refers to the protocol+hostname+port where the Docker server
+is hosted.
+* version (str): The version of the API the client will use
+* timeout (int): The HTTP request timeout, in seconds.
+* tls (bool or [TLSConfig](tls.md#TLSConfig)): Equivalent CLI options:
+`docker --tls ...`
+
+
+## attach
+
+The `.logs()` function is a wrapper around this method, which you can use
+instead if you want to fetch/stream container output without first retrieving
+the entire backlog.
+
+**Params**:
+
+* container (str): The container to attach to
+* stdout (bool): Get STDOUT
+* stderr (bool): Get STDERR
+* stream (bool): Return an interator
+* logs (bool): Get all previous output
+
+**Returns** (generator or str): The logs or output for the image
+
+## build
+
+Similar to the `docker build` command. Either `path` or `fileobj` needs to be
+set. `path` can be a local path (to a directory containing a Dockerfile) or a
+remote URL. `fileobj` must be a readable file-like object to a Dockerfile.
+
+If you have a tar file for the Docker build context (including a Dockerfile)
+already, pass a readable file-like object to `fileobj` and also pass
+`custom_context=True`. If the stream is compressed also, set `encoding` to the
+correct value (e.g `gzip`).
+
+**Params**:
+
+* path (str): Path to the directory containing the Dockerfile
+* tag (str): A tag to add to the final image
+* quiet (bool): Whether to return the status
+* fileobj: A file object to use as the Dockerfile. (Or a file-like object)
+* nocache (bool): Don't use the cache when set to `True`
+* rm (bool): Remove intermediate containers
+* stream (bool): Return a blocking generator you can iterate over to retrieve
+build output as it happens
+* timeout (int): HTTP timeout
+* custom_context (bool): Optional if using `fileobj`
+* encoding (str): The encoding for a stream. Set to `gzip` for compressing
+
+**Returns** (generator): A generator of the build output
+
+```python
+>>> from io import BytesIO
+>>> from docker import Client
+>>> dockerfile = '''
+... # Shared Volume
+... FROM busybox:buildroot-2014.02
+... MAINTAINER first last, first.last@yourdomain.com
+... VOLUME /data
+... CMD ["/bin/sh"]
+... '''
+>>> f = BytesIO(dockerfile.encode('utf-8'))
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> response = [line for line in cli.build(
+... fileobj=f, rm=True, tag='yourname/volume'
+... )]
+>>> response
+['{"stream":" ---\\u003e a9eb17255234\\n"}',
+'{"stream":"Step 1 : MAINTAINER first last, first.last@yourdomain.com\\n"}',
+'{"stream":" ---\\u003e Running in 08787d0ee8b1\\n"}',
+'{"stream":" ---\\u003e 23e5e66a4494\\n"}',
+'{"stream":"Removing intermediate container 08787d0ee8b1\\n"}',
+'{"stream":"Step 2 : VOLUME /data\\n"}',
+'{"stream":" ---\\u003e Running in abdc1e6896c6\\n"}',
+'{"stream":" ---\\u003e 713bca62012e\\n"}',
+'{"stream":"Removing intermediate container abdc1e6896c6\\n"}',
+'{"stream":"Step 3 : CMD [\\"/bin/sh\\"]\\n"}',
+'{"stream":" ---\\u003e Running in dba30f2a1a7e\\n"}',
+'{"stream":" ---\\u003e 032b8b2855fc\\n"}',
+'{"stream":"Removing intermediate container dba30f2a1a7e\\n"}',
+'{"stream":"Successfully built 032b8b2855fc\\n"}']
+```
+
+**Raises:** [TypeError](
+https://docs.python.org/3.4/library/exceptions.html#TypeError) if `path` nor
+`fileobj` are specified
+
+## commit
+
+Identical to the `docker commit` command.
+
+**Params**:
+
+* container (str): The image hash of the container
+* repository (str): The repository to push the image to
+* tag (str): The tag to push
+* message (str): A commit message
+* author (str): The name of the author
+* conf (dict): The configuraton for the container. See the [Docker remote api](
+https://docs.docker.com/reference/api/docker_remote_api/) for full details.
+
+## containers
+
+List containers. Identical to the `docker ps` command.
+
+**Params**:
+
+* quiet (bool): Only display numeric Ids
+* all (bool): Show all containers. Only running containers are shown by default
+* trunc (bool): Truncate output
+* latest (bool): Show only the latest created container, include non-running
+ones.
+* since (str): Show only containers created since Id or Name, include
+non-running ones
+* before (str): Show only container created before Id or Name, include
+non-running ones
+* limit (int): Show `limit` last created containers, include non-running ones
+* size (bool): Display sizes
+
+**Returns** (dict): The system's containers
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> cli.containers()
+[{'Command': '/bin/sleep 30',
+ 'Created': 1412574844,
+ 'Id': '6e276c9e6e5759e12a6a9214efec6439f80b4f37618e1a6547f28a3da34db07a',
+ 'Image': 'busybox:buildroot-2014.02',
+ 'Names': ['/grave_mayer'],
+ 'Ports': [],
+ 'Status': 'Up 1 seconds'}]
+```
+
+## copy
+Identical to the `docker cp` command. Get files/folders from the container.
+
+**Params**:
+
+* container (str): The container to copy from
+* resource (str): The path within the container
+
+**Returns** (str): The contents of the file as a string
+
+## create_container
+
+Creates a container that can then be `.start()` ed. Parameters are similar to
+those for the `docker run` command except it doesn't support the attach
+options (`-a`).
+
+See [Port bindings](port-bindings.md) and [Using volumes](volumes.md) for more
+information on how to create port bindings and volume mappings.
+
+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](
+https://docs.python.org/3.4/library/exceptions.html#TypeError) exception if
+they are used against v1.10 of the Docker remote API. Those arguments should be
+passed to `start()` instead.
+
+**Params**:
+
+* image (str): The image to run
+* command (str or list): The command to be run in the container
+* hostname (str): Optional hostname for the container
+* user (str or int): Username or UID
+* detach (bool): Detached mode: run container in the background and print new
+container Id
+* stdin_open (bool): Keep STDIN open even if not attached
+* tty (bool): Allocate a pseudo-TTY
+* mem_limit (float or str): Memory limit (format: [number][optional unit],
+where unit = b, k, m, or g)
+* ports (list of ints): A list of port numbers
+* environment (dict or list): A dictionary or a list of strings in the
+following format `["PASSWORD=xxx"]` or `{"PASSWORD": "xxx"}`.
+* dns (list): DNS name servers
+* volumes (str or list):
+* volumes_from (str or list): List of container names or Ids to get volumes
+from. Optionally a single string joining container id's with commas
+* network_disabled (bool): Disable networking
+* name (str): A name for the container
+* entrypoint (str or list): An entrypoint
+* cpu_shares (int or float): CPU shares (relative weight)
+* working_dir (str): Path to the working directory
+* domainname (str or list): Set custom DNS search domains
+* memswap_limit:
+
+**Returns** (dict): A dictionary with an image 'Id' key and a 'Warnings' key.
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> container = cli.create_container(image='busybox:latest', command='/bin/sleep 30')
+>>> print(container)
+{'Id': '8a61192da2b3bb2d922875585e29b74ec0dc4e0117fcbf84c962204e97564cd7',
+ 'Warnings': None}
+```
+
+## diff
+
+Inspect changes on a container's filesystem
+
+**Params**:
+
+* container (str): The container to diff
+
+**Returns** (str):
+
+## exec
+
+```python
+c.exec(container, cmd, detach=False, stdout=True, stderr=True,
+ stream=False, tty=False)
+```
+
+Execute a command in a running container.
+
+**Params**:
+
+* container (str): can be a container dictionary (result of
+running `inspect_container`), unique id or container name.
+
+
+* cmd (str or list): representing the command and its arguments.
+
+* detach (bool): flag to `True` will run the process in the background.
+
+* stdout (bool): indicates which output streams to read from.
+* stderr (bool): indicates which output streams to read from.
+
+* stream (bool): indicates whether to return a generator which will yield
+ the streaming response in chunks.
+
+## export
+
+Export the contents of a filesystem as a tar archive to STDOUT
+
+**Params**:
+
+* container (str): The container to export
+
+**Returns** (str): The filesystem tar archive as a str
+
+## history
+
+Show the history of an image
+
+**Params**:
+
+* image (str): The image to show history for
+
+**Returns** (str): The history of the image
+
+## images
+
+List images. Identical to the `docker images` command.
+
+**Params**:
+
+* name (str): Optional filter for a name
+* quiet (bool): Only show numeric Ids. Returns a list
+* all (bool): Show all images (by default filter out the intermediate image
+layers)
+* viz: *Depreciated*
+
+**Returns** (dict or list): A list if `quiet=True`, otherwise a dict.
+
+```python
+[{'Created': 1401926735,
+'Id': 'a9eb172552348a9a49180694790b33a1097f546456d041b6e82e4d7716ddb721',
+'ParentId': '120e218dd395ec314e7b6249f39d2853911b3d6def6ea164ae05722649f34b16',
+'RepoTags': ['busybox:buildroot-2014.02', 'busybox:latest'],
+'Size': 0,
+'VirtualSize': 2433303},
+...
+```
+
+## import_image
+
+Identical to the `docker import` command. If `src` is a string or unicode
+string, it will be treated as a URL to fetch the image from. To import an image
+from the local machine, `src` needs to be a file-like object or bytes
+collection. To import from a tarball use your absolute path to your tarball.
+To load arbitrary data as tarball use whatever you want as src and your
+tarball content in data.
+
+**Params**:
+
+* src (str or file): Path to tarfile or URL
+* repository (str): The repository to create
+* tag (str): The tag to apply
+* image (str): Use another image like the `FROM` Dockerfile parameter
+
+## info
+
+Display system-wide information. Identical to the `docker info` command.
+
+**Returns** (dict): The info as a dict
+
+```
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> cli.info()
+{'Containers': 3,
+ 'Debug': 1,
+ 'Driver': 'aufs',
+ 'DriverStatus': [['Root Dir', '/mnt/sda1/var/lib/docker/aufs'],
+ ['Dirs', '225']],
+ 'ExecutionDriver': 'native-0.2',
+ 'IPv4Forwarding': 1,
+ 'Images': 219,
+ 'IndexServerAddress': 'https://index.docker.io/v1/',
+ 'InitPath': '/usr/local/bin/docker',
+ 'InitSha1': '',
+ 'KernelVersion': '3.16.1-tinycore64',
+ 'MemoryLimit': 1,
+ 'NEventsListener': 0,
+ 'NFd': 11,
+ 'NGoroutines': 12,
+ 'OperatingSystem': 'Boot2Docker 1.2.0 (TCL 5.3);',
+ 'SwapLimit': 1}
+```
+
+## insert
+*DEPRECATED*
+
+## inspect_container
+
+Identical to the `docker inspect` command, but only for containers.
+
+**Params**:
+
+* container (str): The container to inspect
+
+**Returns** (dict): Nearly the same output as `docker inspect`, just as a
+single dict
+
+## inspect_image
+
+Identical to the `docker inspect` command, but only for images
+
+**Params**:
+
+* image_id (str): The image to inspect
+
+**Returns** (dict): Nearly the same output as `docker inspect`, just as a
+single dict
+
+## kill
+
+Kill a container or send a signal to a container
+
+**Params**:
+
+* container (str): The container to kill
+* signal (str or int): The singal to send. Defaults to `SIGKILL`
+
+## login
+
+Nearly identical to the `docker login` command, but non-interactive.
+
+**Params**:
+
+* username (str): The registry username
+* password (str): The plaintext password
+* email (str): The email for the registry account
+* registry (str): URL to the registry. Ex:`https://index.docker.io/v1/`
+* reauth (bool): Whether refresh existing authentication on the docker server.
+
+**Returns** (dict): The response from the login request
+
+## logs
+
+Identical to the `docker logs` command. The `stream` parameter makes the `logs`
+function return a blocking generator you can iterate over to retrieve log
+output as it happens.
+
+**Params**:
+
+* container (str): The container to get logs from
+* stdout (bool): Get STDOUT
+* stderr (bool): Get STDERR
+* stream (bool): Stream the response
+* timestamps (bool): Show timestamps
+
+**Returns** (generator or str):
+
+## pause
+
+Pauses all processes within a container.
+
+**Params**:
+
+* container (str): The container to pause
+
+
+## ping
+
+Hits the `/_ping` endpoint of the remote API and returns the result. An
+exception will be raised if the endpoint isn't responding.
+
+**Returns** (bool)
+
+## port
+Lookup the public-facing port that is NAT-ed to `private_port`. Identical to
+the `docker port` command.
+
+**Params**:
+
+* container (str): The container to look up
+* private_port (int): The private port to inspect
+
+**Returns** (list of dict): The mapping for the host ports
+
+```bash
+$ docker run -d -p 80:80 ubuntu:14.04 /bin/sleep 30
+7174d6347063a83f412fad6124c99cffd25ffe1a0807eb4b7f9cec76ac8cb43b
+```
+```python
+>>> cli.port('7174d6347063', 80)
+[{'HostIp': '0.0.0.0', 'HostPort': '80'}]
+```
+
+## pull
+
+Identical to the `docker pull` command.
+
+**Params**:
+
+* repository (str): The repository to pull
+* tag (str): The tag to pull
+* stream (bool): Stream the output as a generator
+* insecure_registry (bool): Use an insecure registry
+
+**Returns** (generator or str): The output
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> for line in cli.pull('busybox', stream=True):
+... print(json.dumps(json.loads(line), indent=4))
+{
+ "status": "Pulling image (latest) from busybox",
+ "progressDetail": {},
+ "id": "e72ac664f4f0"
+}
+{
+ "status": "Pulling image (latest) from busybox, endpoint: ...",
+ "progressDetail": {},
+ "id": "e72ac664f4f0"
+}
+```
+
+## push
+
+Push an image or a repository to the registry. Identical to the `docker push`
+command
+
+**Params**:
+
+* repository (str): The repository to push to
+* tag (str): An optional tag to push
+* stream (bool): Stream the output as a blocking generator
+* insecure_registry (bool): Use `http://` to connect to the registry
+
+**Returns** (generator or str): The output of the upload
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> response = [line for line in cli.push('yourname/app', stream=True)]
+>>> response
+['{"status":"Pushing repository yourname/app (1 tags)"}\\n',
+ '{"status":"Pushing","progressDetail":{},"id":"511136ea3c5a"}\\n',
+ '{"status":"Image already pushed, skipping","progressDetail":{},
+ "id":"511136ea3c5a"}\\n',
+ ...
+ '{"status":"Pushing tag for rev [918af568e6e5] on {
+ https://cdn-registry-1.docker.io/v1/repositories/
+ yourname/app/tags/latest}"}\\n']
+```
+
+## remove_container
+
+Remove a container. Similar to the `docker rm` command.
+
+**Params**:
+
+* container (str): The container to remove
+* v (bool): Remove the volumes associated with the container
+* link (bool): Remove the specified link and not the underlying container
+* force (bool): Force the removal of a running container (uses SIGKILL)
+
+## remove_image
+
+Remove an image. Similar to the `docker rmi` command.
+
+**Params**:
+
+* image (str): The image to remove
+* force (bool): Force removal of the image
+* noprune (bool): Do not delete untagged parents
+
+## restart
+
+Restart a container. Similar to the `docker restart` command.
+
+If `container` a dict, the `Id` key is used.
+
+**Params**:
+
+* container (str or dict): The container to restart
+* timeout (int): Number of seconds to try to stop for before killing the
+container. Once killed it will then be restarted. Default is 10 seconds.
+
+## search
+Identical to the `docker search` command.
+
+**Params**:
+
+* term (str): A term to search for
+
+**Returns** (list of dicts): The response of the search
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> response = cli.search('nginx')
+>>> response[:2]
+[{'description': 'Official build of Nginx.',
+ 'is_official': True,
+ 'is_trusted': False,
+ 'name': 'nginx',
+ 'star_count': 266},
+ {'description': 'Trusted automated Nginx (http://nginx.org/) ...',
+ 'is_official': False,
+ 'is_trusted': True,
+ 'name': 'dockerfile/nginx',
+ 'star_count': 60},
+ ...
+```
+
+## start
+
+Similar to the `docker start` command, but doesn't support attach options. Use
+`.logs()` to recover `stdout`/`stderr`.
+
+`binds` allows to bind a directory in the host to the container. See [Using
+volumes](volumes.md) for more information. `port_bindings` exposes container
+ports to the host. See [Port bindings](port-bindings.md) for more information.
+`lxc_conf` allows to pass LXC configuration options using a dictionary.
+`privileged` starts the container in privileged mode.
+
+[Links](http://docs.docker.io/en/latest/use/working_with_links_names/) can be
+specified with the `links` argument. They can either be specified as a
+dictionary mapping name to alias or as a list of `(name, alias)` tuples.
+
+`dns` and `volumes_from` are only available if they are used with version v1.10
+of docker remote API. Otherwise they are ignored.
+
+`network_mode` is available since v1.11 and sets the Network mode for the
+container ('bridge': creates a new network stack for the container on the
+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:
+```python
+{
+ "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:
+```python
+{
+ "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:
+```python
+[
+ "SYS_ADMIN",
+ "MKNOD"
+]
+```
+
+**Params**:
+
+* container (str): The container to start
+* binds: Volumes to bind
+* port_bindings (dict): Port bindings. See note above
+* lxc_conf (dict): LXC config
+* publish_all_ports (bool): Whether to publish all ports to the host
+* links (dict or list of tuples): See note above
+* privileged (bool): Give extended privileges to this container
+* dns (list): Set custom DNS servers
+* dns_search (list): DNS search domains
+* volumes_from (str or list): List of container names or Ids to get volumes
+from. Optionally a single string joining container id's with commas
+* network_mode (str): One of `['bridge', None, 'container:<name|id>',
+'host']`
+* restart_policy (dict): See note above. "Name" param must be one of
+`['on-failure', 'always']`
+* cap_add (list of str): See note above
+* cap_drop (list of str): See note above
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> container = cli.create_container(
+... image='busybox:latest',
+... command='/bin/sleep 30')
+>>> response = cli.start(container=container.get('Id'))
+>>> print(response)
+None
+```
+
+## stop
+
+Stops a container. Similar to the `docker stop` command.
+
+**Params**:
+
+* container (str): The container to stop
+* timeout (int): Timeout in seconds to wait for the container to stop before
+sending a `SIGKILL`
+
+## tag
+
+Tag an image into a repository. Identical to the `docker tag` command.
+
+**Params**:
+
+* image (str): The image to tag
+* repository (str): The repository to set for the tag
+* tag (str): The tag name
+* force (bool): Force
+
+**Returns** (bool): True if successful
+
+## top
+Display the running processes of a container
+
+**Params**:
+
+* container (str): The container to inspect
+
+**Returns** (str): The output of the top
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> cli.create_container('busybox:latest', '/bin/sleep 30', name='sleeper')
+>>> cli.start('sleeper')
+>>> cli.top('sleeper')
+{'Processes': [['952', 'root', '/bin/sleep 30']],
+ 'Titles': ['PID', 'USER', 'COMMAND']}
+```
+
+## unpause
+
+Unpauses all processes within a container.
+
+**Params**:
+
+* container (str): The container to unpause
+
+## version
+Nearly identical to the `docker version` command.
+
+**Returns** (dict): The server version information
+
+```python
+>>> from docker import Client
+>>> cli = Client(base_url='tcp://127.0.0.1:2375')
+>>> cli.version()
+{
+ "KernelVersion": "3.16.4-tinycore64",
+ "Arch": "amd64",
+ "ApiVersion": "1.15",
+ "Version": "1.3.0",
+ "GitCommit": "c78088f",
+ "Os": "linux",
+ "GoVersion": "go1.3.3"
+}
+```
+
+
+## wait
+Identical to the `docker wait` command. Block until a container stops, then
+print its exit code. Returns the value `-1` if no `StatusCode` is returned by
+the API.
+
+If `container` a dict, the `Id` key is used.
+
+**Params**:
+
+* container (str or dict): The container to wait on
+
+**Returns** (int): The exit code of the container
+
+
+<!---
+TODO:
+
+* events
+* get_image
+* load_image
+* resize
+
+-->
diff --git a/ChangeLog.md b/docs/change_log.md
index e58f28e..f42f6e0 100644
--- a/ChangeLog.md
+++ b/docs/change_log.md
@@ -1,5 +1,10 @@
-ChangeLog
-=========
+Change Log
+==========
+
+0.5.4
+-----
+
+* Added MkDocs documentation.
0.5.3
-----
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..e79b95a
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,36 @@
+# Contributing
+See the [Docker contributing guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
+The following is specific to docker-py.
+
+## Running the tests & Code Quality
+
+
+To get the source source code and run the unit tests, run:
+```
+$ git clone git://github.com/docker/docker-py.git
+$ cd docker-py
+$ pip install tox
+$ tox
+```
+
+## Building the docs
+Docs are built with [MkDocs](http://www.mkdocs.org/). For development, you can
+run the following in the project directory:
+```
+$ pip install -r docs-requirements.txt
+$ mkdocs serve
+```
+
+## Release Checklist
+
+Before a new release, please go through the following checklist:
+
+* Bump version in docker/version.py
+* Add a release note in docs/change_log.md
+* Git tag the version
+* Upload to pypi
+
+## Vulnerability Reporting
+For any security issues, please do NOT file an issue or pull request on github!
+Please contact [security@docker.com](mailto:security@docker.com) or read [the
+Docker security page](https://www.docker.com/resources/security/).
diff --git a/docs/favicon_whale.png b/docs/favicon_whale.png
new file mode 100644
index 0000000..ee01a5e
--- /dev/null
+++ b/docs/favicon_whale.png
Binary files differ
diff --git a/docs/host-devices.md b/docs/host-devices.md
new file mode 100644
index 0000000..ae1d32b
--- /dev/null
+++ b/docs/host-devices.md
@@ -0,0 +1,13 @@
+# Access to devices on the host
+
+If you need to directly expose some host devices to a container, you can use
+the devices parameter in the `Client.start` method as shown below
+
+```python
+c.start(container_id, devices=['/dev/sda:/dev/xvda:rwm'])
+```
+
+Each string is a single mapping using the colon (':') as the separator. So the
+above example essentially allow the container to have read write permissions to
+access the host's /dev/sda via a node named /dev/xvda in the container. The
+devices parameter is a list to allow multiple devices to be mapped.
diff --git a/docs/index.md b/docs/index.md
new file mode 100644
index 0000000..347e32c
--- /dev/null
+++ b/docs/index.md
@@ -0,0 +1,16 @@
+# docker-py documentation
+
+An API client for docker written in Python
+
+## Installation
+
+Our latest stable is always available on PyPi.
+
+ pip install docker-py
+
+## Documentation
+Full documentation is available in the `/docs/` directory.
+
+## License
+Docker is licensed under the Apache License, Version 2.0. See LICENSE for full
+license text
diff --git a/docs/port-bindings.md b/docs/port-bindings.md
new file mode 100644
index 0000000..6f240db
--- /dev/null
+++ b/docs/port-bindings.md
@@ -0,0 +1,38 @@
+# Port bindings
+Port bindings is done in two parts. Firstly, by providing a list of ports to
+open inside the container in the `Client().create_container()` method.
+
+```python
+c.create_container('busybox', 'ls', ports=[1111, 2222])
+```
+
+Bindings are then declared in the `Client.start` method.
+
+```python
+c.start(container_id, port_bindings={1111: 4567, 2222: None})
+```
+
+You can limit the host address on which the port will be exposed like such:
+
+```python
+c.start(container_id, port_bindings={1111: ('127.0.0.1', 4567)})
+```
+
+Or without host port assignment:
+
+```python
+c.start(container_id, port_bindings={1111: ('127.0.0.1',)})
+```
+
+If you wish to use UDP instead of TCP (default), you need to declare it
+like such in both the `create_container()` and `start()` calls:
+
+```python
+container_id = c.create_container(
+ 'busybox',
+ 'ls',
+ ports=[(1111, 'udp'), 2222]
+)
+c.start(container_id, port_bindings={'1111/udp': 4567, 2222: None})
+```
+
diff --git a/docs/tls.md b/docs/tls.md
new file mode 100644
index 0000000..3d167b7
--- /dev/null
+++ b/docs/tls.md
@@ -0,0 +1,85 @@
+## Connection to daemon using HTTPS
+
+**Note:** *These instructions are docker-py specific. Please refer to
+[http://docs.docker.com/articles/https/](http://docs.docker.com/articles/https/)
+first.*
+
+## TLSConfig
+
+**Params**:
+
+* client_cert (tuple of str): Path to client cert, path to client key
+* ca_cert (str): Path to CA cert file
+* verify (bool or str): This can be `False` or a path to a CA Cert file
+* ssl_version (int): A valid [SSL version](
+https://docs.python.org/3.4/library/ssl.html#ssl.PROTOCOL_TLSv1)
+* assert_hostname (bool): Verify hostname of docker daemon
+
+### configure_client
+
+**Params**:
+
+* client: ([Client](api.md#client-api)): A client to apply this config to
+
+
+## Authenticate server based on public/default CA pool
+
+```python
+client = docker.Client(base_url='<https_url>', tls=True)
+```
+
+Equivalent CLI options:
+```bash
+docker --tls ...
+```
+
+If you want to use TLS but don't want to verify the server certificate
+(for example when testing with a self-signed certificate):
+
+```python
+tls_config = docker.tls.TLSConfig(verify=False)
+client = docker.Client(base_url='<https_url>', tls=tls_config)
+```
+
+## Authenticate server based on given CA
+
+```python
+tls_config = docker.tls.TLSConfig(ca_cert='/path/to/ca.pem')
+client = docker.Client(base_url='<https_url>', tls=tls_config)
+```
+
+Equivalent CLI options:
+```bash
+docker --tlsverify --tlscacert /path/to/ca.pem ...`
+
+## Authenticate with client certificate, do not authenticate server based on given CA
+
+```python
+tls_config = docker.tls.TLSConfig(
+ client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem')
+)
+client = docker.Client(base_url='<https_url>', tls=tls_config)
+```
+
+Equivalent CLI options:
+```bash
+docker --tls --tlscert /path/to/client-cert.pem --tlskey /path/to/client-key.pem ...
+```
+
+## Authenticate with client certificate, authenticate server based on given CA
+
+```python
+tls_config = docker.tls.TLSConfig(
+ client_cert=('/path/to/client-cert.pem', '/path/to/client-key.pem'),
+ ca_cert='/path/to/ca.pem'
+)
+client = docker.Client(base_url='<https_url>', tls=tls_config)
+```
+
+Equivalent CLI options:
+```bash
+docker --tlsverify \
+ --tlscert /path/to/client-cert.pem \
+ --tlskey /path/to/client-key.pem \
+ --tlscacert /path/to/ca.pem ...
+```
diff --git a/docs/volumes.md b/docs/volumes.md
new file mode 100644
index 0000000..8c6b5d3
--- /dev/null
+++ b/docs/volumes.md
@@ -0,0 +1,25 @@
+# Using volumes
+
+Volume declaration is done in two parts. First, you have to provide a list of
+mountpoints to the `Client().create_container()` method.
+
+```python
+c.create_container('busybox', 'ls', volumes=['/mnt/vol1', '/mnt/vol2'])
+```
+
+Volume mappings are then declared inside the `Client.start` method like this:
+
+```python
+c.start(container_id, binds={
+ '/home/user1/':
+ {
+ 'bind': '/mnt/vol2',
+ 'ro': False
+ },
+ '/var/www':
+ {
+ 'bind': '/mnt/vol1',
+ 'ro': True
+ }
+})
+```
diff --git a/mkdocs.yml b/mkdocs.yml
new file mode 100644
index 0000000..46b1773
--- /dev/null
+++ b/mkdocs.yml
@@ -0,0 +1,15 @@
+site_name: docker-py Documentation
+site_description: An API client for Docker written in Python
+site_favicon: favicon_whale.png
+# site_url: docker-py.readthedocs.org
+repo_url: https://github.com/docker/docker-py/
+theme: readthedocs
+pages:
+- [index.md, Home]
+- [api.md, Client API]
+- [port-bindings.md, Port Bindings]
+- [volumes.md, Using Volumes]
+- [tls.md, Using TLS]
+- [host-devices.md, Host devices]
+- [change_log.md, Change Log]
+- [contributing.md, Contributing]
diff --git a/tests/fake_api.py b/tests/fake_api.py
index 861c8b3..9d76ee3 100644
--- a/tests/fake_api.py
+++ b/tests/fake_api.py
@@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-CURRENT_VERSION = 'v1.12'
+CURRENT_VERSION = 'v1.15'
FAKE_CONTAINER_ID = '3cc2351ab11b'
FAKE_IMAGE_ID = 'e9aa60c60128'
@@ -225,6 +225,20 @@ def get_fake_export():
return status_code, response
+def post_fake_execute():
+ status_code = 200
+ response = {'Id': FAKE_CONTAINER_ID}
+ return status_code, response
+
+
+def post_fake_execute_start():
+ status_code = 200
+ response = (b'\x01\x00\x00\x00\x00\x00\x00\x11bin\nboot\ndev\netc\n'
+ b'\x01\x00\x00\x00\x00\x00\x00\x12lib\nmnt\nproc\nroot\n'
+ b'\x01\x00\x00\x00\x00\x00\x00\x0csbin\nusr\nvar\n')
+ return status_code, response
+
+
def post_fake_stop_container():
status_code = 200
response = {'Id': FAKE_CONTAINER_ID}
@@ -237,6 +251,18 @@ def post_fake_kill_container():
return status_code, response
+def post_fake_pause_container():
+ status_code = 200
+ response = {'Id': FAKE_CONTAINER_ID}
+ return status_code, response
+
+
+def post_fake_unpause_container():
+ status_code = 200
+ response = {'Id': FAKE_CONTAINER_ID}
+ return status_code, response
+
+
def post_fake_restart_container():
status_code = 200
response = {'Id': FAKE_CONTAINER_ID}
@@ -330,10 +356,18 @@ fake_responses = {
get_fake_diff,
'{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix):
get_fake_export,
+ '{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix):
+ post_fake_execute,
+ '{1}/{0}/exec/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix):
+ post_fake_execute_start,
'{1}/{0}/containers/3cc2351ab11b/stop'.format(CURRENT_VERSION, prefix):
post_fake_stop_container,
'{1}/{0}/containers/3cc2351ab11b/kill'.format(CURRENT_VERSION, prefix):
post_fake_kill_container,
+ '{1}/{0}/containers/3cc2351ab11b/pause'.format(CURRENT_VERSION, prefix):
+ post_fake_pause_container,
+ '{1}/{0}/containers/3cc2351ab11b/unpause'.format(CURRENT_VERSION, prefix):
+ post_fake_unpause_container,
'{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix):
get_fake_port,
'{1}/{0}/containers/3cc2351ab11b/restart'.format(CURRENT_VERSION, prefix):
diff --git a/tests/integration_test.py b/tests/integration_test.py
index d4900b7..8a39ce3 100644
--- a/tests/integration_test.py
+++ b/tests/integration_test.py
@@ -627,6 +627,76 @@ class TestRestartingContainer(BaseTestCase):
res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)]
self.assertEqual(len(res), 0)
+
+class TestExecuteCommand(BaseTestCase):
+ def runTest(self):
+ container = self.client.create_container('busybox', 'cat',
+ detach=True, stdin_open=True)
+ id = container['Id']
+ self.client.start(id)
+ self.tmp_containers.append(id)
+
+ res = self.client.execute(id, ['echo', 'hello'])
+ expected = b'hello\n' if six.PY3 else 'hello\n'
+ self.assertEqual(res, expected)
+
+
+class TestExecuteCommandString(BaseTestCase):
+ def runTest(self):
+ container = self.client.create_container('busybox', 'cat',
+ detach=True, stdin_open=True)
+ id = container['Id']
+ self.client.start(id)
+ self.tmp_containers.append(id)
+
+ res = self.client.execute(id, 'echo hello world', stdout=True)
+ expected = b'hello world\n' if six.PY3 else 'hello world\n'
+ self.assertEqual(res, expected)
+
+
+class TestExecuteCommandStreaming(BaseTestCase):
+ def runTest(self):
+ container = self.client.create_container('busybox', 'cat',
+ detach=True, stdin_open=True)
+ id = container['Id']
+ self.client.start(id)
+ self.tmp_containers.append(id)
+
+ chunks = self.client.execute(id, ['echo', 'hello\nworld'], stream=True)
+ res = b'' if six.PY3 else ''
+ for chunk in chunks:
+ res += chunk
+ expected = b'hello\nworld\n' if six.PY3 else 'hello\nworld\n'
+ self.assertEqual(res, expected)
+
+
+class TestPauseUnpauseContainer(BaseTestCase):
+ def runTest(self):
+ container = self.client.create_container('busybox', ['sleep', '9999'])
+ id = container['Id']
+ self.client.pause(id)
+ container_info = self.client.inspect_container(id)
+ self.assertIn('State', container_info)
+ state = container_info['State']
+ self.assertIn('ExitCode', state)
+ self.assertEqual(state['ExitCode'], 0)
+ self.assertIn('Running', state)
+ self.assertEqual(state['Running'], True)
+ self.assertIn('Paused', state)
+ self.assertEqual(state['Paused'], True)
+
+ self.client.unpause(id)
+ container_info = self.client.inspect_container(id)
+ self.assertIn('State', container_info)
+ state = container_info['State']
+ self.assertIn('ExitCode', state)
+ self.assertEqual(state['ExitCode'], 0)
+ self.assertIn('Running', state)
+ self.assertEqual(state['Running'], True)
+ self.assertIn('Paused', state)
+ self.assertEqual(state['Paused'], False)
+
+
#################
# LINKS TESTS #
#################
diff --git a/tests/test.py b/tests/test.py
index 984e350..598ccac 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -349,6 +349,30 @@ class DockerClientTest(Cleanup, unittest.TestCase):
self.assertEqual(args[1]['headers'],
{'Content-Type': 'application/json'})
+ def test_create_container_with_cpuset(self):
+ try:
+ self.client.create_container('busybox', 'ls',
+ cpuset='0,1')
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+
+ args = fake_request.call_args
+ self.assertEqual(args[0][0],
+ url_prefix + 'containers/create')
+ self.assertEqual(json.loads(args[1]['data']),
+ json.loads('''
+ {"Tty": false, "Image": "busybox",
+ "Cmd": ["ls"], "AttachStdin": false,
+ "Memory": 0,
+ "AttachStderr": true,
+ "AttachStdout": true, "OpenStdin": false,
+ "StdinOnce": false,
+ "NetworkDisabled": false,
+ "Cpuset": "0,1",
+ "MemorySwap": 0}'''))
+ self.assertEqual(args[1]['headers'],
+ {'Content-Type': 'application/json'})
+
def test_create_container_with_working_dir(self):
try:
self.client.create_container('busybox', 'ls',
@@ -891,6 +915,41 @@ class DockerClientTest(Cleanup, unittest.TestCase):
docker.client.DEFAULT_TIMEOUT_SECONDS
)
+ def test_start_container_with_devices(self):
+ try:
+ self.client.start(fake_api.FAKE_CONTAINER_ID,
+ devices=['/dev/sda:/dev/xvda:rwm',
+ '/dev/sdb:/dev/xvdb',
+ '/dev/sdc'])
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+ args = fake_request.call_args
+ self.assertEqual(
+ args[0][0],
+ url_prefix + 'containers/3cc2351ab11b/start'
+ )
+ self.assertEqual(
+ json.loads(args[1]['data']),
+ {"PublishAllPorts": False, "Privileged": False,
+ "Devices": [{'CgroupPermissions': 'rwm',
+ 'PathInContainer': '/dev/sda:/dev/xvda:rwm',
+ 'PathOnHost': '/dev/sda:/dev/xvda:rwm'},
+ {'CgroupPermissions': 'rwm',
+ 'PathInContainer': '/dev/sdb:/dev/xvdb',
+ 'PathOnHost': '/dev/sdb:/dev/xvdb'},
+ {'CgroupPermissions': 'rwm',
+ 'PathInContainer': '/dev/sdc',
+ 'PathOnHost': '/dev/sdc'}]}
+ )
+ self.assertEqual(
+ args[1]['headers'],
+ {'Content-Type': 'application/json'}
+ )
+ self.assertEqual(
+ args[1]['timeout'],
+ docker.client.DEFAULT_TIMEOUT_SECONDS
+ )
+
def test_resize_container(self):
try:
self.client.resize(
@@ -1064,6 +1123,51 @@ class DockerClientTest(Cleanup, unittest.TestCase):
timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS + timeout)
)
+ def test_execute_command(self):
+ try:
+ self.client.execute(fake_api.FAKE_CONTAINER_ID, ['ls', '-1'])
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+
+ args = fake_request.call_args
+ self.assertEqual(args[0][0],
+ url_prefix + 'exec/3cc2351ab11b/start')
+
+ self.assertEqual(json.loads(args[1]['data']),
+ json.loads('''{
+ "Tty": false,
+ "AttachStderr": true,
+ "Container": "3cc2351ab11b",
+ "Cmd": ["ls", "-1"],
+ "AttachStdin": false,
+ "User": "",
+ "Detach": false,
+ "Privileged": false,
+ "AttachStdout": true}'''))
+
+ self.assertEqual(args[1]['headers'],
+ {'Content-Type': 'application/json'})
+
+ def test_pause_container(self):
+ try:
+ self.client.pause(fake_api.FAKE_CONTAINER_ID)
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+ fake_request.assert_called_with(
+ url_prefix + 'containers/3cc2351ab11b/pause',
+ timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS)
+ )
+
+ def test_unpause_container(self):
+ try:
+ self.client.unpause(fake_api.FAKE_CONTAINER_ID)
+ except Exception as e:
+ self.fail('Command should not raise exception: {0}'.format(e))
+ fake_request.assert_called_with(
+ url_prefix + 'containers/3cc2351ab11b/unpause',
+ timeout=(docker.client.DEFAULT_TIMEOUT_SECONDS)
+ )
+
def test_kill_container(self):
try:
self.client.kill(fake_api.FAKE_CONTAINER_ID)
@@ -1615,16 +1719,47 @@ class DockerClientTest(Cleanup, unittest.TestCase):
f.write("content")
for exclude, names in (
- (['*.py'], ['bar/a.txt', 'bar/other.png',
- 'test/foo/a.txt', 'test/foo/other.png']),
- (['*.png', 'bar'], ['test/foo/a.txt', 'test/foo/b.py']),
- (['test/foo', 'a.txt'], ['bar/a.txt', 'bar/b.py',
- 'bar/other.png']),
+ (['*.py'], ['bar', 'bar/a.txt', 'bar/other.png',
+ 'test', 'test/foo', 'test/foo/a.txt',
+ 'test/foo/other.png']),
+ (['*.png', 'bar'], ['test', 'test/foo', 'test/foo/a.txt',
+ 'test/foo/b.py']),
+ (['test/foo', 'a.txt'], ['bar', 'bar/a.txt', 'bar/b.py',
+ 'bar/other.png', 'test']),
):
archive = docker.utils.tar(base, exclude=exclude)
tar = tarfile.open(fileobj=archive)
self.assertEqual(sorted(tar.getnames()), names)
+ def test_tar_with_empty_directory(self):
+ base = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base)
+ for d in ['foo', 'bar']:
+ os.makedirs(os.path.join(base, d))
+ archive = docker.utils.tar(base)
+ tar = tarfile.open(fileobj=archive)
+ self.assertEqual(sorted(tar.getnames()), ['bar', 'foo'])
+
+ def test_tar_with_file_symlinks(self):
+ base = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base)
+ with open(os.path.join(base, 'foo'), 'w') as f:
+ f.write("content")
+ os.makedirs(os.path.join(base, 'bar'))
+ os.symlink('../foo', os.path.join(base, 'bar/foo'))
+ archive = docker.utils.tar(base)
+ tar = tarfile.open(fileobj=archive)
+ self.assertEqual(sorted(tar.getnames()), ['bar', 'bar/foo', 'foo'])
+
+ def test_tar_with_directory_symlinks(self):
+ base = tempfile.mkdtemp()
+ self.addCleanup(shutil.rmtree, base)
+ for d in ['foo', 'bar']:
+ os.makedirs(os.path.join(base, d))
+ os.symlink('../foo', os.path.join(base, 'bar/foo'))
+ archive = docker.utils.tar(base)
+ tar = tarfile.open(fileobj=archive)
+ self.assertEqual(sorted(tar.getnames()), ['bar', 'bar/foo', 'foo'])
if __name__ == '__main__':
unittest.main()
diff --git a/tests/testdata/certs/ca.pem b/tests/testdata/certs/ca.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/certs/ca.pem
diff --git a/tests/testdata/certs/cert.pem b/tests/testdata/certs/cert.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/certs/cert.pem
diff --git a/tests/testdata/certs/key.pem b/tests/testdata/certs/key.pem
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/testdata/certs/key.pem
diff --git a/tests/utils_test.py b/tests/utils_test.py
index 277781b..7532d83 100644
--- a/tests/utils_test.py
+++ b/tests/utils_test.py
@@ -1,12 +1,22 @@
import unittest
from docker.errors import DockerException
-from docker.utils import parse_repository_tag, parse_host
+from docker.utils import parse_repository_tag, parse_host, kwargs_from_env
+from docker.client import Client
+
+import os
+import os.path
class UtilsTest(unittest.TestCase):
longMessage = True
+ def setUp(self):
+ self.os_environ = os.environ.copy()
+
+ def tearDown(self):
+ os.environ = self.os_environ
+
def test_parse_repository_tag(self):
self.assertEqual(parse_repository_tag("root"),
("root", None))
@@ -53,5 +63,25 @@ class UtilsTest(unittest.TestCase):
for host, expected in valid_hosts.items():
self.assertEqual(parse_host(host), expected, msg=host)
+ def test_kwargs_from_env(self):
+ os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376',
+ DOCKER_CERT_PATH=os.path.join(
+ os.path.dirname(__file__),
+ 'testdata/certs'),
+ DOCKER_TLS_VERIFY='1')
+ kwargs = kwargs_from_env(assert_hostname=False)
+ self.assertEquals('https://192.168.59.103:2376', kwargs['base_url'])
+ self.assertIn('ca.pem', kwargs['tls'].verify)
+ self.assertIn('cert.pem', kwargs['tls'].cert[0])
+ self.assertIn('key.pem', kwargs['tls'].cert[1])
+ self.assertEquals(False, kwargs['tls'].assert_hostname)
+ try:
+ client = Client(**kwargs)
+ self.assertEquals(kwargs['base_url'], client.base_url)
+ self.assertEquals(kwargs['tls'].verify, client.verify)
+ self.assertEquals(kwargs['tls'].cert, client.cert)
+ except TypeError, e:
+ self.fail(e)
+
if __name__ == '__main__':
unittest.main()