diff options
author | micahhausler <micah.hausler@ambition.com> | 2014-10-29 15:21:18 -0400 |
---|---|---|
committer | micahhausler <micah.hausler@ambition.com> | 2014-10-29 15:21:18 -0400 |
commit | 1803c4b83dd2dd7cb04e07d5b784fa0c799525b0 (patch) | |
tree | f63725f6590db122f14f4b3563d6964a8c1bb322 | |
parent | 1476ad302dd1b6622b7c53d3bea2270e658b0248 (diff) | |
parent | cf9d75be0ac49066c3172da51c6d0d8d4bc9288d (diff) | |
download | docker-py-1803c4b83dd2dd7cb04e07d5b784fa0c799525b0.tar.gz |
Fixed conflict
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | MANIFEST.in | 3 | ||||
-rw-r--r-- | Makefile | 15 | ||||
-rw-r--r-- | README.md | 436 | ||||
-rw-r--r-- | docker/client.py | 75 | ||||
-rw-r--r-- | docker/utils/__init__.py | 2 | ||||
-rw-r--r-- | docker/utils/utils.py | 46 | ||||
-rw-r--r-- | docs-requirements.txt | 1 | ||||
-rw-r--r-- | docs/api.md | 736 | ||||
-rw-r--r-- | docs/change_log.md (renamed from ChangeLog.md) | 9 | ||||
-rw-r--r-- | docs/contributing.md | 36 | ||||
-rw-r--r-- | docs/favicon_whale.png | bin | 0 -> 1475 bytes | |||
-rw-r--r-- | docs/host-devices.md | 13 | ||||
-rw-r--r-- | docs/index.md | 16 | ||||
-rw-r--r-- | docs/port-bindings.md | 38 | ||||
-rw-r--r-- | docs/tls.md | 85 | ||||
-rw-r--r-- | docs/volumes.md | 25 | ||||
-rw-r--r-- | mkdocs.yml | 15 | ||||
-rw-r--r-- | tests/fake_api.py | 36 | ||||
-rw-r--r-- | tests/integration_test.py | 70 | ||||
-rw-r--r-- | tests/test.py | 145 | ||||
-rw-r--r-- | tests/testdata/certs/ca.pem | 0 | ||||
-rw-r--r-- | tests/testdata/certs/cert.pem | 0 | ||||
-rw-r--r-- | tests/testdata/certs/key.pem | 0 | ||||
-rw-r--r-- | tests/utils_test.py | 32 |
25 files changed, 1394 insertions, 447 deletions
@@ -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 + @@ -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 Binary files differnew file mode 100644 index 0000000..ee01a5e --- /dev/null +++ b/docs/favicon_whale.png 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() |