diff -Nru python-docker-1.5.0/.coveragerc python-docker-1.8.0/.coveragerc --- python-docker-1.5.0/.coveragerc 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/.coveragerc 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,10 @@ +[run] +branch = True +source = docker + +[report] +exclude_lines = + if __name__ == .__main__.: + +[html] +directory = html diff -Nru python-docker-1.5.0/debian/changelog python-docker-1.8.0/debian/changelog --- python-docker-1.5.0/debian/changelog 2015-11-08 20:59:42.000000000 +0000 +++ python-docker-1.8.0/debian/changelog 2016-04-11 15:11:40.000000000 +0000 @@ -1,3 +1,11 @@ +python-docker (1.8.0-0ubuntu1) xenial; urgency=medium + + * Update to 1.8.0 upstream release. + - Dropped d/p/0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch + Applied upstream. + + -- Pierre-André MOREY Mon, 11 Apr 2016 17:11:12 +0200 + python-docker (1.5.0-1) unstable; urgency=medium * Update to 1.5.0 upstream release. diff -Nru python-docker-1.5.0/debian/control python-docker-1.8.0/debian/control --- python-docker-1.5.0/debian/control 2015-11-08 20:59:13.000000000 +0000 +++ python-docker-1.8.0/debian/control 2016-04-12 16:14:22.000000000 +0000 @@ -1,7 +1,8 @@ Source: python-docker Section: python Priority: optional -Maintainer: Debian Python Modules Team +Maintainer: Ubuntu Developers +XSBC-Original-Maintainer: Debian Python Modules Team Uploaders: Paul Tagliamonte , Docker Packaging Team , Tianon Gravi diff -Nru python-docker-1.5.0/debian/patches/0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch python-docker-1.8.0/debian/patches/0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch --- python-docker-1.5.0/debian/patches/0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch 2015-11-08 20:59:13.000000000 +0000 +++ python-docker-1.8.0/debian/patches/0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,43 +0,0 @@ -From 06f1e6fdcf6eb6a33f067d2b35ccc5c84cdbfaf5 Mon Sep 17 00:00:00 2001 -From: Joffrey F -Date: Mon, 19 Oct 2015 16:55:49 -0700 -Subject: Fix Unix adapter bug with newer versions of requests - -The select_proxy utility in requests errors out when the provided URL -doesn't have a hostname, like is the case when using a UNIX socket. -Since proxies are an irrelevant notion in the case of UNIX sockets -anyway, we simply return the path URL directly. - -Signed-off-by: Joffrey F ---- - docker/unixconn/unixconn.py | 14 +++++++++++--- - 1 file changed, 11 insertions(+), 3 deletions(-) - -diff --git a/docker/unixconn/unixconn.py b/docker/unixconn/unixconn.py -index 551bd29..d7e249e 100644 ---- a/docker/unixconn/unixconn.py -+++ b/docker/unixconn/unixconn.py -@@ -73,12 +73,20 @@ class UnixAdapter(requests.adapters.HTTPAdapter): - if pool: - return pool - -- pool = UnixHTTPConnectionPool(url, -- self.socket_path, -- self.timeout) -+ pool = UnixHTTPConnectionPool( -+ url, self.socket_path, self.timeout -+ ) - self.pools[url] = pool - - return pool - -+ def request_url(self, request, proxies): -+ # The select_proxy utility in requests errors out when the provided URL -+ # doesn't have a hostname, like is the case when using a UNIX socket. -+ # Since proxies are an irrelevant notion in the case of UNIX sockets -+ # anyway, we simply return the path URL directly. -+ # See also: https://github.com/docker/docker-py/issues/811 -+ return request.path_url -+ - def close(self): - self.pools.clear() diff -Nru python-docker-1.5.0/debian/patches/requirements.patch python-docker-1.8.0/debian/patches/requirements.patch --- python-docker-1.5.0/debian/patches/requirements.patch 2015-11-08 20:59:13.000000000 +0000 +++ python-docker-1.8.0/debian/patches/requirements.patch 2016-04-11 15:05:01.000000000 +0000 @@ -13,30 +13,35 @@ test-requirements.txt | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) -diff --git a/docker_py.egg-info/requires.txt b/docker_py.egg-info/requires.txt -index 43cfa6a..aa5b97e 100644 ---- a/docker_py.egg-info/requires.txt -+++ b/docker_py.egg-info/requires.txt -@@ -1,3 +1,3 @@ +Index: Ubuntu-1.8.0/docker_py.egg-info/requires.txt +=================================================================== +--- Ubuntu-1.8.0.orig/docker_py.egg-info/requires.txt ++++ Ubuntu-1.8.0/docker_py.egg-info/requires.txt +@@ -1,6 +1,6 @@ -requests >= 2.5.2 +requests >= 2.5.3 six >= 1.4.0 -websocket-client >= 0.32.0 +websocket-client >= 0.18.0 -diff --git a/requirements.txt b/requirements.txt -index 72c255d..cd79880 100644 ---- a/requirements.txt -+++ b/requirements.txt -@@ -1,3 +1,3 @@ + + [:python_version < "3"] + py2-ipaddress >= 3.4.1 +Index: Ubuntu-1.8.0/requirements.txt +=================================================================== +--- Ubuntu-1.8.0.orig/requirements.txt ++++ Ubuntu-1.8.0/requirements.txt +@@ -1,4 +1,4 @@ -requests==2.5.3 +requests>=2.5.3 six>=1.4.0 -websocket-client==0.32.0 +websocket-client>=0.18.0 -diff --git a/setup.py b/setup.py -index 1d0a842..3de226b 100644 ---- a/setup.py -+++ b/setup.py + py2-ipaddress==3.4.1 ; python_version < '3.2' +\ No newline at end of file +Index: Ubuntu-1.8.0/setup.py +=================================================================== +--- Ubuntu-1.8.0.orig/setup.py ++++ Ubuntu-1.8.0/setup.py @@ -7,9 +7,9 @@ ROOT_DIR = os.path.dirname(__file__) SOURCE_DIR = os.path.join(ROOT_DIR) @@ -48,11 +53,11 @@ + 'websocket-client >= 0.18.0', ] - exec(open('docker/version.py').read()) -diff --git a/test-requirements.txt b/test-requirements.txt -index be49988..45b2b38 100644 ---- a/test-requirements.txt -+++ b/test-requirements.txt + extras_require = { +Index: Ubuntu-1.8.0/test-requirements.txt +=================================================================== +--- Ubuntu-1.8.0.orig/test-requirements.txt ++++ Ubuntu-1.8.0/test-requirements.txt @@ -1,5 +1,5 @@ -mock==1.0.1 -pytest==2.7.2 diff -Nru python-docker-1.5.0/debian/patches/series python-docker-1.8.0/debian/patches/series --- python-docker-1.5.0/debian/patches/series 2015-11-08 20:59:13.000000000 +0000 +++ python-docker-1.8.0/debian/patches/series 2016-04-11 14:51:36.000000000 +0000 @@ -1,2 +1 @@ requirements.patch -0002-Fix-Unix-adapter-bug-with-newer-versions-of-requests.patch diff -Nru python-docker-1.5.0/docker/api/build.py python-docker-1.8.0/docker/api/build.py --- python-docker-1.5.0/docker/api/build.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/build.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,6 +1,7 @@ import logging import os import re +import json from .. import constants from .. import errors @@ -16,11 +17,15 @@ nocache=False, rm=False, stream=False, timeout=None, custom_context=False, encoding=None, pull=False, forcerm=False, dockerfile=None, container_limits=None, - decode=False): + decode=False, buildargs=None, gzip=False): remote = context = headers = None container_limits = container_limits or {} if path is None and fileobj is None: raise TypeError("Either path or fileobj needs to be provided.") + if gzip and encoding is not None: + raise errors.DockerException( + 'Can not use custom encoding if gzip is enabled' + ) for key in container_limits.keys(): if key not in constants.CONTAINER_LIMITS_KEYS: @@ -45,7 +50,10 @@ if os.path.exists(dockerignore): with open(dockerignore, 'r') as f: exclude = list(filter(bool, f.read().splitlines())) - context = utils.tar(path, exclude=exclude, dockerfile=dockerfile) + context = utils.tar( + path, exclude=exclude, dockerfile=dockerfile, gzip=gzip + ) + encoding = 'gzip' if gzip else encoding if utils.compare_version('1.8', self._version) >= 0: stream = True @@ -71,6 +79,14 @@ } params.update(container_limits) + if buildargs: + if utils.version_gte(self._version, '1.21'): + params.update({'buildargs': json.dumps(buildargs)}) + else: + raise errors.InvalidVersion( + 'buildargs was only introduced in API version 1.21' + ) + if context is not None: headers = {'Content-Type': 'application/tar'} if encoding: diff -Nru python-docker-1.5.0/docker/api/container.py python-docker-1.8.0/docker/api/container.py --- python-docker-1.5.0/docker/api/container.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/container.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,8 +1,10 @@ import six import warnings +from datetime import datetime from .. import errors from .. import utils +from ..utils.utils import create_networking_config, create_endpoint_config class ContainerApiMixin(object): @@ -38,13 +40,14 @@ @utils.check_resource def commit(self, container, repository=None, tag=None, message=None, - author=None, conf=None): + author=None, changes=None, conf=None): params = { 'container': container, 'repo': repository, 'tag': tag, 'comment': message, - 'author': author + 'author': author, + 'changes': changes } u = self._url("/commit") return self._result(self._post_json(u, data=conf, params=params), @@ -96,7 +99,8 @@ network_disabled=False, name=None, entrypoint=None, cpu_shares=None, working_dir=None, domainname=None, memswap_limit=None, cpuset=None, host_config=None, - mac_address=None, labels=None, volume_driver=None): + mac_address=None, labels=None, volume_driver=None, + stop_signal=None, networking_config=None): if isinstance(volumes, six.string_types): volumes = [volumes, ] @@ -111,7 +115,7 @@ tty, mem_limit, ports, environment, dns, volumes, volumes_from, network_disabled, entrypoint, cpu_shares, working_dir, domainname, memswap_limit, cpuset, host_config, mac_address, labels, - volume_driver + volume_driver, stop_signal, networking_config, ) return self.create_container_from_config(config, name) @@ -137,6 +141,12 @@ kwargs['version'] = self._version return utils.create_host_config(*args, **kwargs) + def create_networking_config(self, *args, **kwargs): + return create_networking_config(*args, **kwargs) + + def create_endpoint_config(self, *args, **kwargs): + return create_endpoint_config(self._version, *args, **kwargs) + @utils.check_resource def diff(self, container): return self._result( @@ -184,17 +194,30 @@ @utils.check_resource def logs(self, container, stdout=True, stderr=True, stream=False, - timestamps=False, tail='all'): + timestamps=False, tail='all', since=None, follow=None): if utils.compare_version('1.11', self._version) >= 0: + if follow is None: + follow = stream params = {'stderr': stderr and 1 or 0, 'stdout': stdout and 1 or 0, 'timestamps': timestamps and 1 or 0, - 'follow': stream and 1 or 0, + 'follow': follow and 1 or 0, } if utils.compare_version('1.13', self._version) >= 0: - if tail != 'all' and (not isinstance(tail, int) or tail <= 0): + if tail != 'all' and (not isinstance(tail, int) or tail < 0): tail = 'all' params['tail'] = tail + + if since is not None: + if utils.compare_version('1.19', self._version) < 0: + raise errors.InvalidVersion( + 'since is not supported in API < 1.19' + ) + else: + if isinstance(since, datetime): + params['since'] = utils.datetime_to_timestamp(since) + elif (isinstance(since, int) and since > 0): + params['since'] = since url = self._url("/containers/{0}/logs", container) res = self._get(url, params=params, stream=stream) return self._get_result(container, stream, res) @@ -344,9 +367,14 @@ @utils.minimum_version('1.17') @utils.check_resource - def stats(self, container, decode=None): + def stats(self, container, decode=None, stream=True): url = self._url("/containers/{0}/stats", container) - return self._stream_helper(self._get(url, stream=True), decode=decode) + if stream: + return self._stream_helper(self._get(url, stream=True), + decode=decode) + else: + return self._result(self._get(url, params={'stream': False}), + json=True) @utils.check_resource def stop(self, container, timeout=10): @@ -371,6 +399,39 @@ res = self._post(url) self._raise_for_status(res) + @utils.minimum_version('1.22') + @utils.check_resource + def update_container( + self, container, blkio_weight=None, cpu_period=None, cpu_quota=None, + cpu_shares=None, cpuset_cpus=None, cpuset_mems=None, mem_limit=None, + mem_reservation=None, memswap_limit=None, kernel_memory=None + ): + url = self._url('/containers/{0}/update', container) + data = {} + if blkio_weight: + data['BlkioWeight'] = blkio_weight + if cpu_period: + data['CpuPeriod'] = cpu_period + if cpu_shares: + data['CpuShares'] = cpu_shares + if cpu_quota: + data['CpuQuota'] = cpu_quota + if cpuset_cpus: + data['CpusetCpus'] = cpuset_cpus + if cpuset_mems: + data['CpusetMems'] = cpuset_mems + if mem_limit: + data['Memory'] = utils.parse_bytes(mem_limit) + if mem_reservation: + data['MemoryReservation'] = utils.parse_bytes(mem_reservation) + if memswap_limit: + data['MemorySwap'] = utils.parse_bytes(memswap_limit) + if kernel_memory: + data['KernelMemory'] = utils.parse_bytes(kernel_memory) + + res = self._post_json(url, data=data) + return self._result(res, True) + @utils.check_resource def wait(self, container, timeout=None): url = self._url("/containers/{0}/wait", container) diff -Nru python-docker-1.5.0/docker/api/exec_api.py python-docker-1.8.0/docker/api/exec_api.py --- python-docker-1.5.0/docker/api/exec_api.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/exec_api.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,5 +1,3 @@ -import shlex - import six from .. import errors @@ -9,8 +7,8 @@ class ExecApiMixin(object): @utils.minimum_version('1.15') @utils.check_resource - def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False, - privileged=False, user=''): + def exec_create(self, container, cmd, stdout=True, stderr=True, + stdin=False, tty=False, privileged=False, user=''): if privileged and utils.compare_version('1.19', self._version) < 0: raise errors.InvalidVersion( 'Privileged exec is not supported in API < 1.19' @@ -20,14 +18,14 @@ 'User-specific exec is not supported in API < 1.19' ) if isinstance(cmd, six.string_types): - cmd = shlex.split(str(cmd)) + cmd = utils.split_command(cmd) data = { 'Container': container, 'User': user, 'Privileged': privileged, 'Tty': tty, - 'AttachStdin': False, + 'AttachStdin': stdin, 'AttachStdout': stdout, 'AttachStderr': stderr, 'Cmd': cmd @@ -55,7 +53,11 @@ self._raise_for_status(res) @utils.minimum_version('1.15') - def exec_start(self, exec_id, detach=False, tty=False, stream=False): + def exec_start(self, exec_id, detach=False, tty=False, stream=False, + socket=False): + # we want opened socket if socket == True + if socket: + stream = True if isinstance(exec_id, dict): exec_id = exec_id.get('Id') @@ -67,4 +69,7 @@ res = self._post_json( self._url('/exec/{0}/start', exec_id), data=data, stream=stream ) + + if socket: + return self._get_raw_response_socket(res) return self._get_result_tty(stream, res, tty) diff -Nru python-docker-1.5.0/docker/api/image.py python-docker-1.8.0/docker/api/image.py --- python-docker-1.5.0/docker/api/image.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/image.py 2016-04-11 14:18:22.000000000 +0000 @@ -148,7 +148,7 @@ self._raise_for_status(res) def pull(self, repository, tag=None, stream=False, - insecure_registry=False, auth_config=None): + insecure_registry=False, auth_config=None, decode=False): if insecure_registry: warnings.warn( INSECURE_REGISTRY_DEPRECATION_WARNING.format('pull()'), @@ -158,8 +158,6 @@ if not tag: repository, tag = utils.parse_repository_tag(repository) registry, repo_name = auth.resolve_repository_name(repository) - if repo_name.count(":") == 1: - repository, tag = repository.rsplit(":", 1) params = { 'tag': tag, @@ -174,7 +172,8 @@ log.debug('Looking for auth config') if not self._auth_configs: log.debug( - "No auth config in memory - loading from filesystem") + "No auth config in memory - loading from filesystem" + ) self._auth_configs = auth.load_config() authcfg = auth.resolve_authconfig(self._auth_configs, registry) # Do not fail here if no authentication exists for this @@ -201,12 +200,12 @@ self._raise_for_status(response) if stream: - return self._stream_helper(response) + return self._stream_helper(response, decode=decode) return self._result(response) def push(self, repository, tag=None, stream=False, - insecure_registry=False): + insecure_registry=False, decode=False): if insecure_registry: warnings.warn( INSECURE_REGISTRY_DEPRECATION_WARNING.format('push()'), @@ -242,7 +241,7 @@ self._raise_for_status(response) if stream: - return self._stream_helper(response) + return self._stream_helper(response, decode=decode) return self._result(response) diff -Nru python-docker-1.5.0/docker/api/network.py python-docker-1.8.0/docker/api/network.py --- python-docker-1.5.0/docker/api/network.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/network.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,6 +1,8 @@ import json -from ..utils import check_resource, minimum_version +from ..errors import InvalidVersion +from ..utils import check_resource, minimum_version, normalize_links +from ..utils import version_lt class NetworkApiMixin(object): @@ -19,10 +21,17 @@ return self._result(res, json=True) @minimum_version('1.21') - def create_network(self, name, driver=None): + def create_network(self, name, driver=None, options=None, ipam=None, + check_duplicate=None): + if options is not None and not isinstance(options, dict): + raise TypeError('options must be a dictionary') + data = { - 'name': name, - 'driver': driver, + 'Name': name, + 'Driver': driver, + 'Options': options, + 'IPAM': ipam, + 'CheckDuplicate': check_duplicate } url = self._url("/networks/create") res = self._post_json(url, data=data) @@ -42,14 +51,39 @@ @check_resource @minimum_version('1.21') - def connect_container_to_network(self, container, net_id): - data = {"container": container} + def connect_container_to_network(self, container, net_id, + ipv4_address=None, ipv6_address=None, + aliases=None, links=None): + data = { + "Container": container, + "EndpointConfig": { + "Aliases": aliases, + "Links": normalize_links(links) if links else None, + }, + } + + # IPv4 or IPv6 or neither: + if ipv4_address or ipv6_address: + if version_lt(self._version, '1.22'): + raise InvalidVersion('IP address assignment is not ' + 'supported in API version < 1.22') + + data['EndpointConfig']['IPAMConfig'] = dict() + if ipv4_address: + data['EndpointConfig']['IPAMConfig']['IPv4Address'] = \ + ipv4_address + if ipv6_address: + data['EndpointConfig']['IPAMConfig']['IPv6Address'] = \ + ipv6_address + url = self._url("/networks/{0}/connect", net_id) - self._post_json(url, data=data) + res = self._post_json(url, data=data) + self._raise_for_status(res) @check_resource @minimum_version('1.21') def disconnect_container_from_network(self, container, net_id): data = {"container": container} url = self._url("/networks/{0}/disconnect", net_id) - self._post_json(url, data=data) + res = self._post_json(url, data=data) + self._raise_for_status(res) diff -Nru python-docker-1.5.0/docker/api/volume.py python-docker-1.8.0/docker/api/volume.py --- python-docker-1.5.0/docker/api/volume.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/api/volume.py 2016-04-11 14:18:22.000000000 +0000 @@ -5,14 +5,14 @@ @utils.minimum_version('1.21') def volumes(self, filters=None): params = { - 'filter': utils.convert_filters(filters) if filters else None + 'filters': utils.convert_filters(filters) if filters else None } url = self._url('/volumes') return self._result(self._get(url, params=params), True) @utils.minimum_version('1.21') def create_volume(self, name, driver=None, driver_opts=None): - url = self._url('/volumes') + url = self._url('/volumes/create') if driver_opts is not None and not isinstance(driver_opts, dict): raise TypeError('driver_opts must be a dictionary') @@ -33,4 +33,3 @@ url = self._url('/volumes/{0}', name) resp = self._delete(url) self._raise_for_status(resp) - return True diff -Nru python-docker-1.5.0/docker/auth/auth.py python-docker-1.8.0/docker/auth/auth.py --- python-docker-1.5.0/docker/auth/auth.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/auth/auth.py 2016-04-11 14:18:22.000000000 +0000 @@ -13,18 +13,15 @@ # limitations under the License. import base64 -import fileinput import json import logging import os -import warnings import six -from .. import constants from .. import errors -INDEX_NAME = 'index.docker.io' +INDEX_NAME = 'docker.io' INDEX_URL = 'https://{0}/v1/'.format(INDEX_NAME) DOCKER_CONFIG_FILENAME = os.path.join('.docker', 'config.json') LEGACY_DOCKER_CONFIG_FILENAME = '.dockercfg' @@ -32,31 +29,36 @@ log = logging.getLogger(__name__) -def resolve_repository_name(repo_name, insecure=False): - if insecure: - warnings.warn( - constants.INSECURE_REGISTRY_DEPRECATION_WARNING.format( - 'resolve_repository_name()' - ), DeprecationWarning - ) - +def resolve_repository_name(repo_name): if '://' in repo_name: raise errors.InvalidRepository( - 'Repository name cannot contain a scheme ({0})'.format(repo_name)) - parts = repo_name.split('/', 1) - if '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost': - # This is a docker index repo (ex: foo/bar or ubuntu) - return INDEX_NAME, repo_name - if len(parts) < 2: - raise errors.InvalidRepository( - 'Invalid repository name ({0})'.format(repo_name)) + 'Repository name cannot contain a scheme ({0})'.format(repo_name) + ) - if 'index.docker.io' in parts[0]: + index_name, remote_name = split_repo_name(repo_name) + if index_name[0] == '-' or index_name[-1] == '-': raise errors.InvalidRepository( - 'Invalid repository name, try "{0}" instead'.format(parts[1]) + 'Invalid index name ({0}). Cannot begin or end with a' + ' hyphen.'.format(index_name) ) + return resolve_index_name(index_name), remote_name + - return parts[0], parts[1] +def resolve_index_name(index_name): + index_name = convert_to_hostname(index_name) + if index_name == 'index.' + INDEX_NAME: + index_name = INDEX_NAME + return index_name + + +def split_repo_name(repo_name): + parts = repo_name.split('/', 1) + if len(parts) == 1 or ( + '.' not in parts[0] and ':' not in parts[0] and parts[0] != 'localhost' + ): + # This is a docker index repo (ex: username/foobar or ubuntu) + return INDEX_NAME, repo_name + return tuple(parts) def resolve_authconfig(authconfig, registry=None): @@ -67,7 +69,7 @@ Returns None if no match was found. """ # Default to the public index server - registry = convert_to_hostname(registry) if registry else INDEX_NAME + registry = resolve_index_name(registry) if registry else INDEX_NAME log.debug("Looking for auth entry for {0}".format(repr(registry))) if registry in authconfig: @@ -75,7 +77,7 @@ return authconfig[registry] for key, config in six.iteritems(authconfig): - if convert_to_hostname(key) == registry: + if resolve_index_name(key) == registry: log.debug("Found {0}".format(repr(key))) return config @@ -87,17 +89,12 @@ return url.replace('http://', '').replace('https://', '').split('/', 1)[0] -def encode_auth(auth_info): - return base64.b64encode(auth_info.get('username', '') + b':' + - auth_info.get('password', '')) - - def decode_auth(auth): if isinstance(auth, six.string_types): auth = auth.encode('ascii') s = base64.b64decode(auth) login, pwd = s.split(b':', 1) - return login.decode('ascii'), pwd.decode('ascii') + return login.decode('utf8'), pwd.decode('utf8') def encode_header(auth): @@ -105,12 +102,14 @@ return base64.urlsafe_b64encode(auth_json) -def parse_auth(entries): +def parse_auth(entries, raise_on_error=False): """ Parses authentication entries Args: - entries: Dict of authentication entries. + entries: Dict of authentication entries. + raise_on_error: If set to true, an invalid format will raise + InvalidConfigFile Returns: Authentication registry. @@ -118,6 +117,29 @@ conf = {} for registry, entry in six.iteritems(entries): + if not isinstance(entry, dict): + log.debug( + 'Config entry for key {0} is not auth config'.format(registry) + ) + # We sometimes fall back to parsing the whole config as if it was + # the auth config by itself, for legacy purposes. In that case, we + # fail silently and return an empty conf if any of the keys is not + # formatted properly. + if raise_on_error: + raise errors.InvalidConfigFile( + 'Invalid configuration for registry {0}'.format(registry) + ) + return {} + if 'auth' not in entry: + # Starting with engine v1.11 (API 1.23), an empty dictionary is + # a valid value in the auths config. + # https://github.com/docker/compose/issues/3265 + log.debug( + 'Auth data for {0} is absent. Client might be using a ' + 'credentials store instead.' + ) + return {} + username, password = decode_auth(entry['auth']) log.debug( 'Found entry (registry={0}, username={1})' @@ -126,84 +148,93 @@ conf[registry] = { 'username': username, 'password': password, - 'email': entry['email'], + 'email': entry.get('email'), 'serveraddress': registry, } return conf +def find_config_file(config_path=None): + environment_path = os.path.join( + os.environ.get('DOCKER_CONFIG'), + os.path.basename(DOCKER_CONFIG_FILENAME) + ) if os.environ.get('DOCKER_CONFIG') else None + + paths = [ + config_path, # 1 + environment_path, # 2 + os.path.join(os.path.expanduser('~'), DOCKER_CONFIG_FILENAME), # 3 + os.path.join( + os.path.expanduser('~'), LEGACY_DOCKER_CONFIG_FILENAME + ) # 4 + ] + + for path in paths: + if path and os.path.exists(path): + return path + return None + + def load_config(config_path=None): """ Loads authentication data from a Docker configuration file in the given root directory or if config_path is passed use given path. + Lookup priority: + explicit config_path parameter > DOCKER_CONFIG environment variable > + ~/.docker/config.json > ~/.dockercfg """ - conf = {} - data = None + config_file = find_config_file(config_path) - # Prefer ~/.docker/config.json. - config_file = config_path or os.path.join(os.path.expanduser('~'), - DOCKER_CONFIG_FILENAME) - - log.debug("Trying {0}".format(config_file)) - - if os.path.exists(config_file): - try: - with open(config_file) as f: - for section, data in six.iteritems(json.load(f)): - if section != 'auths': - continue - log.debug("Found 'auths' section") - return parse_auth(data) - log.debug("Couldn't find 'auths' section") - except (IOError, KeyError, ValueError) as e: - # Likely missing new Docker config file or it's in an - # unknown format, continue to attempt to read old location - # and format. - log.debug(e) - pass - else: + if not config_file: log.debug("File doesn't exist") - - config_file = config_path or os.path.join(os.path.expanduser('~'), - LEGACY_DOCKER_CONFIG_FILENAME) - - log.debug("Trying {0}".format(config_file)) - - if not os.path.exists(config_file): - log.debug("File doesn't exist - returning empty config") return {} - log.debug("Attempting to parse as JSON") try: with open(config_file) as f: - return parse_auth(json.load(f)) - except Exception as e: + data = json.load(f) + res = {} + if data.get('auths'): + log.debug("Found 'auths' section") + res.update(parse_auth(data['auths'], raise_on_error=True)) + if data.get('HttpHeaders'): + log.debug("Found 'HttpHeaders' section") + res.update({'HttpHeaders': data['HttpHeaders']}) + if data.get('credsStore'): + log.debug("Found 'credsStore' section") + res.update({'credsStore': data['credsStore']}) + if res: + return res + else: + log.debug("Couldn't find 'auths' or 'HttpHeaders' sections") + f.seek(0) + return parse_auth(json.load(f)) + except (IOError, KeyError, ValueError) as e: + # Likely missing new Docker config file or it's in an + # unknown format, continue to attempt to read old location + # and format. log.debug(e) - pass - # If that fails, we assume the configuration file contains a single - # authentication token for the public registry in the following format: - # - # auth = AUTH_TOKEN - # email = email@domain.com log.debug("Attempting to parse legacy auth file format") try: data = [] - for line in fileinput.input(config_file): - data.append(line.strip().split(' = ')[1]) - if len(data) < 2: - # Not enough data - raise errors.InvalidConfigFile( - 'Invalid or empty configuration file!') + with open(config_file) as f: + for line in f.readlines(): + data.append(line.strip().split(' = ')[1]) + if len(data) < 2: + # Not enough data + raise errors.InvalidConfigFile( + 'Invalid or empty configuration file!' + ) username, password = decode_auth(data[0]) - conf[INDEX_NAME] = { - 'username': username, - 'password': password, - 'email': data[1], - 'serveraddress': INDEX_URL, + return { + INDEX_NAME: { + 'username': username, + 'password': password, + 'email': data[1], + 'serveraddress': INDEX_URL, + } } - return conf except Exception as e: log.debug(e) pass diff -Nru python-docker-1.5.0/docker/client.py python-docker-1.8.0/docker/client.py --- python-docker-1.5.0/docker/client.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/client.py 2016-04-11 14:18:22.000000000 +0000 @@ -28,10 +28,14 @@ from .auth import auth from .unixconn import unixconn from .ssladapter import ssladapter -from .utils import utils, check_resource +from .utils import utils, check_resource, update_headers, kwargs_from_env from .tls import TLSConfig +def from_env(**kwargs): + return Client.from_env(**kwargs) + + class Client( requests.Session, api.BuildApiMixin, @@ -45,17 +49,17 @@ timeout=constants.DEFAULT_TIMEOUT_SECONDS, tls=False): super(Client, self).__init__() - if tls and not base_url.startswith('https://'): + if tls and not base_url: raise errors.TLSParameterError( - 'If using TLS, the base_url argument must begin with ' - '"https://".') + 'If using TLS, the base_url argument must be provided.' + ) self.base_url = base_url self.timeout = timeout self._auth_configs = auth.load_config() - base_url = utils.parse_host(base_url, sys.platform) + base_url = utils.parse_host(base_url, sys.platform, tls=bool(tls)) if base_url.startswith('http+unix://'): self._custom_adapter = unixconn.UnixAdapter(base_url, timeout) self.mount('http+docker://', self._custom_adapter) @@ -84,6 +88,10 @@ ) ) + @classmethod + def from_env(cls, **kwargs): + return cls(**kwargs_from_env(**kwargs)) + def _retrieve_server_version(self): try: return self.version(api_version=False)["ApiVersion"] @@ -103,15 +111,19 @@ kwargs.setdefault('timeout', self.timeout) return kwargs + @update_headers def _post(self, url, **kwargs): return self.post(url, **self._set_request_timeout(kwargs)) + @update_headers def _get(self, url, **kwargs): return self.get(url, **self._set_request_timeout(kwargs)) + @update_headers def _put(self, url, **kwargs): return self.put(url, **self._set_request_timeout(kwargs)) + @update_headers def _delete(self, url, **kwargs): return self.delete(url, **self._set_request_timeout(kwargs)) @@ -188,6 +200,8 @@ self._raise_for_status(response) if six.PY3: sock = response.raw._fp.fp.raw + if self.base_url.startswith("https://"): + sock = sock._sock else: sock = response.raw._fp.fp._sock try: @@ -244,10 +258,7 @@ # Disable timeout on the underlying socket to prevent # Read timed out(s) for long running processes socket = self._get_raw_response_socket(response) - if six.PY3: - socket._sock.settimeout(None) - else: - socket.settimeout(None) + self._disable_socket_timeout(socket) while True: header = response.raw.read(constants.STREAM_HEADER_SIZE_BYTES) @@ -276,6 +287,19 @@ for out in response.iter_content(chunk_size=1, decode_unicode=True): yield out + def _disable_socket_timeout(self, socket): + """ Depending on the combination of python version and whether we're + connecting over http or https, we might need to access _sock, which + may or may not exist; or we may need to just settimeout on socket + itself, which also may or may not have settimeout on it. + + To avoid missing the correct one, we try both. + """ + if hasattr(socket, "settimeout"): + socket.settimeout(None) + if hasattr(socket, "_sock") and hasattr(socket._sock, "settimeout"): + socket._sock.settimeout(None) + def _get_result(self, container, stream, res): cont = self.inspect_container(container) return self._get_result_tty(stream, res, cont['Config']['Tty']) diff -Nru python-docker-1.5.0/docker/constants.py python-docker-1.8.0/docker/constants.py --- python-docker-1.5.0/docker/constants.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/constants.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,4 +1,4 @@ -DEFAULT_DOCKER_API_VERSION = '1.20' +DEFAULT_DOCKER_API_VERSION = '1.22' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 CONTAINER_LIMITS_KEYS = [ diff -Nru python-docker-1.5.0/docker/errors.py python-docker-1.8.0/docker/errors.py --- python-docker-1.5.0/docker/errors.py 2015-09-10 22:08:54.000000000 +0000 +++ python-docker-1.8.0/docker/errors.py 2016-04-11 14:18:22.000000000 +0000 @@ -80,8 +80,8 @@ def __str__(self): return self.msg + (". TLS configurations should map the Docker CLI " "client configurations. See " - "http://docs.docker.com/examples/https/ for " - "API details.") + "https://docs.docker.com/engine/articles/https/ " + "for API details.") class NullResource(DockerException, ValueError): diff -Nru python-docker-1.5.0/docker/__init__.py python-docker-1.8.0/docker/__init__.py --- python-docker-1.5.0/docker/__init__.py 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/docker/__init__.py 2016-04-11 14:18:22.000000000 +0000 @@ -17,4 +17,4 @@ __version__ = version __title__ = 'docker-py' -from .client import Client, AutoVersionClient # flake8: noqa +from .client import Client, AutoVersionClient, from_env # flake8: noqa diff -Nru python-docker-1.5.0/docker/ssladapter/ssladapter.py python-docker-1.8.0/docker/ssladapter/ssladapter.py --- python-docker-1.5.0/docker/ssladapter/ssladapter.py 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/docker/ssladapter/ssladapter.py 2016-04-11 14:18:22.000000000 +0000 @@ -2,32 +2,30 @@ https://lukasa.co.uk/2013/01/Choosing_SSL_Version_In_Requests/ https://github.com/kennethreitz/requests/pull/799 """ +import sys + from distutils.version import StrictVersion from requests.adapters import HTTPAdapter -import ssl try: import requests.packages.urllib3 as urllib3 except ImportError: import urllib3 -PoolManager = urllib3.poolmanager.PoolManager +PoolManager = urllib3.poolmanager.PoolManager -def get_max_tls_protocol(): - protocols = ('PROTOCOL_TLSv1_2', - 'PROTOCOL_TLSv1_1', - 'PROTOCOL_TLSv1') - for proto in protocols: - if hasattr(ssl, proto): - return getattr(ssl, proto) +# Monkey-patching match_hostname with a version that supports +# IP-address checking. Not necessary for Python 3.5 and above +if sys.version_info[0] < 3 or sys.version_info[1] < 5: + from .ssl_match_hostname import match_hostname + urllib3.connection.match_hostname = match_hostname class SSLAdapter(HTTPAdapter): '''An HTTPS Transport Adapter that uses an arbitrary SSL version.''' def __init__(self, ssl_version=None, assert_hostname=None, assert_fingerprint=None, **kwargs): - ssl_version = ssl_version or get_max_tls_protocol() self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint @@ -41,11 +39,24 @@ 'assert_hostname': self.assert_hostname, 'assert_fingerprint': self.assert_fingerprint, } - if self.can_override_ssl_version(): + if self.ssl_version and self.can_override_ssl_version(): kwargs['ssl_version'] = self.ssl_version self.poolmanager = PoolManager(**kwargs) + def get_connection(self, *args, **kwargs): + """ + Ensure assert_hostname is set correctly on our pool + + We already take care of a normal poolmanager via init_poolmanager + + But we still need to take care of when there is a proxy poolmanager + """ + conn = super(SSLAdapter, self).get_connection(*args, **kwargs) + if conn.assert_hostname != self.assert_hostname: + conn.assert_hostname = self.assert_hostname + return conn + def can_override_ssl_version(self): urllib_ver = urllib3.__version__.split('-')[0] if urllib_ver is None: diff -Nru python-docker-1.5.0/docker/ssladapter/ssl_match_hostname.py python-docker-1.8.0/docker/ssladapter/ssl_match_hostname.py --- python-docker-1.5.0/docker/ssladapter/ssl_match_hostname.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/docker/ssladapter/ssl_match_hostname.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,130 @@ +# Slightly modified version of match_hostname in python's ssl library +# https://hg.python.org/cpython/file/tip/Lib/ssl.py +# Changed to make code python 2.x compatible (unicode strings for ip_address +# and 3.5-specific var assignment syntax) + +import ipaddress +import re + +try: + from ssl import CertificateError +except ImportError: + CertificateError = ValueError + +import six + + +def _ipaddress_match(ipname, host_ip): + """Exact matching of IP addresses. + + RFC 6125 explicitly doesn't define an algorithm for this + (section 1.7.2 - "Out of Scope"). + """ + # OpenSSL may add a trailing newline to a subjectAltName's IP address + ip = ipaddress.ip_address(six.text_type(ipname.rstrip())) + return ip == host_ip + + +def _dnsname_match(dn, hostname, max_wildcards=1): + """Matching according to RFC 6125, section 6.4.3 + + http://tools.ietf.org/html/rfc6125#section-6.4.3 + """ + pats = [] + if not dn: + return False + + split_dn = dn.split(r'.') + leftmost, remainder = split_dn[0], split_dn[1:] + + wildcards = leftmost.count('*') + if wildcards > max_wildcards: + # Issue #17980: avoid denials of service by refusing more + # than one wildcard per fragment. A survey of established + # policy among SSL implementations showed it to be a + # reasonable choice. + raise CertificateError( + "too many wildcards in certificate DNS name: " + repr(dn)) + + # speed up common case w/o wildcards + if not wildcards: + return dn.lower() == hostname.lower() + + # RFC 6125, section 6.4.3, subitem 1. + # The client SHOULD NOT attempt to match a presented identifier in which + # the wildcard character comprises a label other than the left-most label. + if leftmost == '*': + # When '*' is a fragment by itself, it matches a non-empty dotless + # fragment. + pats.append('[^.]+') + elif leftmost.startswith('xn--') or hostname.startswith('xn--'): + # RFC 6125, section 6.4.3, subitem 3. + # The client SHOULD NOT attempt to match a presented identifier + # where the wildcard character is embedded within an A-label or + # U-label of an internationalized domain name. + pats.append(re.escape(leftmost)) + else: + # Otherwise, '*' matches any dotless string, e.g. www* + pats.append(re.escape(leftmost).replace(r'\*', '[^.]*')) + + # add the remaining fragments, ignore any wildcards + for frag in remainder: + pats.append(re.escape(frag)) + + pat = re.compile(r'\A' + r'\.'.join(pats) + r'\Z', re.IGNORECASE) + return pat.match(hostname) + + +def match_hostname(cert, hostname): + """Verify that *cert* (in decoded format as returned by + SSLSocket.getpeercert()) matches the *hostname*. RFC 2818 and RFC 6125 + rules are followed, but IP addresses are not accepted for *hostname*. + + CertificateError is raised on failure. On success, the function + returns nothing. + """ + if not cert: + raise ValueError("empty or no certificate, match_hostname needs a " + "SSL socket or SSL context with either " + "CERT_OPTIONAL or CERT_REQUIRED") + try: + host_ip = ipaddress.ip_address(six.text_type(hostname)) + except ValueError: + # Not an IP address (common case) + host_ip = None + dnsnames = [] + san = cert.get('subjectAltName', ()) + for key, value in san: + if key == 'DNS': + if host_ip is None and _dnsname_match(value, hostname): + return + dnsnames.append(value) + elif key == 'IP Address': + if host_ip is not None and _ipaddress_match(value, host_ip): + return + dnsnames.append(value) + if not dnsnames: + # The subject is only checked when there is no dNSName entry + # in subjectAltName + for sub in cert.get('subject', ()): + for key, value in sub: + # XXX according to RFC 2818, the most specific Common Name + # must be used. + if key == 'commonName': + if _dnsname_match(value, hostname): + return + dnsnames.append(value) + if len(dnsnames) > 1: + raise CertificateError( + "hostname %r doesn't match either of %s" + % (hostname, ', '.join(map(repr, dnsnames)))) + elif len(dnsnames) == 1: + raise CertificateError( + "hostname %r doesn't match %r" + % (hostname, dnsnames[0]) + ) + else: + raise CertificateError( + "no appropriate commonName or " + "subjectAltName fields were found" + ) diff -Nru python-docker-1.5.0/docker/tls.py python-docker-1.8.0/docker/tls.py --- python-docker-1.5.0/docker/tls.py 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/docker/tls.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,4 +1,5 @@ import os +import ssl from . import errors from .ssladapter import ssladapter @@ -6,6 +7,7 @@ class TLSConfig(object): cert = None + ca_cert = None verify = None ssl_version = None @@ -13,20 +15,19 @@ ssl_version=None, assert_hostname=None, assert_fingerprint=None): # Argument compatibility/mapping with - # http://docs.docker.com/examples/https/ + # https://docs.docker.com/engine/articles/https/ # This diverges from the Docker CLI in that users can specify 'tls' # here, but also disable any public/default CA pool verification by # leaving tls_verify=False - # urllib3 sets a default ssl_version if ssl_version is None, - # but that default is the vulnerable PROTOCOL_SSLv23 selection, - # so we override the default with the maximum supported in the running - # Python interpeter up to TLS 1.2. (see: http://tinyurl.com/kxga8hb) - ssl_version = ssl_version or ssladapter.get_max_tls_protocol() - self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint + # TLS v1.0 seems to be the safest default; SSLv23 fails in mysterious + # ways: https://github.com/docker/docker-py/issues/963 + + self.ssl_version = ssl_version or ssl.PROTOCOL_TLSv1 + # "tls" and "tls_verify" must have both or neither cert/key files # In either case, Alert the user when both are expected, but any are # missing. @@ -48,29 +49,25 @@ ) self.cert = (tls_cert, tls_key) - # Either set verify to True (public/default CA checks) or to the - # path of a CA Cert file. - if verify is not None: - if not ca_cert: - self.verify = verify - elif os.path.isfile(ca_cert): - if not verify: - raise errors.TLSParameterError( - 'verify can not be False when a CA cert is' - ' provided.' - ) - self.verify = ca_cert - else: - raise errors.TLSParameterError( - 'Invalid CA certificate provided for `tls_ca_cert`.' - ) + # If verify is set, make sure the cert exists + self.verify = verify + self.ca_cert = ca_cert + if self.verify and self.ca_cert and not os.path.isfile(self.ca_cert): + raise errors.TLSParameterError( + 'Invalid CA certificate provided for `tls_ca_cert`.' + ) def configure_client(self, client): client.ssl_version = self.ssl_version - if self.verify is not None: + + if self.verify and self.ca_cert: + client.verify = self.ca_cert + else: client.verify = self.verify + if self.cert: client.cert = self.cert + client.mount('https://', ssladapter.SSLAdapter( ssl_version=self.ssl_version, assert_hostname=self.assert_hostname, diff -Nru python-docker-1.5.0/docker/unixconn/unixconn.py python-docker-1.8.0/docker/unixconn/unixconn.py --- python-docker-1.5.0/docker/unixconn/unixconn.py 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/docker/unixconn/unixconn.py 2016-04-11 14:49:54.000000000 +0000 @@ -30,7 +30,9 @@ class UnixHTTPConnection(httplib.HTTPConnection, object): def __init__(self, base_url, unix_socket, timeout=60): - httplib.HTTPConnection.__init__(self, 'localhost', timeout=timeout) + super(UnixHTTPConnection, self).__init__( + 'localhost', timeout=timeout + ) self.base_url = base_url self.unix_socket = unix_socket self.timeout = timeout @@ -44,8 +46,8 @@ class UnixHTTPConnectionPool(urllib3.connectionpool.HTTPConnectionPool): def __init__(self, base_url, socket_path, timeout=60): - urllib3.connectionpool.HTTPConnectionPool.__init__( - self, 'localhost', timeout=timeout + super(UnixHTTPConnectionPool, self).__init__( + 'localhost', timeout=timeout ) self.base_url = base_url self.socket_path = socket_path @@ -73,12 +75,20 @@ if pool: return pool - pool = UnixHTTPConnectionPool(url, - self.socket_path, - self.timeout) + pool = UnixHTTPConnectionPool( + url, self.socket_path, self.timeout + ) self.pools[url] = pool return pool + def request_url(self, request, proxies): + # The select_proxy utility in requests errors out when the provided URL + # doesn't have a hostname, like is the case when using a UNIX socket. + # Since proxies are an irrelevant notion in the case of UNIX sockets + # anyway, we simply return the path URL directly. + # See also: https://github.com/docker/docker-py/issues/811 + return request.path_url + def close(self): self.pools.clear() diff -Nru python-docker-1.5.0/docker/utils/decorators.py python-docker-1.8.0/docker/utils/decorators.py --- python-docker-1.5.0/docker/utils/decorators.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/utils/decorators.py 2016-04-11 14:18:22.000000000 +0000 @@ -35,3 +35,14 @@ return f(self, *args, **kwargs) return wrapper return decorator + + +def update_headers(f): + def inner(self, *args, **kwargs): + if 'HttpHeaders' in self._auth_configs: + if 'headers' not in kwargs: + kwargs['headers'] = self._auth_configs['HttpHeaders'] + else: + kwargs['headers'].update(self._auth_configs['HttpHeaders']) + return f(self, *args, **kwargs) + return inner diff -Nru python-docker-1.5.0/docker/utils/__init__.py python-docker-1.8.0/docker/utils/__init__.py --- python-docker-1.5.0/docker/utils/__init__.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/utils/__init__.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,10 +1,11 @@ from .utils import ( compare_version, convert_port_bindings, convert_volume_binds, mkbuildcontext, tar, exclude_paths, parse_repository_tag, parse_host, - kwargs_from_env, convert_filters, create_host_config, + kwargs_from_env, convert_filters, datetime_to_timestamp, create_host_config, create_container_config, parse_bytes, ping_registry, parse_env_file, - version_lt, version_gte, decode_json_header + version_lt, version_gte, decode_json_header, split_command, + create_ipam_config, create_ipam_pool, parse_devices, normalize_links, ) # flake8: noqa from .types import Ulimit, LogConfig # flake8: noqa -from .decorators import check_resource, minimum_version #flake8: noqa +from .decorators import check_resource, minimum_version, update_headers #flake8: noqa diff -Nru python-docker-1.5.0/docker/utils/utils.py python-docker-1.8.0/docker/utils/utils.py --- python-docker-1.5.0/docker/utils/utils.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/utils/utils.py 2016-04-11 14:18:22.000000000 +0000 @@ -44,6 +44,23 @@ } +def create_ipam_pool(subnet=None, iprange=None, gateway=None, + aux_addresses=None): + return { + 'Subnet': subnet, + 'IPRange': iprange, + 'Gateway': gateway, + 'AuxiliaryAddresses': aux_addresses + } + + +def create_ipam_config(driver='default', pool_configs=None): + return { + 'Driver': driver, + 'Config': pool_configs or [] + } + + def mkbuildcontext(dockerfile): f = tempfile.NamedTemporaryFile() t = tarfile.open(mode='w', fileobj=f) @@ -74,9 +91,10 @@ return json.loads(data) -def tar(path, exclude=None, dockerfile=None): - f = tempfile.NamedTemporaryFile() - t = tarfile.open(mode='w', fileobj=f) +def tar(path, exclude=None, dockerfile=None, fileobj=None, gzip=False): + if not fileobj: + fileobj = tempfile.NamedTemporaryFile() + t = tarfile.open(mode='w:gz' if gzip else 'w', fileobj=fileobj) root = os.path.abspath(path) exclude = exclude or [] @@ -85,8 +103,8 @@ t.add(os.path.join(root, path), arcname=path, recursive=False) t.close() - f.seek(0) - return f + fileobj.seek(0) + return fileobj def exclude_paths(root, patterns, dockerfile=None): @@ -107,38 +125,74 @@ exclude_patterns = list(set(patterns) - set(exceptions)) - all_paths = get_paths(root) + paths = get_paths(root, exclude_patterns, include_patterns, + has_exceptions=len(exceptions) > 0) - # Remove all paths that are matched by any exclusion pattern - paths = [ - p for p in all_paths - if not any(match_path(p, pattern) for pattern in exclude_patterns) - ] - - # Add back the set of paths that are matched by any inclusion pattern. - # Include parent dirs - if we add back 'foo/bar', add 'foo' as well - for p in all_paths: - if any(match_path(p, pattern) for pattern in include_patterns): - components = p.split('/') - paths += [ - '/'.join(components[:end]) - for end in range(1, len(components) + 1) - ] + return set(paths).union( + # If the Dockerfile is in a subdirectory that is excluded, get_paths + # will not descend into it and the file will be skipped. This ensures + # it doesn't happen. + set([dockerfile]) + if os.path.exists(os.path.join(root, dockerfile)) else set() + ) - return set(paths) + +def should_include(path, exclude_patterns, include_patterns): + """ + Given a path, a list of exclude patterns, and a list of inclusion patterns: + + 1. Returns True if the path doesn't match any exclusion pattern + 2. Returns False if the path matches an exclusion pattern and doesn't match + an inclusion pattern + 3. Returns true if the path matches an exclusion pattern and matches an + inclusion pattern + """ + for pattern in exclude_patterns: + if match_path(path, pattern): + for pattern in include_patterns: + if match_path(path, pattern): + return True + return False + return True -def get_paths(root): +def get_paths(root, exclude_patterns, include_patterns, has_exceptions=False): paths = [] - for parent, dirs, files in os.walk(root, followlinks=False): + for parent, dirs, files in os.walk(root, topdown=True, followlinks=False): parent = os.path.relpath(parent, root) if parent == '.': parent = '' + + # If exception rules exist, we can't skip recursing into ignored + # directories, as we need to look for exceptions in them. + # + # It may be possible to optimize this further for exception patterns + # that *couldn't* match within ignored directores. + # + # This matches the current docker logic (as of 2015-11-24): + # https://github.com/docker/docker/blob/37ba67bf636b34dc5c0c0265d62a089d0492088f/pkg/archive/archive.go#L555-L557 + + if not has_exceptions: + + # Remove excluded patterns from the list of directories to traverse + # by mutating the dirs we're iterating over. + # This looks strange, but is considered the correct way to skip + # traversal. See https://docs.python.org/2/library/os.html#os.walk + + dirs[:] = [d for d in dirs if + should_include(os.path.join(parent, d), + exclude_patterns, include_patterns)] + for path in dirs: - paths.append(os.path.join(parent, path)) + if should_include(os.path.join(parent, path), + exclude_patterns, include_patterns): + paths.append(os.path.join(parent, path)) + for path in files: - paths.append(os.path.join(parent, path)) + if should_include(os.path.join(parent, path), + exclude_patterns, include_patterns): + paths.append(os.path.join(parent, path)) return paths @@ -236,7 +290,7 @@ for k, v in six.iteritems(port_bindings): key = str(k) if '/' not in key: - key = key + '/tcp' + key += '/tcp' if isinstance(v, list): result[key] = [_convert_port_binding(binding) for binding in v] else: @@ -283,23 +337,50 @@ return result -def parse_repository_tag(repo): - column_index = repo.rfind(':') - if column_index < 0: - return repo, None - tag = repo[column_index + 1:] - slash_index = tag.find('/') - if slash_index < 0: - return repo[:column_index], tag +def convert_tmpfs_mounts(tmpfs): + if isinstance(tmpfs, dict): + return tmpfs + + if not isinstance(tmpfs, list): + raise ValueError( + 'Expected tmpfs value to be either a list or a dict, found: {}' + .format(type(tmpfs).__name__) + ) + + result = {} + for mount in tmpfs: + if isinstance(mount, six.string_types): + if ":" in mount: + name, options = mount.split(":", 1) + else: + name = mount + options = "" + + else: + raise ValueError( + "Expected item in tmpfs list to be a string, found: {}" + .format(type(mount).__name__) + ) + + result[name] = options + return result + - return repo, None +def parse_repository_tag(repo_name): + parts = repo_name.rsplit('@', 1) + if len(parts) == 2: + return tuple(parts) + parts = repo_name.rsplit(':', 1) + if len(parts) == 2 and '/' not in parts[1]: + return tuple(parts) + return repo_name, None # Based on utils.go:ParseHost http://tinyurl.com/nkahcfh # fd:// protocol unsupported (for obvious reasons) # Added support for http and https # Protocol translation: tcp -> http, unix -> http+unix -def parse_host(addr, platform=None): +def parse_host(addr, platform=None, tls=False): proto = "http+unix" host = DEFAULT_HTTP_HOST port = None @@ -319,11 +400,12 @@ if addr == 'tcp://': raise errors.DockerException( - "Invalid bind address format: {0}".format(addr)) + "Invalid bind address format: {0}".format(addr) + ) elif addr.startswith('unix://'): addr = addr[7:] elif addr.startswith('tcp://'): - proto = "http" + proto = 'http{0}'.format('s' if tls else '') addr = addr[6:] elif addr.startswith('https://'): proto = "https" @@ -335,7 +417,7 @@ raise errors.DockerException( "Invalid bind address protocol: {0}".format(addr) ) - proto = "http" + proto = "https" if tls else "http" if proto != "http+unix" and ":" in addr: host_parts = addr.split(':') @@ -354,7 +436,7 @@ port = int(port) except Exception: raise errors.DockerException( - "Invalid port: %s", addr + "Invalid port: {0}".format(addr) ) elif proto in ("http", "https") and ':' not in addr: @@ -371,7 +453,14 @@ def parse_devices(devices): device_list = [] for device in devices: - device_mapping = device.split(":") + if isinstance(device, dict): + device_list.append(device) + continue + if not isinstance(device, six.string_types): + raise errors.DockerException( + 'Invalid device type {0}'.format(type(device)) + ) + device_mapping = device.split(':') if device_mapping: path_on_host = device_mapping[0] if len(device_mapping) > 1: @@ -382,34 +471,57 @@ permissions = device_mapping[2] else: permissions = 'rwm' - device_list.append({"PathOnHost": path_on_host, - "PathInContainer": path_in_container, - "CgroupPermissions": permissions}) + 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') +def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None): + if not environment: + environment = os.environ + host = environment.get('DOCKER_HOST') + + # empty string for cert path is the same as unset. + cert_path = environment.get('DOCKER_CERT_PATH') or None + + # empty string for tls verify counts as "false". + # Any value or 'unset' counts as true. + tls_verify = environment.get('DOCKER_TLS_VERIFY') + if tls_verify == '': + tls_verify = False + else: + tls_verify = tls_verify is not None + enable_tls = cert_path or tls_verify params = {} if host: - params['base_url'] = (host.replace('tcp://', 'https://') - if tls_verify else host) + params['base_url'] = ( + host.replace('tcp://', 'https://') if enable_tls else host + ) + + if not enable_tls: + return params - if tls_verify and not cert_path: + if not cert_path: cert_path = os.path.join(os.path.expanduser('~'), '.docker') - 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) + if not tls_verify and assert_hostname is None: + # assert_hostname is a subset of TLS verification, + # so if it's not set already then set it to false. + assert_hostname = False + + 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=tls_verify, + ssl_version=ssl_version, + assert_hostname=assert_hostname, + ) return params @@ -431,54 +543,81 @@ return delta.seconds + delta.days * 24 * 3600 +def longint(n): + if six.PY3: + return int(n) + return long(n) + + def parse_bytes(s): + if isinstance(s, six.integer_types + (float,)): + return s if len(s) == 0: - s = 0 + return 0 + + if s[-2:-1].isalpha() and s[-1].isalpha(): + if s[-1] == "b" or s[-1] == "B": + s = s[:-1] + units = BYTE_UNITS + suffix = s[-1].lower() + + # Check if the variable is a string representation of an int + # without a units part. Assuming that the units are bytes. + if suffix.isdigit(): + digits_part = s + suffix = 'b' else: - if s[-2:-1].isalpha() and s[-1].isalpha(): - if (s[-1] == "b" or s[-1] == "B"): - s = s[:-1] - units = BYTE_UNITS - suffix = s[-1].lower() - - # Check if the variable is a string representation of an int - # without a units part. Assuming that the units are bytes. - if suffix.isdigit(): - digits_part = s - suffix = 'b' - else: - digits_part = s[:-1] + digits_part = s[:-1] - if suffix in units.keys() or suffix.isdigit(): - try: - digits = int(digits_part) - except ValueError: - message = ('Failed converting the string value for' - 'memory ({0}) to a number.') - formatted_message = message.format(digits_part) - raise errors.DockerException(formatted_message) + if suffix in units.keys() or suffix.isdigit(): + try: + digits = longint(digits_part) + except ValueError: + raise errors.DockerException( + 'Failed converting the string value for memory ({0}) to' + ' an integer.'.format(digits_part) + ) - s = digits * units[suffix] - else: - message = ('The specified value for memory' - ' ({0}) should specify the units. The postfix' - ' should be one of the `b` `k` `m` `g`' - ' characters') - raise errors.DockerException(message.format(s)) + # Reconvert to long for the final result + s = longint(digits * units[suffix]) + else: + raise errors.DockerException( + 'The specified value for memory ({0}) should specify the' + ' units. The postfix should be one of the `b` `k` `m` `g`' + ' characters'.format(s) + ) return s -def create_host_config( - 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, devices=None, - extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None, - security_opt=None, ulimits=None, log_config=None, mem_limit=None, - memswap_limit=None, cgroup_parent=None, group_add=None, cpu_quota=None, - cpu_period=None, version=None -): +def host_config_type_error(param, param_value, expected): + error_msg = 'Invalid type for {0} param: expected {1} but found {2}' + return TypeError(error_msg.format(param, expected, type(param_value))) + + +def host_config_version_error(param, version, less_than=True): + operator = '<' if less_than else '>' + error_msg = '{0} param is not supported in API versions {1} {2}' + return errors.InvalidVersion(error_msg.format(param, operator, version)) + + +def host_config_value_error(param, param_value): + error_msg = 'Invalid value for {0} param: {1}' + return ValueError(error_msg.format(param, param_value)) + + +def create_host_config(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, devices=None, extra_hosts=None, + read_only=None, pid_mode=None, ipc_mode=None, + security_opt=None, ulimits=None, log_config=None, + mem_limit=None, memswap_limit=None, mem_swappiness=None, + cgroup_parent=None, group_add=None, cpu_quota=None, + cpu_period=None, oom_kill_disable=False, shm_size=None, + version=None, tmpfs=None, oom_score_adj=None): + host_config = {} if not version: @@ -489,19 +628,29 @@ version = constants.DEFAULT_DOCKER_API_VERSION if mem_limit is not None: - if isinstance(mem_limit, six.string_types): - mem_limit = parse_bytes(mem_limit) - host_config['Memory'] = mem_limit + host_config['Memory'] = parse_bytes(mem_limit) if memswap_limit is not None: - if isinstance(memswap_limit, six.string_types): - memswap_limit = parse_bytes(memswap_limit) - host_config['MemorySwap'] = memswap_limit + host_config['MemorySwap'] = parse_bytes(memswap_limit) + + if mem_swappiness is not None: + if version_lt(version, '1.20'): + raise host_config_version_error('mem_swappiness', '1.20') + if not isinstance(mem_swappiness, int): + raise host_config_type_error( + 'mem_swappiness', mem_swappiness, 'int' + ) + + host_config['MemorySwappiness'] = mem_swappiness + + if shm_size is not None: + if isinstance(shm_size, six.string_types): + shm_size = parse_bytes(shm_size) + + host_config['ShmSize'] = shm_size if pid_mode not in (None, 'host'): - raise errors.DockerException( - 'Invalid value for pid param: {0}'.format(pid_mode) - ) + raise host_config_value_error('pid_mode', pid_mode) elif pid_mode: host_config['PidMode'] = pid_mode @@ -511,6 +660,21 @@ if privileged: host_config['Privileged'] = privileged + if oom_kill_disable: + if version_lt(version, '1.20'): + raise host_config_version_error('oom_kill_disable', '1.19') + + host_config['OomKillDisable'] = oom_kill_disable + + if oom_score_adj: + if version_lt(version, '1.22'): + raise host_config_version_error('oom_score_adj', '1.22') + if not isinstance(oom_score_adj, int): + raise host_config_type_error( + 'oom_score_adj', oom_score_adj, 'int' + ) + host_config['OomScoreAdj'] = oom_score_adj + if publish_all_ports: host_config['PublishAllPorts'] = publish_all_ports @@ -526,6 +690,11 @@ host_config['NetworkMode'] = 'default' if restart_policy: + if not isinstance(restart_policy, dict): + raise host_config_type_error( + 'restart_policy', restart_policy, 'dict' + ) + host_config['RestartPolicy'] = restart_policy if cap_add: @@ -539,9 +708,8 @@ if group_add: if version_lt(version, '1.20'): - raise errors.InvalidVersion( - 'group_add param not supported for API version < 1.20' - ) + raise host_config_version_error('group_add', '1.20') + host_config['GroupAdd'] = [six.text_type(grp) for grp in group_add] if dns is not None: @@ -549,24 +717,21 @@ if security_opt is not None: if not isinstance(security_opt, list): - raise errors.DockerException( - 'Invalid type for security_opt param: expected list but found' - ' {0}'.format(type(security_opt)) - ) + raise host_config_type_error('security_opt', security_opt, 'list') + host_config['SecurityOpt'] = security_opt if volumes_from is not None: if isinstance(volumes_from, six.string_types): volumes_from = volumes_from.split(',') + host_config['VolumesFrom'] = volumes_from if binds is not None: host_config['Binds'] = convert_volume_binds(binds) if port_bindings is not None: - host_config['PortBindings'] = convert_port_bindings( - port_bindings - ) + host_config['PortBindings'] = convert_port_bindings(port_bindings) if extra_hosts is not None: if isinstance(extra_hosts, dict): @@ -578,14 +743,7 @@ host_config['ExtraHosts'] = extra_hosts if links is not None: - if isinstance(links, dict): - links = six.iteritems(links) - - formatted_links = [ - '{0}:{1}'.format(k, v) for k, v in sorted(links) - ] - - host_config['Links'] = formatted_links + host_config['Links'] = normalize_links(links) if isinstance(lxc_conf, dict): formatted = [] @@ -601,10 +759,7 @@ if ulimits is not None: if not isinstance(ulimits, list): - raise errors.DockerException( - 'Invalid type for ulimits param: expected list but found' - ' {0}'.format(type(ulimits)) - ) + raise host_config_type_error('ulimits', ulimits, 'list') host_config['Ulimits'] = [] for l in ulimits: if not isinstance(l, Ulimit): @@ -614,40 +769,69 @@ if log_config is not None: if not isinstance(log_config, LogConfig): if not isinstance(log_config, dict): - raise errors.DockerException( - 'Invalid type for log_config param: expected LogConfig but' - ' found {0}'.format(type(log_config)) + raise host_config_type_error( + 'log_config', log_config, 'LogConfig' ) log_config = LogConfig(**log_config) + host_config['LogConfig'] = log_config if cpu_quota: if not isinstance(cpu_quota, int): - raise TypeError( - 'Invalid type for cpu_quota param: expected int but' - ' found {0}'.format(type(cpu_quota)) - ) + raise host_config_type_error('cpu_quota', cpu_quota, 'int') if version_lt(version, '1.19'): - raise errors.InvalidVersion( - 'cpu_quota param not supported for API version < 1.19' - ) + raise host_config_version_error('cpu_quota', '1.19') + host_config['CpuQuota'] = cpu_quota if cpu_period: if not isinstance(cpu_period, int): - raise TypeError( - 'Invalid type for cpu_period param: expected int but' - ' found {0}'.format(type(cpu_period)) - ) + raise host_config_type_error('cpu_period', cpu_period, 'int') if version_lt(version, '1.19'): - raise errors.InvalidVersion( - 'cpu_period param not supported for API version < 1.19' - ) + raise host_config_version_error('cpu_period', '1.19') + host_config['CpuPeriod'] = cpu_period + if tmpfs: + if version_lt(version, '1.22'): + raise host_config_version_error('tmpfs', '1.22') + host_config["Tmpfs"] = convert_tmpfs_mounts(tmpfs) + return host_config +def normalize_links(links): + if isinstance(links, dict): + links = six.iteritems(links) + + return ['{0}:{1}'.format(k, v) for k, v in sorted(links)] + + +def create_networking_config(endpoints_config=None): + networking_config = {} + + if endpoints_config: + networking_config["EndpointsConfig"] = endpoints_config + + return networking_config + + +def create_endpoint_config(version, aliases=None, links=None): + endpoint_config = {} + + if aliases: + if version_lt(version, '1.22'): + raise host_config_version_error('endpoint_config.aliases', '1.22') + endpoint_config["Aliases"] = aliases + + if links: + if version_lt(version, '1.22'): + raise host_config_version_error('endpoint_config.links', '1.22') + endpoint_config["Links"] = normalize_links(links) + + return endpoint_config + + def parse_env_file(env_file): """ Reads a line-separated environment file. @@ -673,31 +857,47 @@ return environment +def split_command(command): + if six.PY2 and not isinstance(command, six.binary_type): + command = command.encode('utf-8') + return shlex.split(command) + + +def format_environment(environment): + def format_env(key, value): + if value is None: + return key + return '{key}={value}'.format(key=key, value=value) + return [format_env(*var) for var in six.iteritems(environment)] + + def create_container_config( version, image, command, hostname=None, user=None, detach=False, stdin_open=False, tty=False, mem_limit=None, 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=None, cpuset=None, host_config=None, mac_address=None, - labels=None, volume_driver=None + labels=None, volume_driver=None, stop_signal=None, networking_config=None, ): if isinstance(command, six.string_types): - command = shlex.split(str(command)) + command = split_command(command) if isinstance(entrypoint, six.string_types): - entrypoint = shlex.split(str(entrypoint)) + entrypoint = split_command(entrypoint) if isinstance(environment, dict): - environment = [ - six.text_type('{0}={1}').format(k, v) - for k, v in six.iteritems(environment) - ] + environment = format_environment(environment) if labels is not None and compare_version('1.18', version) < 0: raise errors.InvalidVersion( 'labels were only introduced in API version 1.18' ) + if stop_signal is not None and compare_version('1.21', version) < 0: + raise errors.InvalidVersion( + 'stop_signal was only introduced in API version 1.21' + ) + if compare_version('1.19', version) < 0: if volume_driver is not None: raise errors.InvalidVersion( @@ -720,9 +920,9 @@ if isinstance(labels, list): labels = dict((lbl, six.text_type('')) for lbl in labels) - if isinstance(mem_limit, six.string_types): + if mem_limit is not None: mem_limit = parse_bytes(mem_limit) - if isinstance(memswap_limit, six.string_types): + if memswap_limit is not None: memswap_limit = parse_bytes(memswap_limit) if isinstance(ports, list): @@ -768,7 +968,7 @@ if compare_version('1.10', version) >= 0: message = ('{0!r} parameter has no effect on create_container().' - ' It has been moved to start()') + ' It has been moved to host_config') if dns is not None: raise errors.InvalidVersion(message.format('dns')) if volumes_from is not None: @@ -800,7 +1000,9 @@ 'WorkingDir': working_dir, 'MemorySwap': memswap_limit, 'HostConfig': host_config, + 'NetworkingConfig': networking_config, 'MacAddress': mac_address, 'Labels': labels, 'VolumeDriver': volume_driver, + 'StopSignal': stop_signal } diff -Nru python-docker-1.5.0/docker/version.py python-docker-1.8.0/docker/version.py --- python-docker-1.5.0/docker/version.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/docker/version.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,2 +1,2 @@ -version = "1.5.0" +version = "1.8.0" version_info = tuple([int(d) for d in version.split("-")[0].split(".")]) diff -Nru python-docker-1.5.0/.dockerignore python-docker-1.8.0/.dockerignore --- python-docker-1.5.0/.dockerignore 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/.dockerignore 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,17 @@ +.git/ + +build +dist +*.egg-info +*.egg/ +*.pyc +*.swp + +.tox +.coverage +html/* +__pycache__ + +# Compiled Documentation +site/ +Makefile diff -Nru python-docker-1.5.0/docker_py.egg-info/pbr.json python-docker-1.8.0/docker_py.egg-info/pbr.json --- python-docker-1.5.0/docker_py.egg-info/pbr.json 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/docker_py.egg-info/pbr.json 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -{"is_release": false, "git_version": "36f3480"} \ No newline at end of file diff -Nru python-docker-1.5.0/docker_py.egg-info/PKG-INFO python-docker-1.8.0/docker_py.egg-info/PKG-INFO --- python-docker-1.5.0/docker_py.egg-info/PKG-INFO 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/docker_py.egg-info/PKG-INFO 2016-04-11 14:36:29.000000000 +0000 @@ -1,6 +1,6 @@ Metadata-Version: 1.1 Name: docker-py -Version: 1.5.0 +Version: 1.8.0 Summary: Python client for Docker. Home-page: https://github.com/docker/docker-py/ Author: UNKNOWN @@ -15,7 +15,6 @@ Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Topic :: Utilities diff -Nru python-docker-1.5.0/docker_py.egg-info/requires.txt python-docker-1.8.0/docker_py.egg-info/requires.txt --- python-docker-1.5.0/docker_py.egg-info/requires.txt 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/docker_py.egg-info/requires.txt 2016-04-11 15:02:27.000000000 +0000 @@ -1,3 +1,6 @@ requests >= 2.5.2 six >= 1.4.0 websocket-client >= 0.32.0 + +[:python_version < "3"] +py2-ipaddress >= 3.4.1 diff -Nru python-docker-1.5.0/docker_py.egg-info/SOURCES.txt python-docker-1.8.0/docker_py.egg-info/SOURCES.txt --- python-docker-1.5.0/docker_py.egg-info/SOURCES.txt 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/docker_py.egg-info/SOURCES.txt 2016-04-11 14:36:29.000000000 +0000 @@ -2,6 +2,7 @@ MANIFEST.in README.md requirements.txt +setup.cfg setup.py test-requirements.txt docker/__init__.py @@ -21,6 +22,7 @@ docker/auth/__init__.py docker/auth/auth.py docker/ssladapter/__init__.py +docker/ssladapter/ssl_match_hostname.py docker/ssladapter/ssladapter.py docker/unixconn/__init__.py docker/unixconn/unixconn.py @@ -34,14 +36,35 @@ docker_py.egg-info/SOURCES.txt docker_py.egg-info/dependency_links.txt docker_py.egg-info/not-zip-safe -docker_py.egg-info/pbr.json docker_py.egg-info/requires.txt docker_py.egg-info/top_level.txt tests/__init__.py tests/base.py -tests/fake_api.py -tests/fake_stat.py tests/helpers.py -tests/integration_test.py -tests/test.py -tests/utils_test.py \ No newline at end of file +tests/integration/__init__.py +tests/integration/api_test.py +tests/integration/build_test.py +tests/integration/conftest.py +tests/integration/container_test.py +tests/integration/exec_test.py +tests/integration/image_test.py +tests/integration/network_test.py +tests/integration/regression_test.py +tests/integration/volume_test.py +tests/unit/__init__.py +tests/unit/api_test.py +tests/unit/auth_test.py +tests/unit/build_test.py +tests/unit/client_test.py +tests/unit/container_test.py +tests/unit/exec_test.py +tests/unit/fake_api.py +tests/unit/fake_stat.py +tests/unit/image_test.py +tests/unit/network_test.py +tests/unit/ssladapter_test.py +tests/unit/utils_test.py +tests/unit/volume_test.py +tests/unit/testdata/certs/ca.pem +tests/unit/testdata/certs/cert.pem +tests/unit/testdata/certs/key.pem \ No newline at end of file diff -Nru python-docker-1.5.0/.editorconfig python-docker-1.8.0/.editorconfig --- python-docker-1.5.0/.editorconfig 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/.editorconfig 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,11 @@ +root = true + +[*] +indent_style = space +indent_size = 4 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 80 + +[*.md] +trim_trailing_whitespace = false diff -Nru python-docker-1.5.0/.gitignore python-docker-1.8.0/.gitignore --- python-docker-1.5.0/.gitignore 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/.gitignore 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,18 @@ +build +dist +*.egg-info +*.egg/ +*.pyc +*.swp + +.tox +.coverage +html/* + +# Compiled Documentation +site/ +README.rst + +env/ +venv/ +.idea/ diff -Nru python-docker-1.5.0/MANIFEST.in python-docker-1.8.0/MANIFEST.in --- python-docker-1.5.0/MANIFEST.in 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/MANIFEST.in 2016-04-11 14:18:22.000000000 +0000 @@ -1,5 +1,7 @@ include test-requirements.txt include requirements.txt include README.md +include README.rst include LICENSE recursive-include tests *.py +recursive-include tests/unit/testdata * diff -Nru python-docker-1.5.0/PKG-INFO python-docker-1.8.0/PKG-INFO --- python-docker-1.5.0/PKG-INFO 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/PKG-INFO 1970-01-01 00:00:00.000000000 +0000 @@ -1,22 +0,0 @@ -Metadata-Version: 1.1 -Name: docker-py -Version: 1.5.0 -Summary: Python client for Docker. -Home-page: https://github.com/docker/docker-py/ -Author: UNKNOWN -Author-email: UNKNOWN -License: UNKNOWN -Description: UNKNOWN -Platform: UNKNOWN -Classifier: Development Status :: 4 - Beta -Classifier: Environment :: Other Environment -Classifier: Intended Audience :: Developers -Classifier: Operating System :: OS Independent -Classifier: Programming Language :: Python -Classifier: Programming Language :: Python :: 2.6 -Classifier: Programming Language :: Python :: 2.7 -Classifier: Programming Language :: Python :: 3.2 -Classifier: Programming Language :: Python :: 3.3 -Classifier: Programming Language :: Python :: 3.4 -Classifier: Topic :: Utilities -Classifier: License :: OSI Approved :: Apache Software License diff -Nru python-docker-1.5.0/README.md python-docker-1.8.0/README.md --- python-docker-1.5.0/README.md 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/README.md 2016-04-11 14:18:22.000000000 +0000 @@ -3,12 +3,12 @@ [![Build Status](https://travis-ci.org/docker/docker-py.png)](https://travis-ci.org/docker/docker-py) -An API client for docker written in Python +A Python library for the Docker Remote API. It does everything the `docker` command does, but from within Python – run containers, manage them, pull/push images, etc. Installation ------------ -Our latest stable is always available on PyPi. +The latest stable version is always available on PyPi. pip install docker-py @@ -17,8 +17,8 @@ [![Documentation Status](https://readthedocs.org/projects/docker-py/badge/?version=latest)](https://readthedocs.org/projects/docker-py/?badge=latest) -Full documentation is hosted on [ReadTheDocs](http://docker-py.readthedocs.org/en/latest/). -Sources are available in the `docs/` directory. +[Read the full documentation here](http://docker-py.readthedocs.org/en/latest/). +The source is available in the `docs/` directory. License diff -Nru python-docker-1.5.0/requirements.txt python-docker-1.8.0/requirements.txt --- python-docker-1.5.0/requirements.txt 2015-09-10 22:08:54.000000000 +0000 +++ python-docker-1.8.0/requirements.txt 2016-04-11 15:02:27.000000000 +0000 @@ -1,3 +1,4 @@ requests==2.5.3 six>=1.4.0 websocket-client==0.32.0 +py2-ipaddress==3.4.1 ; python_version < '3.2' \ No newline at end of file diff -Nru python-docker-1.5.0/scripts/release.sh python-docker-1.8.0/scripts/release.sh --- python-docker-1.5.0/scripts/release.sh 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/scripts/release.sh 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Create the official release +# + +if [ -z "$(command -v pandoc 2> /dev/null)" ]; then + >&2 echo "$0 requires http://pandoc.org/" + >&2 echo "Please install it and make sure it is available on your \$PATH." + exit 2 +fi + +VERSION=$1 +REPO=docker/docker-py +GITHUB_REPO=git@github.com:$REPO + +if [ -z $VERSION ]; then + echo "Usage: $0 VERSION [upload]" + exit 1 +fi + +echo "##> Tagging the release as $VERSION" +git tag $VERSION || exit 1 +if [[ $2 == 'upload' ]]; then + echo "##> Pushing tag to github" + git push $GITHUB_REPO $VERSION || exit 1 +fi + + +pandoc -f markdown -t rst README.md -o README.rst || exit 1 +if [[ $2 == 'upload' ]]; then + echo "##> Uploading sdist to pypi" + python setup.py sdist bdist_wheel upload +fi diff -Nru python-docker-1.5.0/setup.cfg python-docker-1.8.0/setup.cfg --- python-docker-1.5.0/setup.cfg 2015-10-12 20:52:04.000000000 +0000 +++ python-docker-1.8.0/setup.cfg 2016-04-11 14:18:22.000000000 +0000 @@ -1,5 +1,3 @@ -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 +[bdist_wheel] +universal = 1 diff -Nru python-docker-1.5.0/setup.py python-docker-1.8.0/setup.py --- python-docker-1.5.0/setup.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/setup.py 2016-04-11 15:02:27.000000000 +0000 @@ -12,6 +12,10 @@ 'websocket-client >= 0.32.0', ] +extras_require = { + ':python_version < "3"': 'py2-ipaddress >= 3.4.1', +} + exec(open('docker/version.py').read()) with open('./test-requirements.txt') as test_reqs_txt: @@ -29,6 +33,7 @@ ], install_requires=requirements, tests_require=test_requirements, + extras_require=extras_require, zip_safe=False, test_suite='tests', classifiers=[ @@ -39,7 +44,6 @@ 'Programming Language :: Python', 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Topic :: Utilities', diff -Nru python-docker-1.5.0/tests/base.py python-docker-1.8.0/tests/base.py --- python-docker-1.5.0/tests/base.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/base.py 2016-04-11 14:18:22.000000000 +0000 @@ -21,3 +21,28 @@ ), reason="API version is too low (< {0})".format(version) ) + + +class Cleanup(object): + if sys.version_info < (2, 7): + # Provide a basic implementation of addCleanup for Python < 2.7 + def __init__(self, *args, **kwargs): + super(Cleanup, self).__init__(*args, **kwargs) + self._cleanups = [] + + def tearDown(self): + super(Cleanup, self).tearDown() + ok = True + while self._cleanups: + fn, args, kwargs = self._cleanups.pop(-1) + try: + fn(*args, **kwargs) + except KeyboardInterrupt: + raise + except: + ok = False + if not ok: + raise + + def addCleanup(self, function, *args, **kwargs): + self._cleanups.append((function, args, kwargs)) diff -Nru python-docker-1.5.0/tests/Dockerfile-dind-certs python-docker-1.8.0/tests/Dockerfile-dind-certs --- python-docker-1.5.0/tests/Dockerfile-dind-certs 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/Dockerfile-dind-certs 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,20 @@ +FROM python:2.7 +RUN mkdir /tmp/certs +VOLUME /certs + +WORKDIR /tmp/certs +RUN openssl genrsa -aes256 -passout pass:foobar -out ca-key.pem 4096 +RUN echo "[req]\nprompt=no\ndistinguished_name = req_distinguished_name\n[req_distinguished_name]\ncountryName=AU" > /tmp/config +RUN openssl req -new -x509 -passin pass:foobar -config /tmp/config -days 365 -key ca-key.pem -sha256 -out ca.pem +RUN openssl genrsa -out server-key.pem -passout pass:foobar 4096 +RUN openssl req -subj "/CN=docker" -sha256 -new -key server-key.pem -out server.csr +RUN echo subjectAltName = DNS:docker,DNS:localhost > extfile.cnf +RUN openssl x509 -req -days 365 -passin pass:foobar -sha256 -in server.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out server-cert.pem -extfile extfile.cnf +RUN openssl genrsa -out key.pem 4096 +RUN openssl req -passin pass:foobar -subj '/CN=client' -new -key key.pem -out client.csr +RUN echo extendedKeyUsage = clientAuth > extfile.cnf +RUN openssl x509 -req -passin pass:foobar -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out cert.pem -extfile extfile.cnf +RUN chmod -v 0400 ca-key.pem key.pem server-key.pem +RUN chmod -v 0444 ca.pem server-cert.pem cert.pem + +CMD cp -R /tmp/certs/* /certs && while true; do sleep 1; done diff -Nru python-docker-1.5.0/tests/fake_api.py python-docker-1.8.0/tests/fake_api.py --- python-docker-1.5.0/tests/fake_api.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/fake_api.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,542 +0,0 @@ -# Copyright 2013 dotCloud inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from . import fake_stat -from docker import constants - -CURRENT_VERSION = 'v{0}'.format(constants.DEFAULT_DOCKER_API_VERSION) - -FAKE_CONTAINER_ID = '3cc2351ab11b' -FAKE_IMAGE_ID = 'e9aa60c60128' -FAKE_EXEC_ID = 'd5d177f121dc' -FAKE_IMAGE_NAME = 'test_image' -FAKE_TARBALL_PATH = '/path/to/tarball' -FAKE_REPO_NAME = 'repo' -FAKE_TAG_NAME = 'tag' -FAKE_FILE_NAME = 'file' -FAKE_URL = 'myurl' -FAKE_PATH = '/path' -FAKE_VOLUME_NAME = 'perfectcherryblossom' - -# Each method is prefixed with HTTP method (get, post...) -# for clarity and readability - - -def get_fake_raw_version(): - status_code = 200 - response = { - "ApiVersion": "1.18", - "GitCommit": "fake-commit", - "GoVersion": "go1.3.3", - "Version": "1.5.0" - } - return status_code, response - - -def get_fake_version(): - status_code = 200 - response = {'GoVersion': '1', 'Version': '1.1.1', - 'GitCommit': 'deadbeef+CHANGES'} - return status_code, response - - -def get_fake_info(): - status_code = 200 - response = {'Containers': 1, 'Images': 1, 'Debug': False, - 'MemoryLimit': False, 'SwapLimit': False, - 'IPv4Forwarding': True} - return status_code, response - - -def get_fake_search(): - status_code = 200 - response = [{'Name': 'busybox', 'Description': 'Fake Description'}] - return status_code, response - - -def get_fake_images(): - status_code = 200 - response = [{ - 'Id': FAKE_IMAGE_ID, - 'Created': '2 days ago', - 'Repository': 'busybox', - 'RepoTags': ['busybox:latest', 'busybox:1.0'], - }] - return status_code, response - - -def get_fake_image_history(): - status_code = 200 - response = [ - { - "Id": "b750fe79269d", - "Created": 1364102658, - "CreatedBy": "/bin/bash" - }, - { - "Id": "27cf78414709", - "Created": 1364068391, - "CreatedBy": "" - } - ] - - return status_code, response - - -def post_fake_import_image(): - status_code = 200 - response = 'Import messages...' - - return status_code, response - - -def get_fake_containers(): - status_code = 200 - response = [{ - 'Id': FAKE_CONTAINER_ID, - 'Image': 'busybox:latest', - 'Created': '2 days ago', - 'Command': 'true', - 'Status': 'fake status' - }] - return status_code, response - - -def post_fake_start_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_resize_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_create_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def get_fake_inspect_container(tty=False): - status_code = 200 - response = { - 'Id': FAKE_CONTAINER_ID, - 'Config': {'Privileged': True, 'Tty': tty}, - 'ID': FAKE_CONTAINER_ID, - 'Image': 'busybox:latest', - "State": { - "Running": True, - "Pid": 0, - "ExitCode": 0, - "StartedAt": "2013-09-25T14:01:18.869545111+02:00", - "Ghost": False - }, - "MacAddress": "02:42:ac:11:00:0a" - } - return status_code, response - - -def get_fake_inspect_image(): - status_code = 200 - response = { - 'id': FAKE_IMAGE_ID, - 'parent': "27cf784147099545", - 'created': "2013-03-23T22:24:18.818426-07:00", - 'container': FAKE_CONTAINER_ID, - 'container_config': - { - "Hostname": "", - "User": "", - "Memory": 0, - "MemorySwap": 0, - "AttachStdin": False, - "AttachStdout": False, - "AttachStderr": False, - "PortSpecs": "", - "Tty": True, - "OpenStdin": True, - "StdinOnce": False, - "Env": "", - "Cmd": ["/bin/bash"], - "Dns": "", - "Image": "base", - "Volumes": "", - "VolumesFrom": "", - "WorkingDir": "" - }, - 'Size': 6823592 - } - return status_code, response - - -def get_fake_port(): - status_code = 200 - response = { - 'HostConfig': { - 'Binds': None, - 'ContainerIDFile': '', - 'Links': None, - 'LxcConf': None, - 'PortBindings': { - '1111': None, - '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], - '2222': None - }, - 'Privileged': False, - 'PublishAllPorts': False - }, - 'NetworkSettings': { - 'Bridge': 'docker0', - 'PortMapping': None, - 'Ports': { - '1111': None, - '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], - '2222': None}, - 'MacAddress': '02:42:ac:11:00:0a' - } - } - return status_code, response - - -def get_fake_insert_image(): - status_code = 200 - response = {'StatusCode': 0} - return status_code, response - - -def get_fake_wait(): - status_code = 200 - response = {'StatusCode': 0} - return status_code, response - - -def get_fake_logs(): - status_code = 200 - response = (b'\x01\x00\x00\x00\x00\x00\x00\x11Flowering Nights\n' - b'\x01\x00\x00\x00\x00\x00\x00\x10(Sakuya Iyazoi)\n') - return status_code, response - - -def get_fake_diff(): - status_code = 200 - response = [{'Path': '/test', 'Kind': 1}] - return status_code, response - - -def get_fake_events(): - status_code = 200 - response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID, - 'from': FAKE_IMAGE_ID, 'time': 1423247867}] - return status_code, response - - -def get_fake_export(): - status_code = 200 - response = 'Byte Stream....' - return status_code, response - - -def post_fake_exec_create(): - status_code = 200 - response = {'Id': FAKE_EXEC_ID} - return status_code, response - - -def post_fake_exec_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_exec_resize(): - status_code = 201 - return status_code, '' - - -def get_fake_exec_inspect(): - return 200, { - 'OpenStderr': True, - 'OpenStdout': True, - 'Container': get_fake_inspect_container()[1], - 'Running': False, - 'ProcessConfig': { - 'arguments': ['hello world'], - 'tty': False, - 'entrypoint': 'echo', - 'privileged': False, - 'user': '' - }, - 'ExitCode': 0, - 'ID': FAKE_EXEC_ID, - 'OpenStdin': False - } - - -def post_fake_stop_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_kill_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - 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} - return status_code, response - - -def post_fake_rename_container(): - status_code = 204 - return status_code, None - - -def delete_fake_remove_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_image_create(): - status_code = 200 - response = {'Id': FAKE_IMAGE_ID} - return status_code, response - - -def delete_fake_remove_image(): - status_code = 200 - response = {'Id': FAKE_IMAGE_ID} - return status_code, response - - -def get_fake_get_image(): - status_code = 200 - response = 'Byte Stream....' - return status_code, response - - -def post_fake_load_image(): - status_code = 200 - response = {'Id': FAKE_IMAGE_ID} - return status_code, response - - -def post_fake_commit(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_push(): - status_code = 200 - response = {'Id': FAKE_IMAGE_ID} - return status_code, response - - -def post_fake_build_container(): - status_code = 200 - response = {'Id': FAKE_CONTAINER_ID} - return status_code, response - - -def post_fake_tag_image(): - status_code = 200 - response = {'Id': FAKE_IMAGE_ID} - return status_code, response - - -def get_fake_stats(): - status_code = 200 - response = fake_stat.OBJ - return status_code, response - - -def get_fake_top(): - return 200, { - 'Processes': [ - [ - 'root', - '26501', - '6907', - '0', - '10:32', - 'pts/55', - '00:00:00', - 'sleep 60', - ], - ], - 'Titles': [ - 'UID', - 'PID', - 'PPID', - 'C', - 'STIME', - 'TTY', - 'TIME', - 'CMD', - ], - } - - -def get_fake_volume_list(): - status_code = 200 - response = { - 'Volumes': [ - { - 'Name': 'perfectcherryblossom', - 'Driver': 'local', - 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' - }, { - 'Name': 'subterraneananimism', - 'Driver': 'local', - 'Mountpoint': '/var/lib/docker/volumes/subterraneananimism' - } - ] - } - return status_code, response - - -def get_fake_volume(): - status_code = 200 - response = { - 'Name': 'perfectcherryblossom', - 'Driver': 'local', - 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' - } - return status_code, response - - -def fake_remove_volume(): - return 204, None - -# Maps real api url to fake response callback -prefix = 'http+docker://localunixsocket' -fake_responses = { - '{0}/version'.format(prefix): - get_fake_raw_version, - '{1}/{0}/version'.format(CURRENT_VERSION, prefix): - get_fake_version, - '{1}/{0}/info'.format(CURRENT_VERSION, prefix): - get_fake_info, - '{1}/{0}/images/search'.format(CURRENT_VERSION, prefix): - get_fake_search, - '{1}/{0}/images/json'.format(CURRENT_VERSION, prefix): - get_fake_images, - '{1}/{0}/images/test_image/history'.format(CURRENT_VERSION, prefix): - get_fake_image_history, - '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): - post_fake_import_image, - '{1}/{0}/containers/json'.format(CURRENT_VERSION, prefix): - get_fake_containers, - '{1}/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix): - post_fake_start_container, - '{1}/{0}/containers/3cc2351ab11b/resize'.format(CURRENT_VERSION, prefix): - post_fake_resize_container, - '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): - get_fake_inspect_container, - '{1}/{0}/containers/3cc2351ab11b/rename'.format(CURRENT_VERSION, prefix): - post_fake_rename_container, - '{1}/{0}/images/e9aa60c60128/tag'.format(CURRENT_VERSION, prefix): - post_fake_tag_image, - '{1}/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION, prefix): - get_fake_wait, - '{1}/{0}/containers/3cc2351ab11b/logs'.format(CURRENT_VERSION, prefix): - get_fake_logs, - '{1}/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION, prefix): - 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_exec_create, - '{1}/{0}/exec/d5d177f121dc/start'.format(CURRENT_VERSION, prefix): - post_fake_exec_start, - '{1}/{0}/exec/d5d177f121dc/json'.format(CURRENT_VERSION, prefix): - get_fake_exec_inspect, - '{1}/{0}/exec/d5d177f121dc/resize'.format(CURRENT_VERSION, prefix): - post_fake_exec_resize, - - '{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix): - get_fake_stats, - '{1}/{0}/containers/3cc2351ab11b/top'.format(CURRENT_VERSION, prefix): - get_fake_top, - '{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): - post_fake_restart_container, - '{1}/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION, prefix): - delete_fake_remove_container, - '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): - post_fake_image_create, - '{1}/{0}/images/e9aa60c60128'.format(CURRENT_VERSION, prefix): - delete_fake_remove_image, - '{1}/{0}/images/e9aa60c60128/get'.format(CURRENT_VERSION, prefix): - get_fake_get_image, - '{1}/{0}/images/load'.format(CURRENT_VERSION, prefix): - post_fake_load_image, - '{1}/{0}/images/test_image/json'.format(CURRENT_VERSION, prefix): - get_fake_inspect_image, - '{1}/{0}/images/test_image/insert'.format(CURRENT_VERSION, prefix): - get_fake_insert_image, - '{1}/{0}/images/test_image/push'.format(CURRENT_VERSION, prefix): - post_fake_push, - '{1}/{0}/commit'.format(CURRENT_VERSION, prefix): - post_fake_commit, - '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): - post_fake_create_container, - '{1}/{0}/build'.format(CURRENT_VERSION, prefix): - post_fake_build_container, - '{1}/{0}/events'.format(CURRENT_VERSION, prefix): - get_fake_events, - ('{1}/{0}/volumes'.format(CURRENT_VERSION, prefix), 'GET'): - get_fake_volume_list, - ('{1}/{0}/volumes'.format(CURRENT_VERSION, prefix), 'POST'): - get_fake_volume, - ('{1}/{0}/volumes/{2}'.format( - CURRENT_VERSION, prefix, FAKE_VOLUME_NAME - ), 'GET'): - get_fake_volume, - ('{1}/{0}/volumes/{2}'.format( - CURRENT_VERSION, prefix, FAKE_VOLUME_NAME - ), 'DELETE'): - fake_remove_volume, -} diff -Nru python-docker-1.5.0/tests/fake_stat.py python-docker-1.8.0/tests/fake_stat.py --- python-docker-1.5.0/tests/fake_stat.py 2015-09-10 21:11:09.000000000 +0000 +++ python-docker-1.8.0/tests/fake_stat.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,133 +0,0 @@ -OBJ = { - "read": "2015-02-11T19:20:46.667237763+02:00", - "network": { - "rx_bytes": 567224, - "rx_packets": 3773, - "rx_errors": 0, - "rx_dropped": 0, - "tx_bytes": 1176, - "tx_packets": 13, - "tx_errors": 0, - "tx_dropped": 0 - }, - "cpu_stats": { - "cpu_usage": { - "total_usage": 157260874053, - "percpu_usage": [ - 52196306950, - 24118413549, - 53292684398, - 27653469156 - ], - "usage_in_kernelmode": 37140000000, - "usage_in_usermode": 62140000000 - }, - "system_cpu_usage": 3.0881377e+14, - "throttling_data": { - "periods": 0, - "throttled_periods": 0, - "throttled_time": 0 - } - }, - "memory_stats": { - "usage": 179314688, - "max_usage": 258166784, - "stats": { - "active_anon": 90804224, - "active_file": 2195456, - "cache": 3096576, - "hierarchical_memory_limit": 1.844674407371e+19, - "inactive_anon": 85516288, - "inactive_file": 798720, - "mapped_file": 2646016, - "pgfault": 101034, - "pgmajfault": 1207, - "pgpgin": 115814, - "pgpgout": 75613, - "rss": 176218112, - "rss_huge": 12582912, - "total_active_anon": 90804224, - "total_active_file": 2195456, - "total_cache": 3096576, - "total_inactive_anon": 85516288, - "total_inactive_file": 798720, - "total_mapped_file": 2646016, - "total_pgfault": 101034, - "total_pgmajfault": 1207, - "total_pgpgin": 115814, - "total_pgpgout": 75613, - "total_rss": 176218112, - "total_rss_huge": 12582912, - "total_unevictable": 0, - "total_writeback": 0, - "unevictable": 0, - "writeback": 0 - }, - "failcnt": 0, - "limit": 8039038976 - }, - "blkio_stats": { - "io_service_bytes_recursive": [ - { - "major": 8, - "minor": 0, - "op": "Read", - "value": 72843264 - }, { - "major": 8, - "minor": 0, - "op": "Write", - "value": 4096 - }, { - "major": 8, - "minor": 0, - "op": "Sync", - "value": 4096 - }, { - "major": 8, - "minor": 0, - "op": "Async", - "value": 72843264 - }, { - "major": 8, - "minor": 0, - "op": "Total", - "value": 72847360 - } - ], - "io_serviced_recursive": [ - { - "major": 8, - "minor": 0, - "op": "Read", - "value": 10581 - }, { - "major": 8, - "minor": 0, - "op": "Write", - "value": 1 - }, { - "major": 8, - "minor": 0, - "op": "Sync", - "value": 1 - }, { - "major": 8, - "minor": 0, - "op": "Async", - "value": 10581 - }, { - "major": 8, - "minor": 0, - "op": "Total", - "value": 10582 - } - ], - "io_queue_recursive": [], - "io_service_time_recursive": [], - "io_wait_time_recursive": [], - "io_merged_recursive": [], - "io_time_recursive": [], - "sectors_recursive": [] - } -} diff -Nru python-docker-1.5.0/tests/helpers.py python-docker-1.8.0/tests/helpers.py --- python-docker-1.5.0/tests/helpers.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/helpers.py 2016-04-11 14:18:22.000000000 +0000 @@ -1,7 +1,18 @@ +import errno import os import os.path +import select +import shutil +import struct import tarfile import tempfile +import unittest + +import docker +import six + +BUSYBOX = 'busybox:buildroot-2014.02' +EXEC_DRIVER = [] def make_tree(dirs, files): @@ -35,3 +46,140 @@ result = f.read() f.close() return result + + +def exec_driver_is_native(): + global EXEC_DRIVER + if not EXEC_DRIVER: + c = docker_client() + EXEC_DRIVER = c.info()['ExecutionDriver'] + c.close() + return EXEC_DRIVER.startswith('native') + + +def docker_client(**kwargs): + return docker.Client(**docker_client_kwargs(**kwargs)) + + +def docker_client_kwargs(**kwargs): + client_kwargs = docker.utils.kwargs_from_env(assert_hostname=False) + client_kwargs.update(kwargs) + return client_kwargs + + +def read_socket(socket, n=4096): + """ Code stolen from dockerpty to read the socket """ + recoverable_errors = (errno.EINTR, errno.EDEADLK, errno.EWOULDBLOCK) + + # wait for data to become available + select.select([socket], [], []) + + try: + if hasattr(socket, 'recv'): + return socket.recv(n) + return os.read(socket.fileno(), n) + except EnvironmentError as e: + if e.errno not in recoverable_errors: + raise + + +def next_packet_size(socket): + """ Code stolen from dockerpty to get the next packet size """ + data = six.binary_type() + while len(data) < 8: + next_data = read_socket(socket, 8 - len(data)) + if not next_data: + return 0 + data = data + next_data + + if data is None: + return 0 + + if len(data) == 8: + _, actual = struct.unpack('>BxxxL', data) + return actual + + +def read_data(socket, packet_size): + data = six.binary_type() + while len(data) < packet_size: + next_data = read_socket(socket, packet_size - len(data)) + if not next_data: + assert False, "Failed trying to read in the dataz" + data += next_data + return data + + +class BaseTestCase(unittest.TestCase): + tmp_imgs = [] + tmp_containers = [] + tmp_folders = [] + tmp_volumes = [] + + def setUp(self): + if six.PY2: + self.assertRegex = self.assertRegexpMatches + self.assertCountEqual = self.assertItemsEqual + self.client = docker_client(timeout=60) + self.tmp_imgs = [] + self.tmp_containers = [] + self.tmp_folders = [] + self.tmp_volumes = [] + self.tmp_networks = [] + + def tearDown(self): + for img in self.tmp_imgs: + try: + self.client.remove_image(img) + except docker.errors.APIError: + pass + for container in self.tmp_containers: + try: + self.client.stop(container, timeout=1) + self.client.remove_container(container) + except docker.errors.APIError: + pass + for network in self.tmp_networks: + try: + self.client.remove_network(network) + except docker.errors.APIError: + pass + for folder in self.tmp_folders: + shutil.rmtree(folder) + + for volume in self.tmp_volumes: + try: + self.client.remove_volume(volume) + except docker.errors.APIError: + pass + + self.client.close() + + def run_container(self, *args, **kwargs): + container = self.client.create_container(*args, **kwargs) + self.tmp_containers.append(container) + self.client.start(container) + exitcode = self.client.wait(container) + + if exitcode != 0: + output = self.client.logs(container) + raise Exception( + "Container exited with code {}:\n{}" + .format(exitcode, output)) + + return container + + def create_and_start(self, image='busybox', command='top', **kwargs): + container = self.client.create_container( + image=image, command=command, **kwargs) + self.tmp_containers.append(container) + self.client.start(container) + return container + + def execute(self, container, cmd, exit_code=0, **kwargs): + exc = self.client.exec_create(container, cmd, **kwargs) + output = self.client.exec_start(exc) + actual_exit_code = self.client.exec_inspect(exc)['ExitCode'] + msg = "Expected `{}` to exit with code {} but returned {}:\n{}".format( + " ".join(cmd), exit_code, actual_exit_code, output) + assert actual_exit_code == exit_code, msg diff -Nru python-docker-1.5.0/tests/integration/api_test.py python-docker-1.8.0/tests/integration/api_test.py --- python-docker-1.5.0/tests/integration/api_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/api_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,176 @@ +import base64 +import os +import tempfile +import time +import unittest +import warnings + +import docker + +from .. import helpers + + +class InformationTest(helpers.BaseTestCase): + def test_version(self): + res = self.client.version() + self.assertIn('GoVersion', res) + self.assertIn('Version', res) + self.assertEqual(len(res['Version'].split('.')), 3) + + def test_info(self): + res = self.client.info() + self.assertIn('Containers', res) + self.assertIn('Images', res) + self.assertIn('Debug', res) + + def test_search(self): + self.client = helpers.docker_client(timeout=10) + res = self.client.search('busybox') + self.assertTrue(len(res) >= 1) + base_img = [x for x in res if x['name'] == 'busybox'] + self.assertEqual(len(base_img), 1) + self.assertIn('description', base_img[0]) + + +class LinkTest(helpers.BaseTestCase): + def test_remove_link(self): + # Create containers + container1 = self.client.create_container( + helpers.BUSYBOX, 'cat', detach=True, stdin_open=True + ) + container1_id = container1['Id'] + self.tmp_containers.append(container1_id) + self.client.start(container1_id) + + # Create Link + # we don't want the first / + link_path = self.client.inspect_container(container1_id)['Name'][1:] + link_alias = 'mylink' + + container2 = self.client.create_container( + helpers.BUSYBOX, 'cat', host_config=self.client.create_host_config( + links={link_path: link_alias} + ) + ) + container2_id = container2['Id'] + self.tmp_containers.append(container2_id) + self.client.start(container2_id) + + # Remove link + linked_name = self.client.inspect_container(container2_id)['Name'][1:] + link_name = '%s/%s' % (linked_name, link_alias) + self.client.remove_container(link_name, link=True) + + # Link is gone + containers = self.client.containers(all=True) + retrieved = [x for x in containers if link_name in x['Names']] + self.assertEqual(len(retrieved), 0) + + # Containers are still there + retrieved = [ + x for x in containers if x['Id'].startswith(container1_id) or + x['Id'].startswith(container2_id) + ] + self.assertEqual(len(retrieved), 2) + + +class LoadConfigTest(helpers.BaseTestCase): + def test_load_legacy_config(self): + folder = tempfile.mkdtemp() + self.tmp_folders.append(folder) + cfg_path = os.path.join(folder, '.dockercfg') + f = open(cfg_path, 'w') + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + f.write('auth = {0}\n'.format(auth_)) + f.write('email = sakuya@scarlet.net') + f.close() + cfg = docker.auth.load_config(cfg_path) + self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) + cfg = cfg[docker.auth.INDEX_NAME] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('Auth'), None) + + def test_load_json_config(self): + folder = tempfile.mkdtemp() + self.tmp_folders.append(folder) + cfg_path = os.path.join(folder, '.dockercfg') + f = open(os.path.join(folder, '.dockercfg'), 'w') + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + email_ = 'sakuya@scarlet.net' + f.write('{{"{0}": {{"auth": "{1}", "email": "{2}"}}}}\n'.format( + docker.auth.INDEX_URL, auth_, email_)) + f.close() + cfg = docker.auth.load_config(cfg_path) + self.assertNotEqual(cfg[docker.auth.INDEX_URL], None) + cfg = cfg[docker.auth.INDEX_URL] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('Auth'), None) + + +class AutoDetectVersionTest(unittest.TestCase): + def test_client_init(self): + client = helpers.docker_client(version='auto') + client_version = client._version + api_version = client.version(api_version=False)['ApiVersion'] + self.assertEqual(client_version, api_version) + api_version_2 = client.version()['ApiVersion'] + self.assertEqual(client_version, api_version_2) + client.close() + + def test_auto_client(self): + client = docker.AutoVersionClient(**helpers.docker_client_kwargs()) + client_version = client._version + api_version = client.version(api_version=False)['ApiVersion'] + self.assertEqual(client_version, api_version) + api_version_2 = client.version()['ApiVersion'] + self.assertEqual(client_version, api_version_2) + client.close() + with self.assertRaises(docker.errors.DockerException): + docker.AutoVersionClient( + **helpers.docker_client_kwargs(version='1.11') + ) + + +class ConnectionTimeoutTest(unittest.TestCase): + def setUp(self): + self.timeout = 0.5 + self.client = docker.client.Client(base_url='http://192.168.10.2:4243', + timeout=self.timeout) + + def test_timeout(self): + start = time.time() + res = None + # This call isn't supposed to complete, and it should fail fast. + try: + res = self.client.inspect_container('id') + except: + pass + end = time.time() + self.assertTrue(res is None) + self.assertTrue(end - start < 2 * self.timeout) + + +class UnixconnTest(unittest.TestCase): + """ + Test UNIX socket connection adapter. + """ + + def test_resource_warnings(self): + """ + Test no warnings are produced when using the client. + """ + + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter('always') + + client = helpers.docker_client() + client.images() + client.close() + del client + + assert len(w) == 0, \ + "No warnings produced: {0}".format(w[0].message) diff -Nru python-docker-1.5.0/tests/integration/build_test.py python-docker-1.8.0/tests/integration/build_test.py --- python-docker-1.5.0/tests/integration/build_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/build_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,168 @@ +import io +import json +import os +import shutil +import tempfile + +import six + +from docker import errors + +from .. import helpers +from ..base import requires_api_version + + +class BuildTest(helpers.BaseTestCase): + def test_build_streaming(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + stream = self.client.build(fileobj=script, stream=True) + logs = '' + for chunk in stream: + if six.PY3: + chunk = chunk.decode('utf-8') + json.loads(chunk) # ensure chunk is a single, valid JSON blob + logs += chunk + self.assertNotEqual(logs, '') + + def test_build_from_stringio(self): + if six.PY3: + return + script = io.StringIO(six.text_type('\n').join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ])) + stream = self.client.build(fileobj=script, stream=True) + logs = '' + for chunk in stream: + if six.PY3: + chunk = chunk.decode('utf-8') + logs += chunk + self.assertNotEqual(logs, '') + + @requires_api_version('1.8') + def test_build_with_dockerignore(self): + base_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base_dir) + + with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: + f.write("\n".join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'ADD . /test', + ])) + + with open(os.path.join(base_dir, '.dockerignore'), 'w') as f: + f.write("\n".join([ + 'ignored', + 'Dockerfile', + '.dockerignore', + '!ignored/subdir/excepted-file', + '', # empty line + ])) + + with open(os.path.join(base_dir, 'not-ignored'), 'w') as f: + f.write("this file should not be ignored") + + subdir = os.path.join(base_dir, 'ignored', 'subdir') + os.makedirs(subdir) + with open(os.path.join(subdir, 'file'), 'w') as f: + f.write("this file should be ignored") + + with open(os.path.join(subdir, 'excepted-file'), 'w') as f: + f.write("this file should not be ignored") + + tag = 'docker-py-test-build-with-dockerignore' + stream = self.client.build( + path=base_dir, + tag=tag, + ) + for chunk in stream: + pass + + c = self.client.create_container(tag, ['find', '/test', '-type', 'f']) + self.client.start(c) + self.client.wait(c) + logs = self.client.logs(c) + + if six.PY3: + logs = logs.decode('utf-8') + + self.assertEqual( + sorted(list(filter(None, logs.split('\n')))), + sorted(['/test/ignored/subdir/excepted-file', + '/test/not-ignored']), + ) + + @requires_api_version('1.21') + def test_build_with_buildargs(self): + script = io.BytesIO('\n'.join([ + 'FROM scratch', + 'ARG test', + 'USER $test' + ]).encode('ascii')) + + stream = self.client.build( + fileobj=script, tag='buildargs', buildargs={'test': 'OK'} + ) + self.tmp_imgs.append('buildargs') + for chunk in stream: + pass + + info = self.client.inspect_image('buildargs') + self.assertEqual(info['Config']['User'], 'OK') + + def test_build_stderr_data(self): + control_chars = ['\x1b[91m', '\x1b[0m'] + snippet = 'Ancient Temple (Mystic Oriental Dream ~ Ancient Temple)' + script = io.BytesIO(b'\n'.join([ + b'FROM busybox', + 'RUN sh -c ">&2 echo \'{0}\'"'.format(snippet).encode('utf-8') + ])) + + stream = self.client.build( + fileobj=script, stream=True, decode=True, nocache=True + ) + lines = [] + for chunk in stream: + lines.append(chunk.get('stream')) + expected = '{0}{2}\n{1}'.format( + control_chars[0], control_chars[1], snippet + ) + self.assertTrue(any([line == expected for line in lines])) + + def test_build_gzip_encoding(self): + base_dir = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, base_dir) + + with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: + f.write("\n".join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'ADD . /test', + ])) + + stream = self.client.build( + path=base_dir, stream=True, decode=True, nocache=True, + gzip=True + ) + + lines = [] + for chunk in stream: + lines.append(chunk) + + assert 'Successfully built' in lines[-1]['stream'] + + def test_build_gzip_custom_encoding(self): + with self.assertRaises(errors.DockerException): + self.client.build(path='.', gzip=True, encoding='text/html') diff -Nru python-docker-1.5.0/tests/integration/conftest.py python-docker-1.8.0/tests/integration/conftest.py --- python-docker-1.5.0/tests/integration/conftest.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/conftest.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,31 @@ +from __future__ import print_function + +import json +import sys +import warnings + +import docker.errors +import pytest + +from ..helpers import BUSYBOX +from ..helpers import docker_client + + +@pytest.fixture(autouse=True, scope='session') +def setup_test_session(): + warnings.simplefilter('error') + c = docker_client() + try: + c.inspect_image(BUSYBOX) + except docker.errors.NotFound: + print("\npulling {0}".format(BUSYBOX), file=sys.stderr) + for data in c.pull(BUSYBOX, stream=True): + data = json.loads(data.decode('utf-8')) + status = data.get("status") + progress = data.get("progress") + detail = "{0} - {1}".format(status, progress) + print(detail, file=sys.stderr) + + # Double make sure we now have busybox + c.inspect_image(BUSYBOX) + c.close() diff -Nru python-docker-1.5.0/tests/integration/container_test.py python-docker-1.8.0/tests/integration/container_test.py --- python-docker-1.5.0/tests/integration/container_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/container_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,1079 @@ +import os +import signal +import tempfile + +import docker +import pytest +import six + +from ..base import requires_api_version +from .. import helpers + +BUSYBOX = helpers.BUSYBOX + + +class ListContainersTest(helpers.BaseTestCase): + def test_list_containers(self): + res0 = self.client.containers(all=True) + size = len(res0) + res1 = self.client.create_container(BUSYBOX, 'true') + self.assertIn('Id', res1) + self.client.start(res1['Id']) + self.tmp_containers.append(res1['Id']) + res2 = self.client.containers(all=True) + self.assertEqual(size + 1, len(res2)) + retrieved = [x for x in res2 if x['Id'].startswith(res1['Id'])] + self.assertEqual(len(retrieved), 1) + retrieved = retrieved[0] + self.assertIn('Command', retrieved) + self.assertEqual(retrieved['Command'], six.text_type('true')) + self.assertIn('Image', retrieved) + self.assertRegex(retrieved['Image'], r'busybox:.*') + self.assertIn('Status', retrieved) + + +class CreateContainerTest(helpers.BaseTestCase): + + def test_create(self): + res = self.client.create_container(BUSYBOX, 'true') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + + def test_create_with_host_pid_mode(self): + ctnr = self.client.create_container( + BUSYBOX, 'true', host_config=self.client.create_host_config( + pid_mode='host', network_mode='none' + ) + ) + self.assertIn('Id', ctnr) + self.tmp_containers.append(ctnr['Id']) + self.client.start(ctnr) + inspect = self.client.inspect_container(ctnr) + self.assertIn('HostConfig', inspect) + host_config = inspect['HostConfig'] + self.assertIn('PidMode', host_config) + self.assertEqual(host_config['PidMode'], 'host') + + def test_create_with_links(self): + res0 = self.client.create_container( + BUSYBOX, 'cat', + detach=True, stdin_open=True, + environment={'FOO': '1'}) + + container1_id = res0['Id'] + self.tmp_containers.append(container1_id) + + self.client.start(container1_id) + + res1 = self.client.create_container( + BUSYBOX, 'cat', + detach=True, stdin_open=True, + environment={'FOO': '1'}) + + container2_id = res1['Id'] + self.tmp_containers.append(container2_id) + + self.client.start(container2_id) + + # we don't want the first / + link_path1 = self.client.inspect_container(container1_id)['Name'][1:] + link_alias1 = 'mylink1' + link_env_prefix1 = link_alias1.upper() + + link_path2 = self.client.inspect_container(container2_id)['Name'][1:] + link_alias2 = 'mylink2' + link_env_prefix2 = link_alias2.upper() + + res2 = self.client.create_container( + BUSYBOX, 'env', host_config=self.client.create_host_config( + links={link_path1: link_alias1, link_path2: link_alias2}, + network_mode='bridge' + ) + ) + container3_id = res2['Id'] + self.tmp_containers.append(container3_id) + self.client.start(container3_id) + self.assertEqual(self.client.wait(container3_id), 0) + + logs = self.client.logs(container3_id) + if six.PY3: + logs = logs.decode('utf-8') + self.assertIn('{0}_NAME='.format(link_env_prefix1), logs) + self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix1), logs) + self.assertIn('{0}_NAME='.format(link_env_prefix2), logs) + self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix2), logs) + + def test_create_with_restart_policy(self): + container = self.client.create_container( + BUSYBOX, ['sleep', '2'], + host_config=self.client.create_host_config( + restart_policy={"Name": "always", "MaximumRetryCount": 0}, + network_mode='none' + ) + ) + id = container['Id'] + self.client.start(id) + self.client.wait(id) + with self.assertRaises(docker.errors.APIError) as exc: + self.client.remove_container(id) + err = exc.exception.response.text + self.assertIn( + 'You cannot remove a running container', err + ) + self.client.remove_container(id, force=True) + + def test_create_container_with_volumes_from(self): + vol_names = ['foobar_vol0', 'foobar_vol1'] + + res0 = self.client.create_container( + BUSYBOX, 'true', name=vol_names[0] + ) + container1_id = res0['Id'] + self.tmp_containers.append(container1_id) + self.client.start(container1_id) + + res1 = self.client.create_container( + BUSYBOX, 'true', name=vol_names[1] + ) + container2_id = res1['Id'] + self.tmp_containers.append(container2_id) + self.client.start(container2_id) + with self.assertRaises(docker.errors.DockerException): + self.client.create_container( + BUSYBOX, 'cat', detach=True, stdin_open=True, + volumes_from=vol_names + ) + res2 = self.client.create_container( + BUSYBOX, 'cat', detach=True, stdin_open=True, + host_config=self.client.create_host_config( + volumes_from=vol_names, network_mode='none' + ) + ) + container3_id = res2['Id'] + self.tmp_containers.append(container3_id) + self.client.start(container3_id) + + info = self.client.inspect_container(res2['Id']) + self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names) + + def create_container_readonly_fs(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + ctnr = self.client.create_container( + BUSYBOX, ['mkdir', '/shrine'], + host_config=self.client.create_host_config( + read_only=True, network_mode='none' + ) + ) + self.assertIn('Id', ctnr) + self.tmp_containers.append(ctnr['Id']) + self.client.start(ctnr) + res = self.client.wait(ctnr) + self.assertNotEqual(res, 0) + + def create_container_with_name(self): + res = self.client.create_container(BUSYBOX, 'true', name='foobar') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Name', inspect) + self.assertEqual('/foobar', inspect['Name']) + + def create_container_privileged(self): + res = self.client.create_container( + BUSYBOX, 'true', host_config=self.client.create_host_config( + privileged=True, network_mode='none' + ) + ) + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + self.client.start(res['Id']) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Config', inspect) + self.assertIn('Id', inspect) + self.assertTrue(inspect['Id'].startswith(res['Id'])) + self.assertIn('Image', inspect) + self.assertIn('State', inspect) + self.assertIn('Running', inspect['State']) + if not inspect['State']['Running']: + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], 0) + # Since Nov 2013, the Privileged flag is no longer part of the + # container's config exposed via the API (safety concerns?). + # + if 'Privileged' in inspect['Config']: + self.assertEqual(inspect['Config']['Privileged'], True) + + def test_create_with_mac_address(self): + mac_address_expected = "02:42:ac:11:00:0a" + container = self.client.create_container( + BUSYBOX, ['sleep', '60'], mac_address=mac_address_expected) + + id = container['Id'] + + self.client.start(container) + res = self.client.inspect_container(container['Id']) + self.assertEqual(mac_address_expected, + res['NetworkSettings']['MacAddress']) + + self.client.kill(id) + + @requires_api_version('1.20') + def test_group_id_ints(self): + container = self.client.create_container( + BUSYBOX, 'id -G', + host_config=self.client.create_host_config(group_add=[1000, 1001]) + ) + self.tmp_containers.append(container) + self.client.start(container) + self.client.wait(container) + + logs = self.client.logs(container) + if six.PY3: + logs = logs.decode('utf-8') + groups = logs.strip().split(' ') + self.assertIn('1000', groups) + self.assertIn('1001', groups) + + @requires_api_version('1.20') + def test_group_id_strings(self): + container = self.client.create_container( + BUSYBOX, 'id -G', host_config=self.client.create_host_config( + group_add=['1000', '1001'] + ) + ) + self.tmp_containers.append(container) + self.client.start(container) + self.client.wait(container) + + logs = self.client.logs(container) + if six.PY3: + logs = logs.decode('utf-8') + + groups = logs.strip().split(' ') + self.assertIn('1000', groups) + self.assertIn('1001', groups) + + def test_valid_log_driver_and_log_opt(self): + log_config = docker.utils.LogConfig( + type='json-file', + config={'max-file': '100'} + ) + + container = self.client.create_container( + BUSYBOX, ['true'], + host_config=self.client.create_host_config(log_config=log_config) + ) + self.tmp_containers.append(container['Id']) + self.client.start(container) + + info = self.client.inspect_container(container) + container_log_config = info['HostConfig']['LogConfig'] + + self.assertEqual(container_log_config['Type'], log_config.type) + self.assertEqual(container_log_config['Config'], log_config.config) + + def test_invalid_log_driver_raises_exception(self): + log_config = docker.utils.LogConfig( + type='asdf-nope', + config={} + ) + + expected_msg = "logger: no log driver named 'asdf-nope' is registered" + with pytest.raises(docker.errors.APIError) as excinfo: + # raises an internal server error 500 + container = self.client.create_container( + BUSYBOX, ['true'], host_config=self.client.create_host_config( + log_config=log_config + ) + ) + self.client.start(container) + + assert expected_msg in str(excinfo.value) + + def test_valid_no_log_driver_specified(self): + log_config = docker.utils.LogConfig( + type="", + config={'max-file': '100'} + ) + + container = self.client.create_container( + BUSYBOX, ['true'], + host_config=self.client.create_host_config(log_config=log_config) + ) + self.tmp_containers.append(container['Id']) + self.client.start(container) + + info = self.client.inspect_container(container) + container_log_config = info['HostConfig']['LogConfig'] + + self.assertEqual(container_log_config['Type'], "json-file") + self.assertEqual(container_log_config['Config'], log_config.config) + + def test_valid_no_config_specified(self): + log_config = docker.utils.LogConfig( + type="json-file", + config=None + ) + + container = self.client.create_container( + BUSYBOX, ['true'], + host_config=self.client.create_host_config(log_config=log_config) + ) + self.tmp_containers.append(container['Id']) + self.client.start(container) + + info = self.client.inspect_container(container) + container_log_config = info['HostConfig']['LogConfig'] + + self.assertEqual(container_log_config['Type'], "json-file") + self.assertEqual(container_log_config['Config'], {}) + + def test_create_with_memory_constraints_with_str(self): + ctnr = self.client.create_container( + BUSYBOX, 'true', + host_config=self.client.create_host_config( + memswap_limit='1G', + mem_limit='700M' + ) + ) + self.assertIn('Id', ctnr) + self.tmp_containers.append(ctnr['Id']) + self.client.start(ctnr) + inspect = self.client.inspect_container(ctnr) + + self.assertIn('HostConfig', inspect) + host_config = inspect['HostConfig'] + for limit in ['Memory', 'MemorySwap']: + self.assertIn(limit, host_config) + + def test_create_with_memory_constraints_with_int(self): + ctnr = self.client.create_container( + BUSYBOX, 'true', + host_config=self.client.create_host_config(mem_swappiness=40) + ) + self.assertIn('Id', ctnr) + self.tmp_containers.append(ctnr['Id']) + self.client.start(ctnr) + inspect = self.client.inspect_container(ctnr) + + self.assertIn('HostConfig', inspect) + host_config = inspect['HostConfig'] + self.assertIn('MemorySwappiness', host_config) + + def test_create_host_config_exception_raising(self): + self.assertRaises(TypeError, + self.client.create_host_config, mem_swappiness='40') + + self.assertRaises(ValueError, + self.client.create_host_config, pid_mode='40') + + def test_create_with_environment_variable_no_value(self): + container = self.client.create_container( + BUSYBOX, + ['echo'], + environment={'Foo': None, 'Other': 'one', 'Blank': ''}, + ) + self.tmp_containers.append(container['Id']) + config = self.client.inspect_container(container['Id']) + assert ( + sorted(config['Config']['Env']) == + sorted(['Foo', 'Other=one', 'Blank=']) + ) + + @requires_api_version('1.22') + def test_create_with_tmpfs(self): + tmpfs = { + '/tmp1': 'size=3M' + } + + container = self.client.create_container( + BUSYBOX, + ['echo'], + host_config=self.client.create_host_config( + tmpfs=tmpfs)) + + self.tmp_containers.append(container['Id']) + config = self.client.inspect_container(container) + assert config['HostConfig']['Tmpfs'] == tmpfs + + +class VolumeBindTest(helpers.BaseTestCase): + def setUp(self): + super(VolumeBindTest, self).setUp() + + self.mount_dest = '/mnt' + + # Get a random pathname - we don't need it to exist locally + self.mount_origin = tempfile.mkdtemp() + self.filename = 'shared.txt' + + self.run_with_volume( + False, + BUSYBOX, + ['touch', os.path.join(self.mount_dest, self.filename)], + ) + + def test_create_with_binds_rw(self): + + container = self.run_with_volume( + False, + BUSYBOX, + ['ls', self.mount_dest], + ) + logs = self.client.logs(container) + + if six.PY3: + logs = logs.decode('utf-8') + self.assertIn(self.filename, logs) + inspect_data = self.client.inspect_container(container) + self.check_container_data(inspect_data, True) + + def test_create_with_binds_ro(self): + self.run_with_volume( + False, + BUSYBOX, + ['touch', os.path.join(self.mount_dest, self.filename)], + ) + container = self.run_with_volume( + True, + BUSYBOX, + ['ls', self.mount_dest], + ) + logs = self.client.logs(container) + + if six.PY3: + logs = logs.decode('utf-8') + self.assertIn(self.filename, logs) + + inspect_data = self.client.inspect_container(container) + self.check_container_data(inspect_data, False) + + def check_container_data(self, inspect_data, rw): + if docker.utils.compare_version('1.20', self.client._version) < 0: + self.assertIn('Volumes', inspect_data) + self.assertIn(self.mount_dest, inspect_data['Volumes']) + self.assertEqual( + self.mount_origin, inspect_data['Volumes'][self.mount_dest] + ) + self.assertIn(self.mount_dest, inspect_data['VolumesRW']) + self.assertFalse(inspect_data['VolumesRW'][self.mount_dest]) + else: + self.assertIn('Mounts', inspect_data) + filtered = list(filter( + lambda x: x['Destination'] == self.mount_dest, + inspect_data['Mounts'] + )) + self.assertEqual(len(filtered), 1) + mount_data = filtered[0] + self.assertEqual(mount_data['Source'], self.mount_origin) + self.assertEqual(mount_data['RW'], rw) + + def run_with_volume(self, ro, *args, **kwargs): + return self.run_container( + *args, + volumes={self.mount_dest: {}}, + host_config=self.client.create_host_config( + binds={ + self.mount_origin: { + 'bind': self.mount_dest, + 'ro': ro, + }, + }, + network_mode='none' + ), + **kwargs + ) + + +@requires_api_version('1.20') +class ArchiveTest(helpers.BaseTestCase): + def test_get_file_archive_from_container(self): + data = 'The Maid and the Pocket Watch of Blood' + ctnr = self.client.create_container( + BUSYBOX, 'sh -c "echo {0} > /vol1/data.txt"'.format(data), + volumes=['/vol1'] + ) + self.tmp_containers.append(ctnr) + self.client.start(ctnr) + self.client.wait(ctnr) + with tempfile.NamedTemporaryFile() as destination: + strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') + for d in strm: + destination.write(d) + destination.seek(0) + retrieved_data = helpers.untar_file(destination, 'data.txt') + if six.PY3: + retrieved_data = retrieved_data.decode('utf-8') + self.assertEqual(data, retrieved_data.strip()) + + def test_get_file_stat_from_container(self): + data = 'The Maid and the Pocket Watch of Blood' + ctnr = self.client.create_container( + BUSYBOX, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data), + volumes=['/vol1'] + ) + self.tmp_containers.append(ctnr) + self.client.start(ctnr) + self.client.wait(ctnr) + strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') + self.assertIn('name', stat) + self.assertEqual(stat['name'], 'data.txt') + self.assertIn('size', stat) + self.assertEqual(stat['size'], len(data)) + + def test_copy_file_to_container(self): + data = b'Deaf To All But The Song' + with tempfile.NamedTemporaryFile() as test_file: + test_file.write(data) + test_file.seek(0) + ctnr = self.client.create_container( + BUSYBOX, + 'cat {0}'.format( + os.path.join('/vol1', os.path.basename(test_file.name)) + ), + volumes=['/vol1'] + ) + self.tmp_containers.append(ctnr) + with helpers.simple_tar(test_file.name) as test_tar: + self.client.put_archive(ctnr, '/vol1', test_tar) + self.client.start(ctnr) + self.client.wait(ctnr) + logs = self.client.logs(ctnr) + if six.PY3: + logs = logs.decode('utf-8') + data = data.decode('utf-8') + self.assertEqual(logs.strip(), data) + + def test_copy_directory_to_container(self): + files = ['a.py', 'b.py', 'foo/b.py'] + dirs = ['foo', 'bar'] + base = helpers.make_tree(dirs, files) + ctnr = self.client.create_container( + BUSYBOX, 'ls -p /vol1', volumes=['/vol1'] + ) + self.tmp_containers.append(ctnr) + with docker.utils.tar(base) as test_tar: + self.client.put_archive(ctnr, '/vol1', test_tar) + self.client.start(ctnr) + self.client.wait(ctnr) + logs = self.client.logs(ctnr) + if six.PY3: + logs = logs.decode('utf-8') + results = logs.strip().split() + self.assertIn('a.py', results) + self.assertIn('b.py', results) + self.assertIn('foo/', results) + self.assertIn('bar/', results) + + +class RenameContainerTest(helpers.BaseTestCase): + def test_rename_container(self): + version = self.client.version()['Version'] + name = 'hong_meiling' + res = self.client.create_container(BUSYBOX, 'true') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + self.client.rename(res, name) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Name', inspect) + if version == '1.5.0': + self.assertEqual(name, inspect['Name']) + else: + self.assertEqual('/{0}'.format(name), inspect['Name']) + + +class StartContainerTest(helpers.BaseTestCase): + def test_start_container(self): + res = self.client.create_container(BUSYBOX, 'true') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + self.client.start(res['Id']) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Config', inspect) + self.assertIn('Id', inspect) + self.assertTrue(inspect['Id'].startswith(res['Id'])) + self.assertIn('Image', inspect) + self.assertIn('State', inspect) + self.assertIn('Running', inspect['State']) + if not inspect['State']['Running']: + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], 0) + + def test_start_container_with_dict_instead_of_id(self): + res = self.client.create_container(BUSYBOX, 'true') + self.assertIn('Id', res) + self.tmp_containers.append(res['Id']) + self.client.start(res) + inspect = self.client.inspect_container(res['Id']) + self.assertIn('Config', inspect) + self.assertIn('Id', inspect) + self.assertTrue(inspect['Id'].startswith(res['Id'])) + self.assertIn('Image', inspect) + self.assertIn('State', inspect) + self.assertIn('Running', inspect['State']) + if not inspect['State']['Running']: + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], 0) + + def test_run_shlex_commands(self): + commands = [ + 'true', + 'echo "The Young Descendant of Tepes & Septette for the ' + 'Dead Princess"', + 'echo -n "The Young Descendant of Tepes & Septette for the ' + 'Dead Princess"', + '/bin/sh -c "echo Hello World"', + '/bin/sh -c \'echo "Hello World"\'', + 'echo "\"Night of Nights\""', + 'true && echo "Night of Nights"' + ] + for cmd in commands: + container = self.client.create_container(BUSYBOX, cmd) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0, msg=cmd) + + +class WaitTest(helpers.BaseTestCase): + def test_wait(self): + res = self.client.create_container(BUSYBOX, ['sleep', '3']) + id = res['Id'] + self.tmp_containers.append(id) + self.client.start(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + inspect = self.client.inspect_container(id) + self.assertIn('Running', inspect['State']) + self.assertEqual(inspect['State']['Running'], False) + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], exitcode) + + def test_wait_with_dict_instead_of_id(self): + res = self.client.create_container(BUSYBOX, ['sleep', '3']) + id = res['Id'] + self.tmp_containers.append(id) + self.client.start(res) + exitcode = self.client.wait(res) + self.assertEqual(exitcode, 0) + inspect = self.client.inspect_container(res) + self.assertIn('Running', inspect['State']) + self.assertEqual(inspect['State']['Running'], False) + self.assertIn('ExitCode', inspect['State']) + self.assertEqual(inspect['State']['ExitCode'], exitcode) + + +class LogsTest(helpers.BaseTestCase): + def test_logs(self): + snippet = 'Flowering Nights (Sakuya Iyazoi)' + container = self.client.create_container( + BUSYBOX, 'echo {0}'.format(snippet) + ) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + logs = self.client.logs(id) + self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) + + def test_logs_tail_option(self): + snippet = '''Line1 +Line2''' + container = self.client.create_container( + BUSYBOX, 'echo "{0}"'.format(snippet) + ) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + logs = self.client.logs(id, tail=1) + self.assertEqual(logs, 'Line2\n'.encode(encoding='ascii')) + + def test_logs_streaming_and_follow(self): + snippet = 'Flowering Nights (Sakuya Iyazoi)' + container = self.client.create_container( + BUSYBOX, 'echo {0}'.format(snippet) + ) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + logs = six.binary_type() + for chunk in self.client.logs(id, stream=True, follow=True): + logs += chunk + + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + + self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) + + def test_logs_with_dict_instead_of_id(self): + snippet = 'Flowering Nights (Sakuya Iyazoi)' + container = self.client.create_container( + BUSYBOX, 'echo {0}'.format(snippet) + ) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + logs = self.client.logs(container) + self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) + + def test_logs_with_tail_0(self): + snippet = 'Flowering Nights (Sakuya Iyazoi)' + container = self.client.create_container( + BUSYBOX, 'echo "{0}"'.format(snippet) + ) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + logs = self.client.logs(id, tail=0) + self.assertEqual(logs, ''.encode(encoding='ascii')) + + +class DiffTest(helpers.BaseTestCase): + def test_diff(self): + container = self.client.create_container(BUSYBOX, ['touch', '/test']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + diff = self.client.diff(id) + test_diff = [x for x in diff if x.get('Path', None) == '/test'] + self.assertEqual(len(test_diff), 1) + self.assertIn('Kind', test_diff[0]) + self.assertEqual(test_diff[0]['Kind'], 1) + + def test_diff_with_dict_instead_of_id(self): + container = self.client.create_container(BUSYBOX, ['touch', '/test']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + exitcode = self.client.wait(id) + self.assertEqual(exitcode, 0) + diff = self.client.diff(container) + test_diff = [x for x in diff if x.get('Path', None) == '/test'] + self.assertEqual(len(test_diff), 1) + self.assertIn('Kind', test_diff[0]) + self.assertEqual(test_diff[0]['Kind'], 1) + + +class StopTest(helpers.BaseTestCase): + def test_stop(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + self.client.stop(id, timeout=2) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + + def test_stop_with_dict_instead_of_id(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + self.assertIn('Id', container) + id = container['Id'] + self.client.start(container) + self.tmp_containers.append(id) + self.client.stop(container, timeout=2) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + + +class KillTest(helpers.BaseTestCase): + def test_kill(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + self.client.kill(id) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('ExitCode', state) + if helpers.exec_driver_is_native(): + self.assertNotEqual(state['ExitCode'], 0) + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + + def test_kill_with_dict_instead_of_id(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + self.client.kill(container) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('ExitCode', state) + if helpers.exec_driver_is_native(): + self.assertNotEqual(state['ExitCode'], 0) + self.assertIn('Running', state) + self.assertEqual(state['Running'], False) + + def test_kill_with_signal(self): + container = self.client.create_container(BUSYBOX, ['sleep', '60']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + self.client.kill(id, signal=signal.SIGKILL) + exitcode = self.client.wait(id) + self.assertNotEqual(exitcode, 0) + container_info = self.client.inspect_container(id) + self.assertIn('State', container_info) + state = container_info['State'] + self.assertIn('ExitCode', state) + self.assertNotEqual(state['ExitCode'], 0) + self.assertIn('Running', state) + self.assertEqual(state['Running'], False, state) + + +class PortTest(helpers.BaseTestCase): + def test_port(self): + + port_bindings = { + '1111': ('127.0.0.1', '4567'), + '2222': ('127.0.0.1', '4568') + } + + container = self.client.create_container( + BUSYBOX, ['sleep', '60'], ports=list(port_bindings.keys()), + host_config=self.client.create_host_config( + port_bindings=port_bindings, network_mode='bridge' + ) + ) + id = container['Id'] + + self.client.start(container) + + # Call the port function on each biding and compare expected vs actual + for port in port_bindings: + actual_bindings = self.client.port(container, port) + port_binding = actual_bindings.pop() + + ip, host_port = port_binding['HostIp'], port_binding['HostPort'] + + self.assertEqual(ip, port_bindings[port][0]) + self.assertEqual(host_port, port_bindings[port][1]) + + self.client.kill(id) + + +class ContainerTopTest(helpers.BaseTestCase): + def test_top(self): + container = self.client.create_container( + BUSYBOX, ['sleep', '60']) + + id = container['Id'] + + self.client.start(container) + res = self.client.top(container['Id']) + self.assertEqual( + res['Titles'], + ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'] + ) + self.assertEqual(len(res['Processes']), 1) + self.assertEqual(res['Processes'][0][7], 'sleep 60') + self.client.kill(id) + + def test_top_with_psargs(self): + container = self.client.create_container( + BUSYBOX, ['sleep', '60']) + + id = container['Id'] + + self.client.start(container) + res = self.client.top(container['Id'], 'waux') + self.assertEqual( + res['Titles'], + ['USER', 'PID', '%CPU', '%MEM', 'VSZ', 'RSS', + 'TTY', 'STAT', 'START', 'TIME', 'COMMAND'], + ) + self.assertEqual(len(res['Processes']), 1) + self.assertEqual(res['Processes'][0][10], 'sleep 60') + self.client.kill(id) + + +class RestartContainerTest(helpers.BaseTestCase): + def test_restart(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + info = self.client.inspect_container(id) + self.assertIn('State', info) + self.assertIn('StartedAt', info['State']) + start_time1 = info['State']['StartedAt'] + self.client.restart(id, timeout=2) + info2 = self.client.inspect_container(id) + self.assertIn('State', info2) + self.assertIn('StartedAt', info2['State']) + start_time2 = info2['State']['StartedAt'] + self.assertNotEqual(start_time1, start_time2) + self.assertIn('Running', info2['State']) + self.assertEqual(info2['State']['Running'], True) + self.client.kill(id) + + def test_restart_with_dict_instead_of_id(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + self.assertIn('Id', container) + id = container['Id'] + self.client.start(container) + self.tmp_containers.append(id) + info = self.client.inspect_container(id) + self.assertIn('State', info) + self.assertIn('StartedAt', info['State']) + start_time1 = info['State']['StartedAt'] + self.client.restart(container, timeout=2) + info2 = self.client.inspect_container(id) + self.assertIn('State', info2) + self.assertIn('StartedAt', info2['State']) + start_time2 = info2['State']['StartedAt'] + self.assertNotEqual(start_time1, start_time2) + self.assertIn('Running', info2['State']) + self.assertEqual(info2['State']['Running'], True) + self.client.kill(id) + + +class RemoveContainerTest(helpers.BaseTestCase): + def test_remove(self): + container = self.client.create_container(BUSYBOX, ['true']) + id = container['Id'] + self.client.start(id) + self.client.wait(id) + self.client.remove_container(id) + containers = self.client.containers(all=True) + res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] + self.assertEqual(len(res), 0) + + def test_remove_with_dict_instead_of_id(self): + container = self.client.create_container(BUSYBOX, ['true']) + id = container['Id'] + self.client.start(id) + self.client.wait(id) + self.client.remove_container(container) + containers = self.client.containers(all=True) + res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] + self.assertEqual(len(res), 0) + + +class AttachContainerTest(helpers.BaseTestCase): + def test_run_container_streaming(self): + container = self.client.create_container(BUSYBOX, '/bin/sh', + detach=True, stdin_open=True) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + sock = self.client.attach_socket(container, ws=False) + self.assertTrue(sock.fileno() > -1) + + def test_run_container_reading_socket(self): + line = 'hi there and stuff and things, words!' + # `echo` appends CRLF, `printf` doesn't + command = "printf '{0}'".format(line) + container = self.client.create_container(BUSYBOX, command, + detach=True, tty=False) + ident = container['Id'] + self.tmp_containers.append(ident) + + opts = {"stdout": 1, "stream": 1, "logs": 1} + pty_stdout = self.client.attach_socket(ident, opts) + self.addCleanup(pty_stdout.close) + + self.client.start(ident) + + next_size = helpers.next_packet_size(pty_stdout) + self.assertEqual(next_size, len(line)) + data = helpers.read_data(pty_stdout, next_size) + self.assertEqual(data.decode('utf-8'), line) + + +class PauseTest(helpers.BaseTestCase): + def test_pause_unpause(self): + container = self.client.create_container(BUSYBOX, ['sleep', '9999']) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(container) + 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) + + +class GetContainerStatsTest(helpers.BaseTestCase): + @requires_api_version('1.19') + def test_get_container_stats_no_stream(self): + container = self.client.create_container( + BUSYBOX, ['sleep', '60'], + ) + self.tmp_containers.append(container) + self.client.start(container) + response = self.client.stats(container, stream=0) + self.client.kill(container) + + self.assertEqual(type(response), dict) + for key in ['read', 'networks', 'precpu_stats', 'cpu_stats', + 'memory_stats', 'blkio_stats']: + self.assertIn(key, response) + + @requires_api_version('1.17') + def test_get_container_stats_stream(self): + container = self.client.create_container( + BUSYBOX, ['sleep', '60'], + ) + self.tmp_containers.append(container) + self.client.start(container) + stream = self.client.stats(container) + for chunk in stream: + self.assertEqual(type(chunk), dict) + for key in ['read', 'network', 'precpu_stats', 'cpu_stats', + 'memory_stats', 'blkio_stats']: + self.assertIn(key, chunk) + + +class ContainerUpdateTest(helpers.BaseTestCase): + @requires_api_version('1.22') + def test_update_container(self): + old_mem_limit = 400 * 1024 * 1024 + new_mem_limit = 300 * 1024 * 1024 + container = self.client.create_container( + BUSYBOX, 'top', host_config=self.client.create_host_config( + mem_limit=old_mem_limit + ), cpu_shares=102 + ) + self.tmp_containers.append(container) + self.client.start(container) + self.client.update_container(container, mem_limit=new_mem_limit) + inspect_data = self.client.inspect_container(container) + self.assertEqual(inspect_data['HostConfig']['Memory'], new_mem_limit) + self.assertEqual(inspect_data['HostConfig']['CpuShares'], 102) diff -Nru python-docker-1.5.0/tests/integration/exec_test.py python-docker-1.8.0/tests/integration/exec_test.py --- python-docker-1.5.0/tests/integration/exec_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/exec_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,130 @@ +import pytest + +from .. import helpers + +BUSYBOX = helpers.BUSYBOX + + +class ExecTest(helpers.BaseTestCase): + def test_execute_command(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + 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.exec_create(id, ['echo', 'hello']) + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + self.assertEqual(exec_log, b'hello\n') + + def test_exec_command_string(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + 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.exec_create(id, 'echo hello world') + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + self.assertEqual(exec_log, b'hello world\n') + + def test_exec_command_as_user(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + 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.exec_create(id, 'whoami', user='default') + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + self.assertEqual(exec_log, b'default\n') + + def test_exec_command_as_root(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + 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.exec_create(id, 'whoami') + self.assertIn('Id', res) + + exec_log = self.client.exec_start(res) + self.assertEqual(exec_log, b'root\n') + + def test_exec_command_streaming(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + container = self.client.create_container(BUSYBOX, 'cat', + detach=True, stdin_open=True) + id = container['Id'] + self.tmp_containers.append(id) + self.client.start(id) + + exec_id = self.client.exec_create(id, ['echo', 'hello\nworld']) + self.assertIn('Id', exec_id) + + res = b'' + for chunk in self.client.exec_start(exec_id, stream=True): + res += chunk + self.assertEqual(res, b'hello\nworld\n') + + def test_exec_start_socket(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + container = self.client.create_container(BUSYBOX, 'cat', + detach=True, stdin_open=True) + container_id = container['Id'] + self.client.start(container_id) + self.tmp_containers.append(container_id) + + line = 'yay, interactive exec!' + # `echo` appends CRLF, `printf` doesn't + exec_id = self.client.exec_create( + container_id, ['printf', line], tty=True) + self.assertIn('Id', exec_id) + + socket = self.client.exec_start(exec_id, socket=True) + self.addCleanup(socket.close) + + next_size = helpers.next_packet_size(socket) + self.assertEqual(next_size, len(line)) + data = helpers.read_data(socket, next_size) + self.assertEqual(data.decode('utf-8'), line) + + def test_exec_inspect(self): + if not helpers.exec_driver_is_native(): + pytest.skip('Exec driver not native') + + container = self.client.create_container(BUSYBOX, 'cat', + detach=True, stdin_open=True) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + + exec_id = self.client.exec_create(id, ['mkdir', '/does/not/exist']) + self.assertIn('Id', exec_id) + self.client.exec_start(exec_id) + exec_info = self.client.exec_inspect(exec_id) + self.assertIn('ExitCode', exec_info) + self.assertNotEqual(exec_info['ExitCode'], 0) diff -Nru python-docker-1.5.0/tests/integration/image_test.py python-docker-1.8.0/tests/integration/image_test.py --- python-docker-1.5.0/tests/integration/image_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/image_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,249 @@ +import contextlib +import json +import shutil +import socket +import tarfile +import tempfile +import threading + +import pytest +import six +from six.moves import BaseHTTPServer +from six.moves import socketserver + + +import docker + +from .. import helpers + +BUSYBOX = helpers.BUSYBOX + + +class ListImagesTest(helpers.BaseTestCase): + def test_images(self): + res1 = self.client.images(all=True) + self.assertIn('Id', res1[0]) + res10 = res1[0] + self.assertIn('Created', res10) + self.assertIn('RepoTags', res10) + distinct = [] + for img in res1: + if img['Id'] not in distinct: + distinct.append(img['Id']) + self.assertEqual(len(distinct), self.client.info()['Images']) + + def test_images_quiet(self): + res1 = self.client.images(quiet=True) + self.assertEqual(type(res1[0]), six.text_type) + + +class PullImageTest(helpers.BaseTestCase): + def test_pull(self): + try: + self.client.remove_image('hello-world') + except docker.errors.APIError: + pass + res = self.client.pull('hello-world') + self.tmp_imgs.append('hello-world') + self.assertEqual(type(res), six.text_type) + self.assertGreaterEqual( + len(self.client.images('hello-world')), 1 + ) + img_info = self.client.inspect_image('hello-world') + self.assertIn('Id', img_info) + + def test_pull_streaming(self): + try: + self.client.remove_image('hello-world') + except docker.errors.APIError: + pass + stream = self.client.pull('hello-world', stream=True) + self.tmp_imgs.append('hello-world') + for chunk in stream: + if six.PY3: + chunk = chunk.decode('utf-8') + json.loads(chunk) # ensure chunk is a single, valid JSON blob + self.assertGreaterEqual( + len(self.client.images('hello-world')), 1 + ) + img_info = self.client.inspect_image('hello-world') + self.assertIn('Id', img_info) + + +class CommitTest(helpers.BaseTestCase): + def test_commit(self): + container = self.client.create_container(BUSYBOX, ['touch', '/test']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + res = self.client.commit(id) + self.assertIn('Id', res) + img_id = res['Id'] + self.tmp_imgs.append(img_id) + img = self.client.inspect_image(img_id) + self.assertIn('Container', img) + self.assertTrue(img['Container'].startswith(id)) + self.assertIn('ContainerConfig', img) + self.assertIn('Image', img['ContainerConfig']) + self.assertEqual(BUSYBOX, img['ContainerConfig']['Image']) + busybox_id = self.client.inspect_image(BUSYBOX)['Id'] + self.assertIn('Parent', img) + self.assertEqual(img['Parent'], busybox_id) + + def test_commit_with_changes(self): + cid = self.client.create_container(BUSYBOX, ['touch', '/test']) + self.tmp_containers.append(cid) + self.client.start(cid) + img_id = self.client.commit( + cid, changes=['EXPOSE 8000', 'CMD ["bash"]'] + ) + self.tmp_imgs.append(img_id) + img = self.client.inspect_image(img_id) + assert 'Container' in img + assert img['Container'].startswith(cid['Id']) + assert '8000/tcp' in img['Config']['ExposedPorts'] + assert img['Config']['Cmd'] == ['bash'] + + +class RemoveImageTest(helpers.BaseTestCase): + def test_remove(self): + container = self.client.create_container(BUSYBOX, ['touch', '/test']) + id = container['Id'] + self.client.start(id) + self.tmp_containers.append(id) + res = self.client.commit(id) + self.assertIn('Id', res) + img_id = res['Id'] + self.tmp_imgs.append(img_id) + self.client.remove_image(img_id, force=True) + images = self.client.images(all=True) + res = [x for x in images if x['Id'].startswith(img_id)] + self.assertEqual(len(res), 0) + + +class ImportImageTest(helpers.BaseTestCase): + '''Base class for `docker import` test cases.''' + + TAR_SIZE = 512 * 1024 + + def write_dummy_tar_content(self, n_bytes, tar_fd): + def extend_file(f, n_bytes): + f.seek(n_bytes - 1) + f.write(bytearray([65])) + f.seek(0) + + tar = tarfile.TarFile(fileobj=tar_fd, mode='w') + + with tempfile.NamedTemporaryFile() as f: + extend_file(f, n_bytes) + tarinfo = tar.gettarinfo(name=f.name, arcname='testdata') + tar.addfile(tarinfo, fileobj=f) + + tar.close() + + @contextlib.contextmanager + def dummy_tar_stream(self, n_bytes): + '''Yields a stream that is valid tar data of size n_bytes.''' + with tempfile.NamedTemporaryFile() as tar_file: + self.write_dummy_tar_content(n_bytes, tar_file) + tar_file.seek(0) + yield tar_file + + @contextlib.contextmanager + def dummy_tar_file(self, n_bytes): + '''Yields the name of a valid tar file of size n_bytes.''' + with tempfile.NamedTemporaryFile() as tar_file: + self.write_dummy_tar_content(n_bytes, tar_file) + tar_file.seek(0) + yield tar_file.name + + def test_import_from_bytes(self): + with self.dummy_tar_stream(n_bytes=500) as f: + content = f.read() + + # The generic import_image() function cannot import in-memory bytes + # data that happens to be represented as a string type, because + # import_image() will try to use it as a filename and usually then + # trigger an exception. So we test the import_image_from_data() + # function instead. + statuses = self.client.import_image_from_data( + content, repository='test/import-from-bytes') + + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + self.assertNotIn('error', result) + + img_id = result['status'] + self.tmp_imgs.append(img_id) + + def test_import_from_file(self): + with self.dummy_tar_file(n_bytes=self.TAR_SIZE) as tar_filename: + # statuses = self.client.import_image( + # src=tar_filename, repository='test/import-from-file') + statuses = self.client.import_image_from_file( + tar_filename, repository='test/import-from-file') + + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + self.assertNotIn('error', result) + + self.assertIn('status', result) + img_id = result['status'] + self.tmp_imgs.append(img_id) + + def test_import_from_stream(self): + with self.dummy_tar_stream(n_bytes=self.TAR_SIZE) as tar_stream: + statuses = self.client.import_image( + src=tar_stream, repository='test/import-from-stream') + # statuses = self.client.import_image_from_stream( + # tar_stream, repository='test/import-from-stream') + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + self.assertNotIn('error', result) + + self.assertIn('status', result) + img_id = result['status'] + self.tmp_imgs.append(img_id) + + @contextlib.contextmanager + def temporary_http_file_server(self, stream): + '''Serve data from an IO stream over HTTP.''' + + class Handler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_GET(self): + self.send_response(200) + self.send_header('Content-Type', 'application/x-tar') + self.end_headers() + shutil.copyfileobj(stream, self.wfile) + + server = socketserver.TCPServer(('', 0), Handler) + thread = threading.Thread(target=server.serve_forever) + thread.setDaemon(True) + thread.start() + + yield 'http://%s:%s' % (socket.gethostname(), server.server_address[1]) + + server.shutdown() + + @pytest.mark.skipif(True, reason="Doesn't work inside a container - FIXME") + def test_import_from_url(self): + # The crappy test HTTP server doesn't handle large files well, so use + # a small file. + tar_size = 10240 + + with self.dummy_tar_stream(n_bytes=tar_size) as tar_data: + with self.temporary_http_file_server(tar_data) as url: + statuses = self.client.import_image( + src=url, repository='test/import-from-url') + + result_text = statuses.splitlines()[-1] + result = json.loads(result_text) + + self.assertNotIn('error', result) + + self.assertIn('status', result) + img_id = result['status'] + self.tmp_imgs.append(img_id) diff -Nru python-docker-1.5.0/tests/integration/network_test.py python-docker-1.8.0/tests/integration/network_test.py --- python-docker-1.5.0/tests/integration/network_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/network_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,300 @@ +import random + +import docker +from docker.utils import create_ipam_config +from docker.utils import create_ipam_pool +import pytest + +from .. import helpers +from ..base import requires_api_version + + +class TestNetworks(helpers.BaseTestCase): + def create_network(self, *args, **kwargs): + net_name = u'dockerpy{}'.format(random.getrandbits(24))[:14] + net_id = self.client.create_network(net_name, *args, **kwargs)['Id'] + self.tmp_networks.append(net_id) + return (net_name, net_id) + + @requires_api_version('1.21') + def test_list_networks(self): + networks = self.client.networks() + initial_size = len(networks) + + net_name, net_id = self.create_network() + + networks = self.client.networks() + self.assertEqual(len(networks), initial_size + 1) + self.assertTrue(net_id in [n['Id'] for n in networks]) + + networks_by_name = self.client.networks(names=[net_name]) + self.assertEqual([n['Id'] for n in networks_by_name], [net_id]) + + networks_by_partial_id = self.client.networks(ids=[net_id[:8]]) + self.assertEqual([n['Id'] for n in networks_by_partial_id], [net_id]) + + @requires_api_version('1.21') + def test_inspect_network(self): + net_name, net_id = self.create_network() + + net = self.client.inspect_network(net_id) + self.assertEqual(net['Id'], net_id) + self.assertEqual(net['Name'], net_name) + self.assertEqual(net['Driver'], 'bridge') + self.assertEqual(net['Scope'], 'local') + self.assertEqual(net['IPAM']['Driver'], 'default') + + @requires_api_version('1.21') + def test_create_network_with_ipam_config(self): + _, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[ + create_ipam_pool( + subnet="172.28.0.0/16", + iprange="172.28.5.0/24", + gateway="172.28.5.254", + aux_addresses={ + "a": "172.28.1.5", + "b": "172.28.1.6", + "c": "172.28.1.7", + }, + ), + ], + ), + ) + + net = self.client.inspect_network(net_id) + ipam = net['IPAM'] + + assert ipam.pop('Options', None) is None + + assert ipam == { + 'Driver': 'default', + 'Config': [{ + 'Subnet': "172.28.0.0/16", + 'IPRange': "172.28.5.0/24", + 'Gateway': "172.28.5.254", + 'AuxiliaryAddresses': { + "a": "172.28.1.5", + "b": "172.28.1.6", + "c": "172.28.1.7", + }, + }], + } + + @requires_api_version('1.21') + def test_create_network_with_host_driver_fails(self): + net_name = 'dockerpy{}'.format(random.getrandbits(24))[:14] + + with pytest.raises(docker.errors.APIError): + self.client.create_network(net_name, driver='host') + + @requires_api_version('1.21') + def test_remove_network(self): + initial_size = len(self.client.networks()) + + net_name, net_id = self.create_network() + self.assertEqual(len(self.client.networks()), initial_size + 1) + + self.client.remove_network(net_id) + self.assertEqual(len(self.client.networks()), initial_size) + + @requires_api_version('1.21') + def test_connect_and_disconnect_container(self): + net_name, net_id = self.create_network() + + container = self.client.create_container('busybox', 'top') + self.tmp_containers.append(container) + self.client.start(container) + + network_data = self.client.inspect_network(net_id) + self.assertFalse(network_data.get('Containers')) + + self.client.connect_container_to_network(container, net_id) + network_data = self.client.inspect_network(net_id) + self.assertEqual( + list(network_data['Containers'].keys()), + [container['Id']]) + + with pytest.raises(docker.errors.APIError): + self.client.connect_container_to_network(container, net_id) + + self.client.disconnect_container_from_network(container, net_id) + network_data = self.client.inspect_network(net_id) + self.assertFalse(network_data.get('Containers')) + + with pytest.raises(docker.errors.APIError): + self.client.disconnect_container_from_network(container, net_id) + + @requires_api_version('1.22') + def test_connect_with_aliases(self): + net_name, net_id = self.create_network() + + container = self.client.create_container('busybox', 'top') + self.tmp_containers.append(container) + self.client.start(container) + + self.client.connect_container_to_network( + container, net_id, aliases=['foo', 'bar']) + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Aliases'], + ['foo', 'bar']) + + @requires_api_version('1.21') + def test_connect_on_container_create(self): + net_name, net_id = self.create_network() + + container = self.client.create_container( + image='busybox', + command='top', + host_config=self.client.create_host_config(network_mode=net_name), + ) + self.tmp_containers.append(container) + self.client.start(container) + + network_data = self.client.inspect_network(net_id) + self.assertEqual( + list(network_data['Containers'].keys()), + [container['Id']]) + + self.client.disconnect_container_from_network(container, net_id) + network_data = self.client.inspect_network(net_id) + self.assertFalse(network_data.get('Containers')) + + @requires_api_version('1.22') + def test_create_with_aliases(self): + net_name, net_id = self.create_network() + + container = self.client.create_container( + image='busybox', + command='top', + host_config=self.client.create_host_config( + network_mode=net_name, + ), + networking_config=self.client.create_networking_config({ + net_name: self.client.create_endpoint_config( + aliases=['foo', 'bar'], + ), + }), + ) + self.tmp_containers.append(container) + self.client.start(container) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Aliases'], + ['foo', 'bar']) + + @requires_api_version('1.22') + def test_create_with_links(self): + net_name, net_id = self.create_network() + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name), + networking_config=self.client.create_networking_config({ + net_name: self.client.create_endpoint_config( + links=[('docker-py-test-upstream', 'bar')], + ), + }), + ) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Links'], + ['docker-py-test-upstream:bar']) + + self.create_and_start( + name='docker-py-test-upstream', + host_config=self.client.create_host_config(network_mode=net_name), + ) + + self.execute(container, ['nslookup', 'bar']) + + @requires_api_version('1.21') + def test_create_check_duplicate(self): + net_name, net_id = self.create_network() + with self.assertRaises(docker.errors.APIError): + self.client.create_network(net_name, check_duplicate=True) + self.client.create_network(net_name, check_duplicate=False) + + @requires_api_version('1.22') + def test_connect_with_links(self): + net_name, net_id = self.create_network() + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name)) + + self.client.disconnect_container_from_network(container, net_name) + self.client.connect_container_to_network( + container, net_name, + links=[('docker-py-test-upstream', 'bar')]) + + container_data = self.client.inspect_container(container) + self.assertEqual( + container_data['NetworkSettings']['Networks'][net_name]['Links'], + ['docker-py-test-upstream:bar']) + + self.create_and_start( + name='docker-py-test-upstream', + host_config=self.client.create_host_config(network_mode=net_name), + ) + + self.execute(container, ['nslookup', 'bar']) + + @requires_api_version('1.22') + def test_connect_with_ipv4_address(self): + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[ + create_ipam_pool( + subnet="172.28.0.0/16", iprange="172.28.5.0/24", + gateway="172.28.5.254" + ) + ] + ) + ) + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name)) + + self.client.disconnect_container_from_network(container, net_name) + self.client.connect_container_to_network( + container, net_name, ipv4_address='172.28.5.24' + ) + + container_data = self.client.inspect_container(container) + net_data = container_data['NetworkSettings']['Networks'][net_name] + self.assertEqual( + net_data['IPAMConfig']['IPv4Address'], '172.28.5.24' + ) + + @requires_api_version('1.22') + def test_connect_with_ipv6_address(self): + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[ + create_ipam_pool( + subnet="2001:389::1/64", iprange="2001:389::0/96", + gateway="2001:389::ffff" + ) + ] + ) + ) + + container = self.create_and_start( + host_config=self.client.create_host_config(network_mode=net_name)) + + self.client.disconnect_container_from_network(container, net_name) + self.client.connect_container_to_network( + container, net_name, ipv6_address='2001:389::f00d' + ) + + container_data = self.client.inspect_container(container) + net_data = container_data['NetworkSettings']['Networks'][net_name] + self.assertEqual( + net_data['IPAMConfig']['IPv6Address'], '2001:389::f00d' + ) diff -Nru python-docker-1.5.0/tests/integration/regression_test.py python-docker-1.8.0/tests/integration/regression_test.py --- python-docker-1.5.0/tests/integration/regression_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/regression_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,69 @@ +import io +import random + +import docker +import six + +from .. import helpers + +BUSYBOX = helpers.BUSYBOX + + +class TestRegressions(helpers.BaseTestCase): + def test_443_handle_nonchunked_response_in_stream(self): + dfile = io.BytesIO() + with self.assertRaises(docker.errors.APIError) as exc: + for line in self.client.build(fileobj=dfile, tag="a/b/c"): + pass + self.assertEqual(exc.exception.response.status_code, 500) + dfile.close() + + def test_542_truncate_ids_client_side(self): + self.client.start( + self.client.create_container(BUSYBOX, ['true']) + ) + result = self.client.containers(all=True, trunc=True) + self.assertEqual(len(result[0]['Id']), 12) + + def test_647_support_doubleslash_in_image_names(self): + with self.assertRaises(docker.errors.APIError): + self.client.inspect_image('gensokyo.jp//kirisame') + + def test_649_handle_timeout_value_none(self): + self.client.timeout = None + ctnr = self.client.create_container(BUSYBOX, ['sleep', '2']) + self.client.start(ctnr) + self.client.stop(ctnr) + + def test_715_handle_user_param_as_int_value(self): + ctnr = self.client.create_container(BUSYBOX, ['id', '-u'], user=1000) + self.client.start(ctnr) + self.client.wait(ctnr) + logs = self.client.logs(ctnr) + if six.PY3: + logs = logs.decode('utf-8') + assert logs == '1000\n' + + def test_792_explicit_port_protocol(self): + + tcp_port, udp_port = random.sample(range(9999, 32000), 2) + ctnr = self.client.create_container( + BUSYBOX, ['sleep', '9999'], ports=[2000, (2000, 'udp')], + host_config=self.client.create_host_config( + port_bindings={'2000/tcp': tcp_port, '2000/udp': udp_port} + ) + ) + self.tmp_containers.append(ctnr) + self.client.start(ctnr) + self.assertEqual( + self.client.port(ctnr, 2000)[0]['HostPort'], + six.text_type(tcp_port) + ) + self.assertEqual( + self.client.port(ctnr, '2000/tcp')[0]['HostPort'], + six.text_type(tcp_port) + ) + self.assertEqual( + self.client.port(ctnr, '2000/udp')[0]['HostPort'], + six.text_type(udp_port) + ) diff -Nru python-docker-1.5.0/tests/integration/volume_test.py python-docker-1.8.0/tests/integration/volume_test.py --- python-docker-1.5.0/tests/integration/volume_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration/volume_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,55 @@ +import docker +import pytest + +from .. import helpers +from ..base import requires_api_version + + +@requires_api_version('1.21') +class TestVolumes(helpers.BaseTestCase): + def test_create_volume(self): + name = 'perfectcherryblossom' + self.tmp_volumes.append(name) + result = self.client.create_volume(name) + self.assertIn('Name', result) + self.assertEqual(result['Name'], name) + self.assertIn('Driver', result) + self.assertEqual(result['Driver'], 'local') + + def test_create_volume_invalid_driver(self): + driver_name = 'invalid.driver' + + with pytest.raises(docker.errors.NotFound): + self.client.create_volume('perfectcherryblossom', driver_name) + + def test_list_volumes(self): + name = 'imperishablenight' + self.tmp_volumes.append(name) + volume_info = self.client.create_volume(name) + result = self.client.volumes() + self.assertIn('Volumes', result) + volumes = result['Volumes'] + self.assertIn(volume_info, volumes) + + def test_inspect_volume(self): + name = 'embodimentofscarletdevil' + self.tmp_volumes.append(name) + volume_info = self.client.create_volume(name) + result = self.client.inspect_volume(name) + self.assertEqual(volume_info, result) + + def test_inspect_nonexistent_volume(self): + name = 'embodimentofscarletdevil' + with pytest.raises(docker.errors.NotFound): + self.client.inspect_volume(name) + + def test_remove_volume(self): + name = 'shootthebullet' + self.tmp_volumes.append(name) + self.client.create_volume(name) + self.client.remove_volume(name) + + def test_remove_nonexistent_volume(self): + name = 'shootthebullet' + with pytest.raises(docker.errors.NotFound): + self.client.remove_volume(name) diff -Nru python-docker-1.5.0/tests/integration_test.py python-docker-1.8.0/tests/integration_test.py --- python-docker-1.5.0/tests/integration_test.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/integration_test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,1971 +0,0 @@ -# Copyright 2013 dotCloud inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import base64 -import contextlib -import json -import io -import os -import random -import shutil -import signal -import socket -import tarfile -import tempfile -import threading -import time -import unittest -import warnings - -import pytest -import six -from six.moves import BaseHTTPServer -from six.moves import socketserver - -import docker -from docker.errors import APIError, NotFound -from docker.utils import kwargs_from_env - -from . import helpers -from .base import requires_api_version -from .test import Cleanup - - -# FIXME: missing tests for -# export; history; insert; port; push; tag; get; load; stats - -warnings.simplefilter('error') -compare_version = docker.utils.compare_version - -EXEC_DRIVER = [] -BUSYBOX = 'busybox:buildroot-2014.02' - - -def exec_driver_is_native(): - global EXEC_DRIVER - if not EXEC_DRIVER: - c = docker_client() - EXEC_DRIVER = c.info()['ExecutionDriver'] - c.close() - return EXEC_DRIVER.startswith('native') - - -def docker_client(**kwargs): - return docker.Client(**docker_client_kwargs(**kwargs)) - - -def docker_client_kwargs(**kwargs): - client_kwargs = kwargs_from_env(assert_hostname=False) - client_kwargs.update(kwargs) - return client_kwargs - - -def setup_module(): - c = docker_client() - try: - c.inspect_image(BUSYBOX) - except NotFound: - c.pull(BUSYBOX) - c.inspect_image(BUSYBOX) - c.close() - - -class BaseTestCase(unittest.TestCase): - tmp_imgs = [] - tmp_containers = [] - tmp_folders = [] - tmp_volumes = [] - - def setUp(self): - if six.PY2: - self.assertRegex = self.assertRegexpMatches - self.assertCountEqual = self.assertItemsEqual - self.client = docker_client(timeout=60) - self.tmp_imgs = [] - self.tmp_containers = [] - self.tmp_folders = [] - self.tmp_volumes = [] - self.tmp_networks = [] - - def tearDown(self): - for img in self.tmp_imgs: - try: - self.client.remove_image(img) - except docker.errors.APIError: - pass - for container in self.tmp_containers: - try: - self.client.stop(container, timeout=1) - self.client.remove_container(container) - except docker.errors.APIError: - pass - for network in self.tmp_networks: - try: - self.client.remove_network(network) - except docker.errors.APIError: - pass - for folder in self.tmp_folders: - shutil.rmtree(folder) - - for volume in self.tmp_volumes: - try: - self.client.remove_volume(volume) - except docker.errors.APIError: - pass - - self.client.close() - - def run_container(self, *args, **kwargs): - container = self.client.create_container(*args, **kwargs) - self.tmp_containers.append(container) - self.client.start(container) - exitcode = self.client.wait(container) - - if exitcode != 0: - output = self.client.logs(container) - raise Exception( - "Container exited with code {}:\n{}" - .format(exitcode, output)) - - return container - - -######################### -# INFORMATION TESTS # -######################### - - -class TestVersion(BaseTestCase): - def runTest(self): - res = self.client.version() - self.assertIn('GoVersion', res) - self.assertIn('Version', res) - self.assertEqual(len(res['Version'].split('.')), 3) - - -class TestInfo(BaseTestCase): - def runTest(self): - res = self.client.info() - self.assertIn('Containers', res) - self.assertIn('Images', res) - self.assertIn('Debug', res) - - -class TestSearch(BaseTestCase): - def runTest(self): - self.client = docker_client(timeout=10) - res = self.client.search('busybox') - self.assertTrue(len(res) >= 1) - base_img = [x for x in res if x['name'] == 'busybox'] - self.assertEqual(len(base_img), 1) - self.assertIn('description', base_img[0]) - -################### -# LISTING TESTS # -################### - - -class TestImages(BaseTestCase): - def runTest(self): - res1 = self.client.images(all=True) - self.assertIn('Id', res1[0]) - res10 = res1[0] - self.assertIn('Created', res10) - self.assertIn('RepoTags', res10) - distinct = [] - for img in res1: - if img['Id'] not in distinct: - distinct.append(img['Id']) - self.assertEqual(len(distinct), self.client.info()['Images']) - - -class TestImageIds(BaseTestCase): - def runTest(self): - res1 = self.client.images(quiet=True) - self.assertEqual(type(res1[0]), six.text_type) - - -class TestListContainers(BaseTestCase): - def runTest(self): - res0 = self.client.containers(all=True) - size = len(res0) - res1 = self.client.create_container(BUSYBOX, 'true') - self.assertIn('Id', res1) - self.client.start(res1['Id']) - self.tmp_containers.append(res1['Id']) - res2 = self.client.containers(all=True) - self.assertEqual(size + 1, len(res2)) - retrieved = [x for x in res2 if x['Id'].startswith(res1['Id'])] - self.assertEqual(len(retrieved), 1) - retrieved = retrieved[0] - self.assertIn('Command', retrieved) - self.assertEqual(retrieved['Command'], six.text_type('true')) - self.assertIn('Image', retrieved) - self.assertRegex(retrieved['Image'], r'busybox:.*') - self.assertIn('Status', retrieved) - -##################### -# CONTAINER TESTS # -##################### - - -class TestCreateContainer(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, 'true') - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - - -class TestCreateContainerWithBinds(BaseTestCase): - def setUp(self): - super(TestCreateContainerWithBinds, self).setUp() - - self.mount_dest = '/mnt' - - # Get a random pathname - we don't need it to exist locally - self.mount_origin = tempfile.mkdtemp() - shutil.rmtree(self.mount_origin) - - self.filename = 'shared.txt' - - self.run_with_volume( - False, - BUSYBOX, - ['touch', os.path.join(self.mount_dest, self.filename)], - ) - - def run_with_volume(self, ro, *args, **kwargs): - return self.run_container( - *args, - volumes={self.mount_dest: {}}, - host_config=self.client.create_host_config( - binds={ - self.mount_origin: { - 'bind': self.mount_dest, - 'ro': ro, - }, - }, - network_mode='none' - ), - **kwargs - ) - - def test_rw(self): - container = self.run_with_volume( - False, - BUSYBOX, - ['ls', self.mount_dest], - ) - logs = self.client.logs(container) - - if six.PY3: - logs = logs.decode('utf-8') - self.assertIn(self.filename, logs) - inspect_data = self.client.inspect_container(container) - self.check_container_data(inspect_data, True) - - def test_ro(self): - container = self.run_with_volume( - True, - BUSYBOX, - ['ls', self.mount_dest], - ) - logs = self.client.logs(container) - - if six.PY3: - logs = logs.decode('utf-8') - self.assertIn(self.filename, logs) - - inspect_data = self.client.inspect_container(container) - self.check_container_data(inspect_data, False) - - def check_container_data(self, inspect_data, rw): - if docker.utils.compare_version('1.20', self.client._version) < 0: - self.assertIn('Volumes', inspect_data) - self.assertIn(self.mount_dest, inspect_data['Volumes']) - self.assertEqual( - self.mount_origin, inspect_data['Volumes'][self.mount_dest] - ) - self.assertIn(self.mount_dest, inspect_data['VolumesRW']) - self.assertFalse(inspect_data['VolumesRW'][self.mount_dest]) - else: - self.assertIn('Mounts', inspect_data) - filtered = list(filter( - lambda x: x['Destination'] == self.mount_dest, - inspect_data['Mounts'] - )) - self.assertEqual(len(filtered), 1) - mount_data = filtered[0] - self.assertEqual(mount_data['Source'], self.mount_origin) - self.assertEqual(mount_data['RW'], rw) - - -@requires_api_version('1.20') -class CreateContainerWithGroupAddTest(BaseTestCase): - def test_group_id_ints(self): - container = self.client.create_container( - BUSYBOX, 'id -G', - host_config=self.client.create_host_config(group_add=[1000, 1001]) - ) - self.tmp_containers.append(container) - self.client.start(container) - self.client.wait(container) - - logs = self.client.logs(container) - if six.PY3: - logs = logs.decode('utf-8') - groups = logs.strip().split(' ') - self.assertIn('1000', groups) - self.assertIn('1001', groups) - - def test_group_id_strings(self): - container = self.client.create_container( - BUSYBOX, 'id -G', host_config=self.client.create_host_config( - group_add=['1000', '1001'] - ) - ) - self.tmp_containers.append(container) - self.client.start(container) - self.client.wait(container) - - logs = self.client.logs(container) - if six.PY3: - logs = logs.decode('utf-8') - - groups = logs.strip().split(' ') - self.assertIn('1000', groups) - self.assertIn('1001', groups) - - -class CreateContainerWithLogConfigTest(BaseTestCase): - def test_valid_log_driver_and_log_opt(self): - log_config = docker.utils.LogConfig( - type='json-file', - config={'max-file': '100'} - ) - - container = self.client.create_container( - BUSYBOX, ['true'], - host_config=self.client.create_host_config(log_config=log_config) - ) - self.tmp_containers.append(container['Id']) - self.client.start(container) - - info = self.client.inspect_container(container) - container_log_config = info['HostConfig']['LogConfig'] - - self.assertEqual(container_log_config['Type'], log_config.type) - self.assertEqual(container_log_config['Config'], log_config.config) - - def test_invalid_log_driver_raises_exception(self): - log_config = docker.utils.LogConfig( - type='asdf-nope', - config={} - ) - - container = self.client.create_container( - BUSYBOX, ['true'], - host_config=self.client.create_host_config(log_config=log_config) - ) - - expected_msg = "logger: no log driver named 'asdf-nope' is registered" - - with pytest.raises(APIError) as excinfo: - # raises an internal server error 500 - self.client.start(container) - - assert expected_msg in str(excinfo.value) - - @pytest.mark.skipif(True, - reason="https://github.com/docker/docker/issues/15633") - def test_valid_no_log_driver_specified(self): - log_config = docker.utils.LogConfig( - type="", - config={'max-file': '100'} - ) - - container = self.client.create_container( - BUSYBOX, ['true'], - host_config=self.client.create_host_config(log_config=log_config) - ) - self.tmp_containers.append(container['Id']) - self.client.start(container) - - info = self.client.inspect_container(container) - container_log_config = info['HostConfig']['LogConfig'] - - self.assertEqual(container_log_config['Type'], "json-file") - self.assertEqual(container_log_config['Config'], log_config.config) - - def test_valid_no_config_specified(self): - log_config = docker.utils.LogConfig( - type="json-file", - config=None - ) - - container = self.client.create_container( - BUSYBOX, ['true'], - host_config=self.client.create_host_config(log_config=log_config) - ) - self.tmp_containers.append(container['Id']) - self.client.start(container) - - info = self.client.inspect_container(container) - container_log_config = info['HostConfig']['LogConfig'] - - self.assertEqual(container_log_config['Type'], "json-file") - self.assertEqual(container_log_config['Config'], {}) - - -@requires_api_version('1.20') -class GetArchiveTest(BaseTestCase): - def test_get_file_archive_from_container(self): - data = 'The Maid and the Pocket Watch of Blood' - ctnr = self.client.create_container( - BUSYBOX, 'sh -c "echo {0} > /vol1/data.txt"'.format(data), - volumes=['/vol1'] - ) - self.tmp_containers.append(ctnr) - self.client.start(ctnr) - self.client.wait(ctnr) - with tempfile.NamedTemporaryFile() as destination: - strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') - for d in strm: - destination.write(d) - destination.seek(0) - retrieved_data = helpers.untar_file(destination, 'data.txt') - if six.PY3: - retrieved_data = retrieved_data.decode('utf-8') - self.assertEqual(data, retrieved_data.strip()) - - def test_get_file_stat_from_container(self): - data = 'The Maid and the Pocket Watch of Blood' - ctnr = self.client.create_container( - BUSYBOX, 'sh -c "echo -n {0} > /vol1/data.txt"'.format(data), - volumes=['/vol1'] - ) - self.tmp_containers.append(ctnr) - self.client.start(ctnr) - self.client.wait(ctnr) - strm, stat = self.client.get_archive(ctnr, '/vol1/data.txt') - self.assertIn('name', stat) - self.assertEqual(stat['name'], 'data.txt') - self.assertIn('size', stat) - self.assertEqual(stat['size'], len(data)) - - -@requires_api_version('1.20') -class PutArchiveTest(BaseTestCase): - def test_copy_file_to_container(self): - data = b'Deaf To All But The Song' - with tempfile.NamedTemporaryFile() as test_file: - test_file.write(data) - test_file.seek(0) - ctnr = self.client.create_container( - BUSYBOX, - 'cat {0}'.format( - os.path.join('/vol1', os.path.basename(test_file.name)) - ), - volumes=['/vol1'] - ) - self.tmp_containers.append(ctnr) - with helpers.simple_tar(test_file.name) as test_tar: - self.client.put_archive(ctnr, '/vol1', test_tar) - self.client.start(ctnr) - self.client.wait(ctnr) - logs = self.client.logs(ctnr) - if six.PY3: - logs = logs.decode('utf-8') - data = data.decode('utf-8') - self.assertEqual(logs.strip(), data) - - def test_copy_directory_to_container(self): - files = ['a.py', 'b.py', 'foo/b.py'] - dirs = ['foo', 'bar'] - base = helpers.make_tree(dirs, files) - ctnr = self.client.create_container( - BUSYBOX, 'ls -p /vol1', volumes=['/vol1'] - ) - self.tmp_containers.append(ctnr) - with docker.utils.tar(base) as test_tar: - self.client.put_archive(ctnr, '/vol1', test_tar) - self.client.start(ctnr) - self.client.wait(ctnr) - logs = self.client.logs(ctnr) - if six.PY3: - logs = logs.decode('utf-8') - results = logs.strip().split() - self.assertIn('a.py', results) - self.assertIn('b.py', results) - self.assertIn('foo/', results) - self.assertIn('bar/', results) - - -class TestCreateContainerReadOnlyFs(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - ctnr = self.client.create_container( - BUSYBOX, ['mkdir', '/shrine'], - host_config=self.client.create_host_config( - read_only=True, network_mode='none' - ) - ) - self.assertIn('Id', ctnr) - self.tmp_containers.append(ctnr['Id']) - self.client.start(ctnr) - res = self.client.wait(ctnr) - self.assertNotEqual(res, 0) - - -class TestCreateContainerWithName(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, 'true', name='foobar') - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - inspect = self.client.inspect_container(res['Id']) - self.assertIn('Name', inspect) - self.assertEqual('/foobar', inspect['Name']) - - -class TestRenameContainer(BaseTestCase): - def runTest(self): - version = self.client.version()['Version'] - name = 'hong_meiling' - res = self.client.create_container(BUSYBOX, 'true') - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - self.client.rename(res, name) - inspect = self.client.inspect_container(res['Id']) - self.assertIn('Name', inspect) - if version == '1.5.0': - self.assertEqual(name, inspect['Name']) - else: - self.assertEqual('/{0}'.format(name), inspect['Name']) - - -class TestStartContainer(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, 'true') - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - self.client.start(res['Id']) - inspect = self.client.inspect_container(res['Id']) - self.assertIn('Config', inspect) - self.assertIn('Id', inspect) - self.assertTrue(inspect['Id'].startswith(res['Id'])) - self.assertIn('Image', inspect) - self.assertIn('State', inspect) - self.assertIn('Running', inspect['State']) - if not inspect['State']['Running']: - self.assertIn('ExitCode', inspect['State']) - self.assertEqual(inspect['State']['ExitCode'], 0) - - -class TestStartContainerWithDictInsteadOfId(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, 'true') - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - self.client.start(res) - inspect = self.client.inspect_container(res['Id']) - self.assertIn('Config', inspect) - self.assertIn('Id', inspect) - self.assertTrue(inspect['Id'].startswith(res['Id'])) - self.assertIn('Image', inspect) - self.assertIn('State', inspect) - self.assertIn('Running', inspect['State']) - if not inspect['State']['Running']: - self.assertIn('ExitCode', inspect['State']) - self.assertEqual(inspect['State']['ExitCode'], 0) - - -class TestCreateContainerPrivileged(BaseTestCase): - def runTest(self): - res = self.client.create_container( - BUSYBOX, 'true', host_config=self.client.create_host_config( - privileged=True, network_mode='none' - ) - ) - self.assertIn('Id', res) - self.tmp_containers.append(res['Id']) - self.client.start(res['Id']) - inspect = self.client.inspect_container(res['Id']) - self.assertIn('Config', inspect) - self.assertIn('Id', inspect) - self.assertTrue(inspect['Id'].startswith(res['Id'])) - self.assertIn('Image', inspect) - self.assertIn('State', inspect) - self.assertIn('Running', inspect['State']) - if not inspect['State']['Running']: - self.assertIn('ExitCode', inspect['State']) - self.assertEqual(inspect['State']['ExitCode'], 0) - # Since Nov 2013, the Privileged flag is no longer part of the - # container's config exposed via the API (safety concerns?). - # - if 'Privileged' in inspect['Config']: - self.assertEqual(inspect['Config']['Privileged'], True) - - -class TestWait(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, ['sleep', '3']) - id = res['Id'] - self.tmp_containers.append(id) - self.client.start(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - inspect = self.client.inspect_container(id) - self.assertIn('Running', inspect['State']) - self.assertEqual(inspect['State']['Running'], False) - self.assertIn('ExitCode', inspect['State']) - self.assertEqual(inspect['State']['ExitCode'], exitcode) - - -class TestWaitWithDictInsteadOfId(BaseTestCase): - def runTest(self): - res = self.client.create_container(BUSYBOX, ['sleep', '3']) - id = res['Id'] - self.tmp_containers.append(id) - self.client.start(res) - exitcode = self.client.wait(res) - self.assertEqual(exitcode, 0) - inspect = self.client.inspect_container(res) - self.assertIn('Running', inspect['State']) - self.assertEqual(inspect['State']['Running'], False) - self.assertIn('ExitCode', inspect['State']) - self.assertEqual(inspect['State']['ExitCode'], exitcode) - - -class TestLogs(BaseTestCase): - def runTest(self): - snippet = 'Flowering Nights (Sakuya Iyazoi)' - container = self.client.create_container( - BUSYBOX, 'echo {0}'.format(snippet) - ) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - logs = self.client.logs(id) - self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) - - -class TestLogsWithTailOption(BaseTestCase): - def runTest(self): - snippet = '''Line1 -Line2''' - container = self.client.create_container( - BUSYBOX, 'echo "{0}"'.format(snippet) - ) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - logs = self.client.logs(id, tail=1) - self.assertEqual(logs, ('Line2\n').encode(encoding='ascii')) - - -# class TestLogsStreaming(BaseTestCase): -# def runTest(self): -# snippet = 'Flowering Nights (Sakuya Iyazoi)' -# container = self.client.create_container( -# BUSYBOX, 'echo {0}'.format(snippet) -# ) -# id = container['Id'] -# self.client.start(id) -# self.tmp_containers.append(id) -# logs = bytes() if six.PY3 else str() -# for chunk in self.client.logs(id, stream=True): -# logs += chunk - -# exitcode = self.client.wait(id) -# self.assertEqual(exitcode, 0) - -# self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) - - -class TestLogsWithDictInsteadOfId(BaseTestCase): - def runTest(self): - snippet = 'Flowering Nights (Sakuya Iyazoi)' - container = self.client.create_container( - BUSYBOX, 'echo {0}'.format(snippet) - ) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - logs = self.client.logs(container) - self.assertEqual(logs, (snippet + '\n').encode(encoding='ascii')) - - -class TestDiff(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - diff = self.client.diff(id) - test_diff = [x for x in diff if x.get('Path', None) == '/test'] - self.assertEqual(len(test_diff), 1) - self.assertIn('Kind', test_diff[0]) - self.assertEqual(test_diff[0]['Kind'], 1) - - -class TestDiffWithDictInsteadOfId(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0) - diff = self.client.diff(container) - test_diff = [x for x in diff if x.get('Path', None) == '/test'] - self.assertEqual(len(test_diff), 1) - self.assertIn('Kind', test_diff[0]) - self.assertEqual(test_diff[0]['Kind'], 1) - - -class TestStop(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - self.client.stop(id, timeout=2) - container_info = self.client.inspect_container(id) - self.assertIn('State', container_info) - state = container_info['State'] - self.assertIn('ExitCode', state) - if exec_driver_is_native(): - self.assertNotEqual(state['ExitCode'], 0) - self.assertIn('Running', state) - self.assertEqual(state['Running'], False) - - -class TestStopWithDictInsteadOfId(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - self.assertIn('Id', container) - id = container['Id'] - self.client.start(container) - self.tmp_containers.append(id) - self.client.stop(container, timeout=2) - container_info = self.client.inspect_container(id) - self.assertIn('State', container_info) - state = container_info['State'] - self.assertIn('ExitCode', state) - if exec_driver_is_native(): - self.assertNotEqual(state['ExitCode'], 0) - self.assertIn('Running', state) - self.assertEqual(state['Running'], False) - - -class TestKill(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - self.client.kill(id) - container_info = self.client.inspect_container(id) - self.assertIn('State', container_info) - state = container_info['State'] - self.assertIn('ExitCode', state) - if exec_driver_is_native(): - self.assertNotEqual(state['ExitCode'], 0) - self.assertIn('Running', state) - self.assertEqual(state['Running'], False) - - -class TestKillWithDictInsteadOfId(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - self.client.kill(container) - container_info = self.client.inspect_container(id) - self.assertIn('State', container_info) - state = container_info['State'] - self.assertIn('ExitCode', state) - if exec_driver_is_native(): - self.assertNotEqual(state['ExitCode'], 0) - self.assertIn('Running', state) - self.assertEqual(state['Running'], False) - - -class TestKillWithSignal(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '60']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - self.client.kill(id, signal=signal.SIGKILL) - exitcode = self.client.wait(id) - self.assertNotEqual(exitcode, 0) - container_info = self.client.inspect_container(id) - self.assertIn('State', container_info) - state = container_info['State'] - self.assertIn('ExitCode', state) - self.assertNotEqual(state['ExitCode'], 0) - self.assertIn('Running', state) - self.assertEqual(state['Running'], False, state) - - -class TestPort(BaseTestCase): - def runTest(self): - - port_bindings = { - '1111': ('127.0.0.1', '4567'), - '2222': ('127.0.0.1', '4568') - } - - container = self.client.create_container( - BUSYBOX, ['sleep', '60'], ports=list(port_bindings.keys()), - host_config=self.client.create_host_config( - port_bindings=port_bindings, network_mode='bridge' - ) - ) - id = container['Id'] - - self.client.start(container) - - # Call the port function on each biding and compare expected vs actual - for port in port_bindings: - actual_bindings = self.client.port(container, port) - port_binding = actual_bindings.pop() - - ip, host_port = port_binding['HostIp'], port_binding['HostPort'] - - self.assertEqual(ip, port_bindings[port][0]) - self.assertEqual(host_port, port_bindings[port][1]) - - self.client.kill(id) - - -class TestMacAddress(BaseTestCase): - def runTest(self): - mac_address_expected = "02:42:ac:11:00:0a" - container = self.client.create_container( - BUSYBOX, ['sleep', '60'], mac_address=mac_address_expected) - - id = container['Id'] - - self.client.start(container) - res = self.client.inspect_container(container['Id']) - self.assertEqual(mac_address_expected, - res['NetworkSettings']['MacAddress']) - - self.client.kill(id) - - -class TestContainerTop(BaseTestCase): - def runTest(self): - container = self.client.create_container( - BUSYBOX, ['sleep', '60']) - - id = container['Id'] - - self.client.start(container) - res = self.client.top(container['Id']) - print(res) - self.assertEqual( - res['Titles'], - ['UID', 'PID', 'PPID', 'C', 'STIME', 'TTY', 'TIME', 'CMD'] - ) - self.assertEqual(len(res['Processes']), 1) - self.assertEqual(res['Processes'][0][7], 'sleep 60') - self.client.kill(id) - - -class TestContainerTopWithPsArgs(BaseTestCase): - def runTest(self): - container = self.client.create_container( - BUSYBOX, ['sleep', '60']) - - id = container['Id'] - - self.client.start(container) - res = self.client.top(container['Id'], 'waux') - self.assertEqual( - res['Titles'], - ['USER', 'PID', '%CPU', '%MEM', 'VSZ', 'RSS', - 'TTY', 'STAT', 'START', 'TIME', 'COMMAND'], - ) - self.assertEqual(len(res['Processes']), 1) - self.assertEqual(res['Processes'][0][10], 'sleep 60') - self.client.kill(id) - - -class TestRestart(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - info = self.client.inspect_container(id) - self.assertIn('State', info) - self.assertIn('StartedAt', info['State']) - start_time1 = info['State']['StartedAt'] - self.client.restart(id, timeout=2) - info2 = self.client.inspect_container(id) - self.assertIn('State', info2) - self.assertIn('StartedAt', info2['State']) - start_time2 = info2['State']['StartedAt'] - self.assertNotEqual(start_time1, start_time2) - self.assertIn('Running', info2['State']) - self.assertEqual(info2['State']['Running'], True) - self.client.kill(id) - - -class TestRestartWithDictInsteadOfId(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - self.assertIn('Id', container) - id = container['Id'] - self.client.start(container) - self.tmp_containers.append(id) - info = self.client.inspect_container(id) - self.assertIn('State', info) - self.assertIn('StartedAt', info['State']) - start_time1 = info['State']['StartedAt'] - self.client.restart(container, timeout=2) - info2 = self.client.inspect_container(id) - self.assertIn('State', info2) - self.assertIn('StartedAt', info2['State']) - start_time2 = info2['State']['StartedAt'] - self.assertNotEqual(start_time1, start_time2) - self.assertIn('Running', info2['State']) - self.assertEqual(info2['State']['Running'], True) - self.client.kill(id) - - -class TestRemoveContainer(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['true']) - id = container['Id'] - self.client.start(id) - self.client.wait(id) - self.client.remove_container(id) - containers = self.client.containers(all=True) - res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] - self.assertEqual(len(res), 0) - - -class TestRemoveContainerWithDictInsteadOfId(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['true']) - id = container['Id'] - self.client.start(id) - self.client.wait(id) - self.client.remove_container(container) - containers = self.client.containers(all=True) - res = [x for x in containers if 'Id' in x and x['Id'].startswith(id)] - self.assertEqual(len(res), 0) - - -class TestCreateContainerWithVolumesFrom(BaseTestCase): - def runTest(self): - vol_names = ['foobar_vol0', 'foobar_vol1'] - - res0 = self.client.create_container( - BUSYBOX, 'true', name=vol_names[0] - ) - container1_id = res0['Id'] - self.tmp_containers.append(container1_id) - self.client.start(container1_id) - - res1 = self.client.create_container( - BUSYBOX, 'true', name=vol_names[1] - ) - container2_id = res1['Id'] - self.tmp_containers.append(container2_id) - self.client.start(container2_id) - with self.assertRaises(docker.errors.DockerException): - self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True, - volumes_from=vol_names - ) - res2 = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True, - host_config=self.client.create_host_config( - volumes_from=vol_names, network_mode='none' - ) - ) - container3_id = res2['Id'] - self.tmp_containers.append(container3_id) - self.client.start(container3_id) - - info = self.client.inspect_container(res2['Id']) - self.assertCountEqual(info['HostConfig']['VolumesFrom'], vol_names) - - -class TestCreateContainerWithLinks(BaseTestCase): - def runTest(self): - res0 = self.client.create_container( - BUSYBOX, 'cat', - detach=True, stdin_open=True, - environment={'FOO': '1'}) - - container1_id = res0['Id'] - self.tmp_containers.append(container1_id) - - self.client.start(container1_id) - - res1 = self.client.create_container( - BUSYBOX, 'cat', - detach=True, stdin_open=True, - environment={'FOO': '1'}) - - container2_id = res1['Id'] - self.tmp_containers.append(container2_id) - - self.client.start(container2_id) - - # we don't want the first / - link_path1 = self.client.inspect_container(container1_id)['Name'][1:] - link_alias1 = 'mylink1' - link_env_prefix1 = link_alias1.upper() - - link_path2 = self.client.inspect_container(container2_id)['Name'][1:] - link_alias2 = 'mylink2' - link_env_prefix2 = link_alias2.upper() - - res2 = self.client.create_container( - BUSYBOX, 'env', host_config=self.client.create_host_config( - links={link_path1: link_alias1, link_path2: link_alias2}, - network_mode='none' - ) - ) - container3_id = res2['Id'] - self.tmp_containers.append(container3_id) - self.client.start(container3_id) - self.assertEqual(self.client.wait(container3_id), 0) - - logs = self.client.logs(container3_id) - if six.PY3: - logs = logs.decode('utf-8') - self.assertIn('{0}_NAME='.format(link_env_prefix1), logs) - self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix1), logs) - self.assertIn('{0}_NAME='.format(link_env_prefix2), logs) - self.assertIn('{0}_ENV_FOO=1'.format(link_env_prefix2), logs) - - -class TestRestartingContainer(BaseTestCase): - def runTest(self): - container = self.client.create_container( - BUSYBOX, ['sleep', '2'], - host_config=self.client.create_host_config( - restart_policy={"Name": "always", "MaximumRetryCount": 0}, - network_mode='none' - ) - ) - id = container['Id'] - self.client.start(id) - self.client.wait(id) - with self.assertRaises(docker.errors.APIError) as exc: - self.client.remove_container(id) - err = exc.exception.response.text - self.assertIn( - 'You cannot remove a running container', err - ) - self.client.remove_container(id, force=True) - - -class TestExecuteCommand(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - 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.exec_create(id, ['echo', 'hello']) - self.assertIn('Id', res) - - exec_log = self.client.exec_start(res) - self.assertEqual(exec_log, b'hello\n') - - -class TestExecuteCommandString(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - 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.exec_create(id, 'echo hello world') - self.assertIn('Id', res) - - exec_log = self.client.exec_start(res) - self.assertEqual(exec_log, b'hello world\n') - - -class TestExecuteCommandStringAsUser(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - 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.exec_create(id, 'whoami', user='default') - self.assertIn('Id', res) - - exec_log = self.client.exec_start(res) - self.assertEqual(exec_log, b'default\n') - - -class TestExecuteCommandStringAsRoot(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - 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.exec_create(id, 'whoami') - self.assertIn('Id', res) - - exec_log = self.client.exec_start(res) - self.assertEqual(exec_log, b'root\n') - - -class TestExecuteCommandStreaming(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - container = self.client.create_container(BUSYBOX, 'cat', - detach=True, stdin_open=True) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - - exec_id = self.client.exec_create(id, ['echo', 'hello\nworld']) - self.assertIn('Id', exec_id) - - res = b'' - for chunk in self.client.exec_start(exec_id, stream=True): - res += chunk - self.assertEqual(res, b'hello\nworld\n') - - -class TestExecInspect(BaseTestCase): - def runTest(self): - if not exec_driver_is_native(): - pytest.skip('Exec driver not native') - - container = self.client.create_container(BUSYBOX, 'cat', - detach=True, stdin_open=True) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - - exec_id = self.client.exec_create(id, ['mkdir', '/does/not/exist']) - self.assertIn('Id', exec_id) - self.client.exec_start(exec_id) - exec_info = self.client.exec_inspect(exec_id) - self.assertIn('ExitCode', exec_info) - self.assertNotEqual(exec_info['ExitCode'], 0) - - -class TestRunContainerStreaming(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, '/bin/sh', - detach=True, stdin_open=True) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - sock = self.client.attach_socket(container, ws=False) - self.assertTrue(sock.fileno() > -1) - - -class TestPauseUnpauseContainer(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['sleep', '9999']) - id = container['Id'] - self.tmp_containers.append(id) - self.client.start(container) - 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) - - -class TestCreateContainerWithHostPidMode(BaseTestCase): - def runTest(self): - ctnr = self.client.create_container( - BUSYBOX, 'true', host_config=self.client.create_host_config( - pid_mode='host', network_mode='none' - ) - ) - self.assertIn('Id', ctnr) - self.tmp_containers.append(ctnr['Id']) - self.client.start(ctnr) - inspect = self.client.inspect_container(ctnr) - self.assertIn('HostConfig', inspect) - host_config = inspect['HostConfig'] - self.assertIn('PidMode', host_config) - self.assertEqual(host_config['PidMode'], 'host') - - -################# -# LINKS TESTS # -################# - - -class TestRemoveLink(BaseTestCase): - def runTest(self): - # Create containers - container1 = self.client.create_container( - BUSYBOX, 'cat', detach=True, stdin_open=True - ) - container1_id = container1['Id'] - self.tmp_containers.append(container1_id) - self.client.start(container1_id) - - # Create Link - # we don't want the first / - link_path = self.client.inspect_container(container1_id)['Name'][1:] - link_alias = 'mylink' - - container2 = self.client.create_container( - BUSYBOX, 'cat', host_config=self.client.create_host_config( - links={link_path: link_alias}, network_mode='none' - ) - ) - container2_id = container2['Id'] - self.tmp_containers.append(container2_id) - self.client.start(container2_id) - - # Remove link - linked_name = self.client.inspect_container(container2_id)['Name'][1:] - link_name = '%s/%s' % (linked_name, link_alias) - self.client.remove_container(link_name, link=True) - - # Link is gone - containers = self.client.containers(all=True) - retrieved = [x for x in containers if link_name in x['Names']] - self.assertEqual(len(retrieved), 0) - - # Containers are still there - retrieved = [ - x for x in containers if x['Id'].startswith(container1_id) or - x['Id'].startswith(container2_id) - ] - self.assertEqual(len(retrieved), 2) - -################## -# IMAGES TESTS # -################## - - -class TestPull(BaseTestCase): - def runTest(self): - try: - self.client.remove_image('hello-world') - except docker.errors.APIError: - pass - res = self.client.pull('hello-world') - self.tmp_imgs.append('hello-world') - self.assertEqual(type(res), six.text_type) - self.assertGreaterEqual( - len(self.client.images('hello-world')), 1 - ) - img_info = self.client.inspect_image('hello-world') - self.assertIn('Id', img_info) - - -class TestPullStream(BaseTestCase): - def runTest(self): - try: - self.client.remove_image('hello-world') - except docker.errors.APIError: - pass - stream = self.client.pull('hello-world', stream=True) - self.tmp_imgs.append('hello-world') - for chunk in stream: - if six.PY3: - chunk = chunk.decode('utf-8') - json.loads(chunk) # ensure chunk is a single, valid JSON blob - self.assertGreaterEqual( - len(self.client.images('hello-world')), 1 - ) - img_info = self.client.inspect_image('hello-world') - self.assertIn('Id', img_info) - - -class TestCommit(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - res = self.client.commit(id) - self.assertIn('Id', res) - img_id = res['Id'] - self.tmp_imgs.append(img_id) - img = self.client.inspect_image(img_id) - self.assertIn('Container', img) - self.assertTrue(img['Container'].startswith(id)) - self.assertIn('ContainerConfig', img) - self.assertIn('Image', img['ContainerConfig']) - self.assertEqual(BUSYBOX, img['ContainerConfig']['Image']) - busybox_id = self.client.inspect_image(BUSYBOX)['Id'] - self.assertIn('Parent', img) - self.assertEqual(img['Parent'], busybox_id) - - -class TestRemoveImage(BaseTestCase): - def runTest(self): - container = self.client.create_container(BUSYBOX, ['touch', '/test']) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - res = self.client.commit(id) - self.assertIn('Id', res) - img_id = res['Id'] - self.tmp_imgs.append(img_id) - self.client.remove_image(img_id, force=True) - images = self.client.images(all=True) - res = [x for x in images if x['Id'].startswith(img_id)] - self.assertEqual(len(res), 0) - - -################## -# IMPORT TESTS # -################## - - -class ImportTestCase(BaseTestCase): - '''Base class for `docker import` test cases.''' - - TAR_SIZE = 512 * 1024 - - def write_dummy_tar_content(self, n_bytes, tar_fd): - def extend_file(f, n_bytes): - f.seek(n_bytes - 1) - f.write(bytearray([65])) - f.seek(0) - - tar = tarfile.TarFile(fileobj=tar_fd, mode='w') - - with tempfile.NamedTemporaryFile() as f: - extend_file(f, n_bytes) - tarinfo = tar.gettarinfo(name=f.name, arcname='testdata') - tar.addfile(tarinfo, fileobj=f) - - tar.close() - - @contextlib.contextmanager - def dummy_tar_stream(self, n_bytes): - '''Yields a stream that is valid tar data of size n_bytes.''' - with tempfile.NamedTemporaryFile() as tar_file: - self.write_dummy_tar_content(n_bytes, tar_file) - tar_file.seek(0) - yield tar_file - - @contextlib.contextmanager - def dummy_tar_file(self, n_bytes): - '''Yields the name of a valid tar file of size n_bytes.''' - with tempfile.NamedTemporaryFile() as tar_file: - self.write_dummy_tar_content(n_bytes, tar_file) - tar_file.seek(0) - yield tar_file.name - - -class TestImportFromBytes(ImportTestCase): - '''Tests importing an image from in-memory byte data.''' - - def runTest(self): - with self.dummy_tar_stream(n_bytes=500) as f: - content = f.read() - - # The generic import_image() function cannot import in-memory bytes - # data that happens to be represented as a string type, because - # import_image() will try to use it as a filename and usually then - # trigger an exception. So we test the import_image_from_data() - # function instead. - statuses = self.client.import_image_from_data( - content, repository='test/import-from-bytes') - - result_text = statuses.splitlines()[-1] - result = json.loads(result_text) - - self.assertNotIn('error', result) - - img_id = result['status'] - self.tmp_imgs.append(img_id) - - -class TestImportFromFile(ImportTestCase): - '''Tests importing an image from a tar file on disk.''' - - def runTest(self): - with self.dummy_tar_file(n_bytes=self.TAR_SIZE) as tar_filename: - # statuses = self.client.import_image( - # src=tar_filename, repository='test/import-from-file') - statuses = self.client.import_image_from_file( - tar_filename, repository='test/import-from-file') - - result_text = statuses.splitlines()[-1] - result = json.loads(result_text) - - self.assertNotIn('error', result) - - self.assertIn('status', result) - img_id = result['status'] - self.tmp_imgs.append(img_id) - - -class TestImportFromStream(ImportTestCase): - '''Tests importing an image from a stream containing tar data.''' - - def runTest(self): - with self.dummy_tar_stream(n_bytes=self.TAR_SIZE) as tar_stream: - statuses = self.client.import_image( - src=tar_stream, repository='test/import-from-stream') - # statuses = self.client.import_image_from_stream( - # tar_stream, repository='test/import-from-stream') - result_text = statuses.splitlines()[-1] - result = json.loads(result_text) - - self.assertNotIn('error', result) - - self.assertIn('status', result) - img_id = result['status'] - self.tmp_imgs.append(img_id) - - -class TestImportFromURL(ImportTestCase): - '''Tests downloading an image over HTTP.''' - - @contextlib.contextmanager - def temporary_http_file_server(self, stream): - '''Serve data from an IO stream over HTTP.''' - - class Handler(BaseHTTPServer.BaseHTTPRequestHandler): - def do_GET(self): - self.send_response(200) - self.send_header('Content-Type', 'application/x-tar') - self.end_headers() - shutil.copyfileobj(stream, self.wfile) - - server = socketserver.TCPServer(('', 0), Handler) - thread = threading.Thread(target=server.serve_forever) - thread.setDaemon(True) - thread.start() - - yield 'http://%s:%s' % (socket.gethostname(), server.server_address[1]) - - server.shutdown() - - @pytest.mark.skipif(True, reason="Doesn't work inside a container - FIXME") - def runTest(self): - # The crappy test HTTP server doesn't handle large files well, so use - # a small file. - TAR_SIZE = 10240 - - with self.dummy_tar_stream(n_bytes=TAR_SIZE) as tar_data: - with self.temporary_http_file_server(tar_data) as url: - statuses = self.client.import_image( - src=url, repository='test/import-from-url') - - result_text = statuses.splitlines()[-1] - result = json.loads(result_text) - - self.assertNotIn('error', result) - - self.assertIn('status', result) - img_id = result['status'] - self.tmp_imgs.append(img_id) - - -################# -# VOLUMES TESTS # -################# - -@requires_api_version('1.21') -class TestVolumes(BaseTestCase): - def test_create_volume(self): - name = 'perfectcherryblossom' - self.tmp_volumes.append(name) - result = self.client.create_volume(name) - self.assertIn('Name', result) - self.assertEqual(result['Name'], name) - self.assertIn('Driver', result) - self.assertEqual(result['Driver'], 'local') - - def test_create_volume_invalid_driver(self): - driver_name = 'invalid.driver' - - with pytest.raises(docker.errors.NotFound): - self.client.create_volume('perfectcherryblossom', driver_name) - - def test_list_volumes(self): - name = 'imperishablenight' - self.tmp_volumes.append(name) - volume_info = self.client.create_volume(name) - result = self.client.volumes() - self.assertIn('Volumes', result) - volumes = result['Volumes'] - self.assertIn(volume_info, volumes) - - def test_inspect_volume(self): - name = 'embodimentofscarletdevil' - self.tmp_volumes.append(name) - volume_info = self.client.create_volume(name) - result = self.client.inspect_volume(name) - self.assertEqual(volume_info, result) - - def test_inspect_nonexistent_volume(self): - name = 'embodimentofscarletdevil' - with pytest.raises(docker.errors.NotFound): - self.client.inspect_volume(name) - - def test_remove_volume(self): - name = 'shootthebullet' - self.tmp_volumes.append(name) - self.client.create_volume(name) - result = self.client.remove_volume(name) - self.assertTrue(result) - - def test_remove_nonexistent_volume(self): - name = 'shootthebullet' - with pytest.raises(docker.errors.NotFound): - self.client.remove_volume(name) - - -################# -# BUILDER TESTS # -################# - -class TestBuildStream(BaseTestCase): - def runTest(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - stream = self.client.build(fileobj=script, stream=True) - logs = '' - for chunk in stream: - if six.PY3: - chunk = chunk.decode('utf-8') - json.loads(chunk) # ensure chunk is a single, valid JSON blob - logs += chunk - self.assertNotEqual(logs, '') - - -class TestBuildFromStringIO(BaseTestCase): - def runTest(self): - if six.PY3: - return - script = io.StringIO(six.text_type('\n').join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ])) - stream = self.client.build(fileobj=script, stream=True) - logs = '' - for chunk in stream: - if six.PY3: - chunk = chunk.decode('utf-8') - logs += chunk - self.assertNotEqual(logs, '') - - -@requires_api_version('1.8') -class TestBuildWithDockerignore(Cleanup, BaseTestCase): - def runTest(self): - base_dir = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, base_dir) - - with open(os.path.join(base_dir, 'Dockerfile'), 'w') as f: - f.write("\n".join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'ADD . /test', - ])) - - with open(os.path.join(base_dir, '.dockerignore'), 'w') as f: - f.write("\n".join([ - 'ignored', - 'Dockerfile', - '.dockerignore', - '', # empty line - ])) - - with open(os.path.join(base_dir, 'not-ignored'), 'w') as f: - f.write("this file should not be ignored") - - subdir = os.path.join(base_dir, 'ignored', 'subdir') - os.makedirs(subdir) - with open(os.path.join(subdir, 'file'), 'w') as f: - f.write("this file should be ignored") - - tag = 'docker-py-test-build-with-dockerignore' - stream = self.client.build( - path=base_dir, - tag=tag, - ) - for chunk in stream: - pass - - c = self.client.create_container(tag, ['ls', '-1A', '/test']) - self.client.start(c) - self.client.wait(c) - logs = self.client.logs(c) - - if six.PY3: - logs = logs.decode('utf-8') - - self.assertEqual( - list(filter(None, logs.split('\n'))), - ['not-ignored'], - ) - - -####################### -# NETWORK TESTS # -####################### - - -@requires_api_version('1.21') -class TestNetworks(BaseTestCase): - def create_network(self, *args, **kwargs): - net_name = 'dockerpy{}'.format(random.getrandbits(24))[:14] - net_id = self.client.create_network(net_name, *args, **kwargs)['id'] - self.tmp_networks.append(net_id) - return (net_name, net_id) - - def test_list_networks(self): - networks = self.client.networks() - initial_size = len(networks) - - net_name, net_id = self.create_network() - - networks = self.client.networks() - self.assertEqual(len(networks), initial_size + 1) - self.assertTrue(net_id in [n['id'] for n in networks]) - - networks_by_name = self.client.networks(names=[net_name]) - self.assertEqual([n['id'] for n in networks_by_name], [net_id]) - - networks_by_partial_id = self.client.networks(ids=[net_id[:8]]) - self.assertEqual([n['id'] for n in networks_by_partial_id], [net_id]) - - def test_inspect_network(self): - net_name, net_id = self.create_network() - - net = self.client.inspect_network(net_id) - self.assertEqual(net, { - u'name': net_name, - u'id': net_id, - u'driver': 'bridge', - u'containers': {}, - }) - - def test_create_network_with_host_driver_fails(self): - net_name = 'dockerpy{}'.format(random.getrandbits(24))[:14] - - with pytest.raises(APIError): - self.client.create_network(net_name, driver='host') - - def test_remove_network(self): - initial_size = len(self.client.networks()) - - net_name, net_id = self.create_network() - self.assertEqual(len(self.client.networks()), initial_size + 1) - - self.client.remove_network(net_id) - self.assertEqual(len(self.client.networks()), initial_size) - - def test_connect_and_disconnect_container(self): - net_name, net_id = self.create_network() - - container = self.client.create_container('busybox', 'top') - self.tmp_containers.append(container) - self.client.start(container) - - network_data = self.client.inspect_network(net_id) - self.assertFalse(network_data.get('containers')) - - self.client.connect_container_to_network(container, net_id) - network_data = self.client.inspect_network(net_id) - self.assertEqual( - list(network_data['containers'].keys()), - [container['Id']]) - - self.client.disconnect_container_from_network(container, net_id) - network_data = self.client.inspect_network(net_id) - self.assertFalse(network_data.get('containers')) - - def test_connect_on_container_create(self): - net_name, net_id = self.create_network() - - container = self.client.create_container( - image='busybox', - command='top', - host_config=self.client.create_host_config(network_mode=net_name), - ) - self.tmp_containers.append(container) - self.client.start(container) - - network_data = self.client.inspect_network(net_id) - self.assertEqual( - list(network_data['containers'].keys()), - [container['Id']]) - - self.client.disconnect_container_from_network(container, net_id) - network_data = self.client.inspect_network(net_id) - self.assertFalse(network_data.get('containers')) - - -####################### -# PY SPECIFIC TESTS # -####################### - - -class TestRunShlex(BaseTestCase): - def runTest(self): - commands = [ - 'true', - 'echo "The Young Descendant of Tepes & Septette for the ' - 'Dead Princess"', - 'echo -n "The Young Descendant of Tepes & Septette for the ' - 'Dead Princess"', - '/bin/sh -c "echo Hello World"', - '/bin/sh -c \'echo "Hello World"\'', - 'echo "\"Night of Nights\""', - 'true && echo "Night of Nights"' - ] - for cmd in commands: - container = self.client.create_container(BUSYBOX, cmd) - id = container['Id'] - self.client.start(id) - self.tmp_containers.append(id) - exitcode = self.client.wait(id) - self.assertEqual(exitcode, 0, msg=cmd) - - -class TestLoadConfig(BaseTestCase): - def runTest(self): - folder = tempfile.mkdtemp() - self.tmp_folders.append(folder) - cfg_path = os.path.join(folder, '.dockercfg') - f = open(cfg_path, 'w') - auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') - f.write('auth = {0}\n'.format(auth_)) - f.write('email = sakuya@scarlet.net') - f.close() - cfg = docker.auth.load_config(cfg_path) - self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) - cfg = cfg[docker.auth.INDEX_NAME] - self.assertEqual(cfg['username'], 'sakuya') - self.assertEqual(cfg['password'], 'izayoi') - self.assertEqual(cfg['email'], 'sakuya@scarlet.net') - self.assertEqual(cfg.get('Auth'), None) - - -class TestLoadJSONConfig(BaseTestCase): - def runTest(self): - folder = tempfile.mkdtemp() - self.tmp_folders.append(folder) - cfg_path = os.path.join(folder, '.dockercfg') - f = open(os.path.join(folder, '.dockercfg'), 'w') - auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') - email_ = 'sakuya@scarlet.net' - f.write('{{"{0}": {{"auth": "{1}", "email": "{2}"}}}}\n'.format( - docker.auth.INDEX_URL, auth_, email_)) - f.close() - cfg = docker.auth.load_config(cfg_path) - self.assertNotEqual(cfg[docker.auth.INDEX_URL], None) - cfg = cfg[docker.auth.INDEX_URL] - self.assertEqual(cfg['username'], 'sakuya') - self.assertEqual(cfg['password'], 'izayoi') - self.assertEqual(cfg['email'], 'sakuya@scarlet.net') - self.assertEqual(cfg.get('Auth'), None) - - -class TestAutoDetectVersion(unittest.TestCase): - def test_client_init(self): - client = docker_client(version='auto') - client_version = client._version - api_version = client.version(api_version=False)['ApiVersion'] - self.assertEqual(client_version, api_version) - api_version_2 = client.version()['ApiVersion'] - self.assertEqual(client_version, api_version_2) - client.close() - - def test_auto_client(self): - client = docker.AutoVersionClient(**docker_client_kwargs()) - client_version = client._version - api_version = client.version(api_version=False)['ApiVersion'] - self.assertEqual(client_version, api_version) - api_version_2 = client.version()['ApiVersion'] - self.assertEqual(client_version, api_version_2) - client.close() - with self.assertRaises(docker.errors.DockerException): - docker.AutoVersionClient(**docker_client_kwargs(version='1.11')) - - -class TestConnectionTimeout(unittest.TestCase): - def setUp(self): - self.timeout = 0.5 - self.client = docker.client.Client(base_url='http://192.168.10.2:4243', - timeout=self.timeout) - - def runTest(self): - start = time.time() - res = None - # This call isn't supposed to complete, and it should fail fast. - try: - res = self.client.inspect_container('id') - except: - pass - end = time.time() - self.assertTrue(res is None) - self.assertTrue(end - start < 2 * self.timeout) - - -class UnixconnTestCase(unittest.TestCase): - """ - Test UNIX socket connection adapter. - """ - - def test_resource_warnings(self): - """ - Test no warnings are produced when using the client. - """ - - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') - - client = docker_client() - client.images() - client.close() - del client - - assert len(w) == 0, \ - "No warnings produced: {0}".format(w[0].message) - - -#################### -# REGRESSION TESTS # -#################### - -class TestRegressions(BaseTestCase): - def test_443(self): - dfile = io.BytesIO() - with self.assertRaises(docker.errors.APIError) as exc: - for line in self.client.build(fileobj=dfile, tag="a/b/c"): - pass - self.assertEqual(exc.exception.response.status_code, 500) - dfile.close() - - def test_542(self): - self.client.start( - self.client.create_container(BUSYBOX, ['true']) - ) - result = self.client.containers(all=True, trunc=True) - self.assertEqual(len(result[0]['Id']), 12) - - def test_647(self): - with self.assertRaises(docker.errors.APIError): - self.client.inspect_image('gensokyo.jp//kirisame') - - def test_649(self): - self.client.timeout = None - ctnr = self.client.create_container(BUSYBOX, ['sleep', '2']) - self.client.start(ctnr) - self.client.stop(ctnr) - - def test_715(self): - ctnr = self.client.create_container(BUSYBOX, ['id', '-u'], user=1000) - self.client.start(ctnr) - self.client.wait(ctnr) - logs = self.client.logs(ctnr) - if six.PY3: - logs = logs.decode('utf-8') - assert logs == '1000\n' - - def test_792_explicit_port_protocol(self): - - tcp_port, udp_port = random.sample(range(9999, 32000), 2) - ctnr = self.client.create_container( - BUSYBOX, ['sleep', '9999'], ports=[2000, (2000, 'udp')], - host_config=self.client.create_host_config( - port_bindings={'2000/tcp': tcp_port, '2000/udp': udp_port} - ) - ) - self.tmp_containers.append(ctnr) - self.client.start(ctnr) - self.assertEqual( - self.client.port(ctnr, 2000)[0]['HostPort'], - six.text_type(tcp_port) - ) - self.assertEqual( - self.client.port(ctnr, '2000/tcp')[0]['HostPort'], - six.text_type(tcp_port) - ) - self.assertEqual( - self.client.port(ctnr, '2000/udp')[0]['HostPort'], - six.text_type(udp_port) - ) diff -Nru python-docker-1.5.0/tests/test.py python-docker-1.8.0/tests/test.py --- python-docker-1.5.0/tests/test.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,2615 +0,0 @@ -# Copyright 2013 dotCloud inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import base64 -import datetime -import gzip -import io -import json -import os -import re -import shutil -import signal -import socket -import sys -import tarfile -import tempfile -import threading -import time -import random - -import docker -import requests -import six - -from . import base -from . import fake_api -from .helpers import make_tree - -import pytest - -try: - from unittest import mock -except ImportError: - import mock - - -DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS - - -def response(status_code=200, content='', headers=None, reason=None, elapsed=0, - request=None): - res = requests.Response() - res.status_code = status_code - if not isinstance(content, six.binary_type): - content = json.dumps(content).encode('ascii') - res._content = content - res.headers = requests.structures.CaseInsensitiveDict(headers or {}) - res.reason = reason - res.elapsed = datetime.timedelta(elapsed) - res.request = request - return res - - -def fake_resolve_authconfig(authconfig, registry=None): - return None - - -def fake_inspect_container(self, container, tty=False): - return fake_api.get_fake_inspect_container(tty=tty)[1] - - -def fake_inspect_container_tty(self, container): - return fake_inspect_container(self, container, tty=True) - - -def fake_resp(method, url, *args, **kwargs): - key = None - if url in fake_api.fake_responses: - key = url - elif (url, method) in fake_api.fake_responses: - key = (url, method) - if not key: - raise Exception('{0} {1}'.format(method, url)) - status_code, content = fake_api.fake_responses[key]() - return response(status_code=status_code, content=content) - - -fake_request = mock.Mock(side_effect=fake_resp) - - -def fake_get(self, url, *args, **kwargs): - return fake_request('GET', url, *args, **kwargs) - - -def fake_post(self, url, *args, **kwargs): - return fake_request('POST', url, *args, **kwargs) - - -def fake_put(self, url, *args, **kwargs): - return fake_request('PUT', url, *args, **kwargs) - - -def fake_delete(self, url, *args, **kwargs): - return fake_request('DELETE', url, *args, **kwargs) - -url_base = 'http+docker://localunixsocket/' -url_prefix = '{0}v{1}/'.format( - url_base, - docker.constants.DEFAULT_DOCKER_API_VERSION) - - -class Cleanup(object): - if sys.version_info < (2, 7): - # Provide a basic implementation of addCleanup for Python < 2.7 - def __init__(self, *args, **kwargs): - super(Cleanup, self).__init__(*args, **kwargs) - self._cleanups = [] - - def tearDown(self): - super(Cleanup, self).tearDown() - ok = True - while self._cleanups: - fn, args, kwargs = self._cleanups.pop(-1) - try: - fn(*args, **kwargs) - except KeyboardInterrupt: - raise - except: - ok = False - if not ok: - raise - - def addCleanup(self, function, *args, **kwargs): - self._cleanups.append((function, args, kwargs)) - - -@mock.patch.multiple('docker.Client', get=fake_get, post=fake_post, - put=fake_put, delete=fake_delete) -class DockerClientTest(Cleanup, base.BaseTestCase): - def setUp(self): - self.client = docker.Client() - # Force-clear authconfig to avoid tampering with the tests - self.client._cfg = {'Configs': {}} - - def tearDown(self): - self.client.close() - - def assertIn(self, object, collection): - if six.PY2 and sys.version_info[1] <= 6: - return self.assertTrue(object in collection) - return super(DockerClientTest, self).assertIn(object, collection) - - def base_create_payload(self, img='busybox', cmd=None): - if not cmd: - cmd = ['true'] - return {"Tty": False, "Image": img, "Cmd": cmd, - "AttachStdin": False, - "AttachStderr": True, "AttachStdout": True, - "StdinOnce": False, - "OpenStdin": False, "NetworkDisabled": False, - } - - def test_ctor(self): - with pytest.raises(docker.errors.DockerException) as excinfo: - docker.Client(version=1.12) - - self.assertEqual( - str(excinfo.value), - 'Version parameter must be a string or None. Found float' - ) - - def test_url_valid_resource(self): - url = self.client._url('/hello/{0}/world', 'somename') - self.assertEqual( - url, '{0}{1}'.format(url_prefix, 'hello/somename/world') - ) - - url = self.client._url( - '/hello/{0}/world/{1}', 'somename', 'someothername' - ) - self.assertEqual( - url, - '{0}{1}'.format(url_prefix, 'hello/somename/world/someothername') - ) - - url = self.client._url('/hello/{0}/world', '/some?name') - self.assertEqual( - url, '{0}{1}'.format(url_prefix, 'hello/%2Fsome%3Fname/world') - ) - - def test_url_invalid_resource(self): - with pytest.raises(ValueError): - self.client._url('/hello/{0}/world', ['sakuya', 'izayoi']) - - def test_url_no_resource(self): - url = self.client._url('/simple') - self.assertEqual(url, '{0}{1}'.format(url_prefix, 'simple')) - - def test_url_unversioned_api(self): - url = self.client._url( - '/hello/{0}/world', 'somename', versioned_api=False - ) - self.assertEqual( - url, '{0}{1}'.format(url_base, 'hello/somename/world') - ) - - ######################### - # INFORMATION TESTS # - ######################### - def test_version(self): - self.client.version() - - fake_request.assert_called_with( - 'GET', - url_prefix + 'version', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_version_no_api_version(self): - self.client.version(False) - - fake_request.assert_called_with( - 'GET', - url_base + 'version', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_retrieve_server_version(self): - client = docker.Client(version="auto") - self.assertTrue(isinstance(client._version, six.string_types)) - self.assertFalse(client._version == "auto") - client.close() - - def test_auto_retrieve_server_version(self): - version = self.client._retrieve_server_version() - self.assertTrue(isinstance(version, six.string_types)) - - def test_info(self): - self.client.info() - - fake_request.assert_called_with( - 'GET', - url_prefix + 'info', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_search(self): - self.client.search('busybox') - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/search', - params={'term': 'busybox'}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_image_viz(self): - with pytest.raises(Exception): - self.client.images('busybox', viz=True) - self.fail('Viz output should not be supported!') - - def test_events(self): - self.client.events() - - fake_request.assert_called_with( - 'GET', - url_prefix + 'events', - params={'since': None, 'until': None, 'filters': None}, - stream=True - ) - - def test_events_with_since_until(self): - ts = 1356048000 - now = datetime.datetime.utcfromtimestamp(ts) - since = now - datetime.timedelta(seconds=10) - until = now + datetime.timedelta(seconds=10) - - self.client.events(since=since, until=until) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'events', - params={ - 'since': ts - 10, - 'until': ts + 10, - 'filters': None - }, - stream=True - ) - - def test_events_with_filters(self): - filters = {'event': ['die', 'stop'], - 'container': fake_api.FAKE_CONTAINER_ID} - - self.client.events(filters=filters) - - expected_filters = docker.utils.convert_filters(filters) - fake_request.assert_called_with( - 'GET', - url_prefix + 'events', - params={ - 'since': None, - 'until': None, - 'filters': expected_filters - }, - stream=True - ) - - ################### - # LISTING TESTS # - ################### - - def test_images(self): - self.client.images(all=True) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/json', - params={'filter': None, 'only_ids': 0, 'all': 1}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_images_quiet(self): - self.client.images(all=True, quiet=True) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/json', - params={'filter': None, 'only_ids': 1, 'all': 1}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_image_ids(self): - self.client.images(quiet=True) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/json', - params={'filter': None, 'only_ids': 1, 'all': 0}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_images_filters(self): - self.client.images(filters={'dangling': True}) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/json', - params={'filter': None, 'only_ids': 0, 'all': 0, - 'filters': '{"dangling": ["true"]}'}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_list_containers(self): - self.client.containers(all=True) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/json', - params={ - 'all': 1, - 'since': None, - 'size': 0, - 'limit': -1, - 'trunc_cmd': 0, - 'before': None - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - @base.requires_api_version('1.21') - def test_list_networks(self): - networks = [ - { - "name": "none", - "id": "8e4e55c6863ef424", - "type": "null", - "endpoints": [] - }, - { - "name": "host", - "id": "062b6d9ea7913fde", - "type": "host", - "endpoints": [] - }, - ] - - get = mock.Mock(return_value=response( - status_code=200, content=json.dumps(networks).encode('utf-8'))) - - with mock.patch('docker.Client.get', get): - self.assertEqual(self.client.networks(), networks) - - self.assertEqual(get.call_args[0][0], url_prefix + 'networks') - - filters = json.loads(get.call_args[1]['params']['filters']) - self.assertFalse(filters) - - self.client.networks(names=['foo']) - filters = json.loads(get.call_args[1]['params']['filters']) - self.assertEqual(filters, {'name': ['foo']}) - - self.client.networks(ids=['123']) - filters = json.loads(get.call_args[1]['params']['filters']) - self.assertEqual(filters, {'id': ['123']}) - - ##################### - # CONTAINER TESTS # - ##################### - - def test_create_container(self): - self.client.create_container('busybox', 'true') - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", "Cmd": ["true"], - "AttachStdin": false, - "AttachStderr": true, "AttachStdout": true, - "StdinOnce": false, - "OpenStdin": false, "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_binds(self): - mount_dest = '/mnt' - - self.client.create_container('busybox', ['ls', mount_dest], - volumes=[mount_dest]) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls", "/mnt"], "AttachStdin": false, - "Volumes": {"/mnt": {}}, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_volume_string(self): - mount_dest = '/mnt' - - self.client.create_container('busybox', ['ls', mount_dest], - volumes=mount_dest) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls", "/mnt"], "AttachStdin": false, - "Volumes": {"/mnt": {}}, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_ports(self): - self.client.create_container('busybox', 'ls', - ports=[1111, (2222, 'udp'), (3333,)]) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls"], "AttachStdin": false, - "ExposedPorts": { - "1111/tcp": {}, - "2222/udp": {}, - "3333/tcp": {} - }, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_entrypoint(self): - self.client.create_container('busybox', 'hello', - entrypoint='cowsay entry') - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["hello"], "AttachStdin": false, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false, - "Entrypoint": ["cowsay", "entry"]}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_cpu_shares(self): - self.client.create_container('busybox', 'ls', - cpu_shares=5) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls"], "AttachStdin": false, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false, - "CpuShares": 5}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_cpuset(self): - self.client.create_container('busybox', 'ls', - cpuset='0,1') - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls"], "AttachStdin": false, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false, - "Cpuset": "0,1", - "CpusetCpus": "0,1"}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_cgroup_parent(self): - self.client.create_container( - 'busybox', 'ls', host_config=self.client.create_host_config( - cgroup_parent='test' - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - data = json.loads(args[1]['data']) - self.assertIn('HostConfig', data) - self.assertIn('CgroupParent', data['HostConfig']) - self.assertEqual(data['HostConfig']['CgroupParent'], 'test') - - def test_create_container_with_working_dir(self): - self.client.create_container('busybox', 'ls', - working_dir='/root') - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", - "Cmd": ["ls"], "AttachStdin": false, - "AttachStderr": true, - "AttachStdout": true, "OpenStdin": false, - "StdinOnce": false, - "NetworkDisabled": false, - "WorkingDir": "/root"}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_stdin_open(self): - self.client.create_container('busybox', 'true', stdin_open=True) - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", "Cmd": ["true"], - "AttachStdin": true, - "AttachStderr": true, "AttachStdout": true, - "StdinOnce": true, - "OpenStdin": true, "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_with_volumes_from(self): - vol_names = ['foo', 'bar'] - try: - self.client.create_container('busybox', 'true', - volumes_from=vol_names) - except docker.errors.DockerException: - self.assertTrue( - docker.utils.compare_version('1.10', self.client._version) >= 0 - ) - return - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data'])['VolumesFrom'], - ','.join(vol_names)) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_create_container_empty_volumes_from(self): - self.client.create_container('busybox', 'true', volumes_from=[]) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertTrue('VolumesFrom' not in data) - - def test_create_named_container(self): - self.client.create_container('busybox', 'true', - name='marisa-kirisame') - - args = fake_request.call_args - self.assertEqual(args[0][1], - url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), - json.loads(''' - {"Tty": false, "Image": "busybox", "Cmd": ["true"], - "AttachStdin": false, - "AttachStderr": true, "AttachStdout": true, - "StdinOnce": false, - "OpenStdin": false, "NetworkDisabled": false}''')) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual(args[1]['params'], {'name': 'marisa-kirisame'}) - - def test_create_container_with_mem_limit_as_int(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - mem_limit=128.0 - ) - ) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertEqual(data['HostConfig']['Memory'], 128.0) - - def test_create_container_with_mem_limit_as_string(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - mem_limit='128' - ) - ) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertEqual(data['HostConfig']['Memory'], 128.0) - - def test_create_container_with_mem_limit_as_string_with_k_unit(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - mem_limit='128k' - ) - ) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024) - - def test_create_container_with_mem_limit_as_string_with_m_unit(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - mem_limit='128m' - ) - ) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024 * 1024) - - def test_create_container_with_mem_limit_as_string_with_g_unit(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - mem_limit='128g' - ) - ) - - args = fake_request.call_args - data = json.loads(args[1]['data']) - self.assertEqual( - data['HostConfig']['Memory'], 128.0 * 1024 * 1024 * 1024 - ) - - def test_create_container_with_mem_limit_as_string_with_wrong_value(self): - self.assertRaises( - docker.errors.DockerException, - self.client.create_host_config, mem_limit='128p' - ) - - self.assertRaises( - docker.errors.DockerException, - self.client.create_host_config, mem_limit='1f28' - ) - - def test_start_container(self): - self.client.start(fake_api.FAKE_CONTAINER_ID) - - args = fake_request.call_args - self.assertEqual( - args[0][1], - url_prefix + 'containers/3cc2351ab11b/start' - ) - self.assertEqual(json.loads(args[1]['data']), {}) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_start_container_none(self): - with pytest.raises(ValueError) as excinfo: - self.client.start(container=None) - - self.assertEqual( - str(excinfo.value), - 'image or container param is undefined', - ) - - with pytest.raises(ValueError) as excinfo: - self.client.start(None) - - self.assertEqual( - str(excinfo.value), - 'image or container param is undefined', - ) - - def test_start_container_regression_573(self): - self.client.start(**{'container': fake_api.FAKE_CONTAINER_ID}) - - def test_create_container_with_lxc_conf(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - lxc_conf={'lxc.conf.k': 'lxc.conf.value'} - ) - ) - - args = fake_request.call_args - self.assertEqual( - args[0][1], - url_prefix + 'containers/create' - ) - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['LxcConf'] = [ - {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} - ] - - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], - {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_lxc_conf_compat(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['LxcConf'] = [ - {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} - ] - self.assertEqual( - json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_binds_ro(self): - mount_dest = '/mnt' - mount_origin = '/tmp' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - binds={mount_origin: { - "bind": mount_dest, - "ro": True - }} - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + - 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:ro"] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_binds_rw(self): - mount_dest = '/mnt' - mount_origin = '/tmp' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - binds={mount_origin: { - "bind": mount_dest, - "ro": False - }} - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + - 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:rw"] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_binds_mode(self): - mount_dest = '/mnt' - mount_origin = '/tmp' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - binds={mount_origin: { - "bind": mount_dest, - "mode": "z", - }} - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + - 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:z"] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_binds_mode_and_ro_error(self): - with pytest.raises(ValueError): - mount_dest = '/mnt' - mount_origin = '/tmp' - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - binds={mount_origin: { - "bind": mount_dest, - "mode": "z", - "ro": True, - }} - ) - ) - - def test_create_container_with_binds_list(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - binds=[ - "/tmp:/mnt/1:ro", - "/tmp:/mnt/2", - ], - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + - 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Binds'] = [ - "/tmp:/mnt/1:ro", - "/tmp:/mnt/2", - ] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_port_binds(self): - self.maxDiff = None - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - port_bindings={ - 1111: None, - 2222: 2222, - '3333/udp': (3333,), - 4444: ('127.0.0.1',), - 5555: ('127.0.0.1', 5555), - 6666: [('127.0.0.1',), ('192.168.0.1',)] - } - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - data = json.loads(args[1]['data']) - port_bindings = data['HostConfig']['PortBindings'] - self.assertTrue('1111/tcp' in port_bindings) - self.assertTrue('2222/tcp' in port_bindings) - self.assertTrue('3333/udp' in port_bindings) - self.assertTrue('4444/tcp' in port_bindings) - self.assertTrue('5555/tcp' in port_bindings) - self.assertTrue('6666/tcp' in port_bindings) - self.assertEqual( - [{"HostPort": "", "HostIp": ""}], - port_bindings['1111/tcp'] - ) - self.assertEqual( - [{"HostPort": "2222", "HostIp": ""}], - port_bindings['2222/tcp'] - ) - self.assertEqual( - [{"HostPort": "3333", "HostIp": ""}], - port_bindings['3333/udp'] - ) - self.assertEqual( - [{"HostPort": "", "HostIp": "127.0.0.1"}], - port_bindings['4444/tcp'] - ) - self.assertEqual( - [{"HostPort": "5555", "HostIp": "127.0.0.1"}], - port_bindings['5555/tcp'] - ) - self.assertEqual(len(port_bindings['6666/tcp']), 2) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_mac_address(self): - mac_address_expected = "02:42:ac:11:00:0a" - - container = self.client.create_container( - 'busybox', ['sleep', '60'], mac_address=mac_address_expected) - - res = self.client.inspect_container(container['Id']) - self.assertEqual(mac_address_expected, - res['NetworkSettings']['MacAddress']) - - def test_create_container_with_links(self): - link_path = 'path' - alias = 'alias' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - links={link_path: alias} - ) - ) - - args = fake_request.call_args - self.assertEqual( - args[0][1], url_prefix + 'containers/create' - ) - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Links'] = ['path:alias'] - - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - - def test_create_container_with_multiple_links(self): - link_path = 'path' - alias = 'alias' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - links={ - link_path + '1': alias + '1', - link_path + '2': alias + '2' - } - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Links'] = [ - 'path1:alias1', 'path2:alias2' - ] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - - def test_create_container_with_links_as_list_of_tuples(self): - link_path = 'path' - alias = 'alias' - - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - links=[(link_path, alias)] - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Links'] = ['path:alias'] - - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - - def test_create_container_privileged(self): - self.client.create_container( - 'busybox', 'true', - host_config=self.client.create_host_config(privileged=True) - ) - - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Privileged'] = True - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_start_container_with_lxc_conf(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, - lxc_conf={'lxc.conf.k': 'lxc.conf.value'} - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_lxc_conf_compat(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, - lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_binds_ro(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, binds={ - '/tmp': { - "bind": '/mnt', - "ro": True - } - } - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_binds_rw(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, binds={ - '/tmp': {"bind": '/mnt', "ro": False} - } - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_port_binds(self): - self.maxDiff = None - - def call_start(): - self.client.start(fake_api.FAKE_CONTAINER_ID, port_bindings={ - 1111: None, - 2222: 2222, - '3333/udp': (3333,), - 4444: ('127.0.0.1',), - 5555: ('127.0.0.1', 5555), - 6666: [('127.0.0.1',), ('192.168.0.1',)] - }) - - pytest.deprecated_call(call_start) - - def test_start_container_with_links(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, links={'path': 'alias'} - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_multiple_links(self): - def call_start(): - self.client.start( - fake_api.FAKE_CONTAINER_ID, - links={ - 'path1': 'alias1', - 'path2': 'alias2' - } - ) - - pytest.deprecated_call(call_start) - - def test_start_container_with_links_as_list_of_tuples(self): - def call_start(): - self.client.start(fake_api.FAKE_CONTAINER_ID, - links=[('path', 'alias')]) - - pytest.deprecated_call(call_start) - - def test_start_container_privileged(self): - def call_start(): - self.client.start(fake_api.FAKE_CONTAINER_ID, privileged=True) - - pytest.deprecated_call(call_start) - - def test_start_container_with_dict_instead_of_id(self): - self.client.start({'Id': fake_api.FAKE_CONTAINER_ID}) - - args = fake_request.call_args - self.assertEqual( - args[0][1], - url_prefix + 'containers/3cc2351ab11b/start' - ) - self.assertEqual(json.loads(args[1]['data']), {}) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_restart_policy(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - restart_policy={ - "Name": "always", - "MaximumRetryCount": 0 - } - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['RestartPolicy'] = { - "MaximumRetryCount": 0, "Name": "always" - } - self.assertEqual(json.loads(args[1]['data']), expected_payload) - - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_added_capabilities(self): - self.client.create_container( - 'busybox', 'true', - host_config=self.client.create_host_config(cap_add=['MKNOD']) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['CapAdd'] = ['MKNOD'] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_dropped_capabilities(self): - self.client.create_container( - 'busybox', 'true', - host_config=self.client.create_host_config(cap_drop=['MKNOD']) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['CapDrop'] = ['MKNOD'] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_devices(self): - self.client.create_container( - 'busybox', 'true', host_config=self.client.create_host_config( - devices=['/dev/sda:/dev/xvda:rwm', - '/dev/sdb:/dev/xvdb', - '/dev/sdc'] - ) - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - expected_payload = self.base_create_payload() - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Devices'] = [ - {'CgroupPermissions': 'rwm', - 'PathInContainer': '/dev/xvda', - 'PathOnHost': '/dev/sda'}, - {'CgroupPermissions': 'rwm', - 'PathInContainer': '/dev/xvdb', - 'PathOnHost': '/dev/sdb'}, - {'CgroupPermissions': 'rwm', - 'PathInContainer': '/dev/sdc', - 'PathOnHost': '/dev/sdc'} - ] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_labels_dict(self): - labels_dict = { - six.text_type('foo'): six.text_type('1'), - six.text_type('bar'): six.text_type('2'), - } - - self.client.create_container( - 'busybox', 'true', - labels=labels_dict, - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_labels_list(self): - labels_list = [ - six.text_type('foo'), - six.text_type('bar'), - ] - labels_dict = { - six.text_type('foo'): six.text_type(), - six.text_type('bar'): six.text_type(), - } - - self.client.create_container( - 'busybox', 'true', - labels=labels_list, - ) - - args = fake_request.call_args - self.assertEqual(args[0][1], url_prefix + 'containers/create') - self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) - self.assertEqual( - args[1]['headers'], {'Content-Type': 'application/json'} - ) - self.assertEqual( - args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS - ) - - def test_create_container_with_named_volume(self): - mount_dest = '/mnt' - volume_name = 'name' - - self.client.create_container( - 'busybox', 'true', - host_config=self.client.create_host_config( - binds={volume_name: { - "bind": mount_dest, - "ro": False - }}), - volume_driver='foodriver', - ) - - args = fake_request.call_args - self.assertEqual( - args[0][1], url_prefix + 'containers/create' - ) - expected_payload = self.base_create_payload() - expected_payload['VolumeDriver'] = 'foodriver' - expected_payload['HostConfig'] = self.client.create_host_config() - expected_payload['HostConfig']['Binds'] = ["name:/mnt:rw"] - self.assertEqual(json.loads(args[1]['data']), expected_payload) - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - self.assertEqual( - args[1]['timeout'], - DEFAULT_TIMEOUT_SECONDS - ) - - def test_resize_container(self): - self.client.resize( - {'Id': fake_api.FAKE_CONTAINER_ID}, - height=15, - width=120 - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/resize', - params={'h': 15, 'w': 120}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_rename_container(self): - self.client.rename( - {'Id': fake_api.FAKE_CONTAINER_ID}, - name='foobar' - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/rename', - params={'name': 'foobar'}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_wait(self): - self.client.wait(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/wait', - timeout=None - ) - - def test_wait_with_dict_instead_of_id(self): - self.client.wait({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/wait', - timeout=None - ) - - def _socket_path_for_client_session(self, client): - socket_adapter = client.get_adapter('http+docker://') - return socket_adapter.socket_path - - def test_url_compatibility_unix(self): - c = docker.Client(base_url="unix://socket") - - assert self._socket_path_for_client_session(c) == '/socket' - - def test_url_compatibility_unix_triple_slash(self): - c = docker.Client(base_url="unix:///socket") - - assert self._socket_path_for_client_session(c) == '/socket' - - def test_url_compatibility_http_unix_triple_slash(self): - c = docker.Client(base_url="http+unix:///socket") - - assert self._socket_path_for_client_session(c) == '/socket' - - def test_url_compatibility_http(self): - c = docker.Client(base_url="http://hostname:1234") - - assert c.base_url == "http://hostname:1234" - - def test_url_compatibility_tcp(self): - c = docker.Client(base_url="tcp://hostname:1234") - - assert c.base_url == "http://hostname:1234" - - def test_logs(self): - with mock.patch('docker.Client.inspect_container', - fake_inspect_container): - logs = self.client.logs(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', - params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, - 'tail': 'all'}, - timeout=DEFAULT_TIMEOUT_SECONDS, - stream=False - ) - - self.assertEqual( - logs, - 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') - ) - - def test_logs_with_dict_instead_of_id(self): - with mock.patch('docker.Client.inspect_container', - fake_inspect_container): - logs = self.client.logs({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', - params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, - 'tail': 'all'}, - timeout=DEFAULT_TIMEOUT_SECONDS, - stream=False - ) - - self.assertEqual( - logs, - 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') - ) - - def test_log_streaming(self): - with mock.patch('docker.Client.inspect_container', - fake_inspect_container): - self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', - params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, - 'tail': 'all'}, - timeout=DEFAULT_TIMEOUT_SECONDS, - stream=True - ) - - def test_log_tail(self): - with mock.patch('docker.Client.inspect_container', - fake_inspect_container): - self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, - tail=10) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', - params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, - 'tail': 10}, - timeout=DEFAULT_TIMEOUT_SECONDS, - stream=False - ) - - def test_log_tty(self): - m = mock.Mock() - with mock.patch('docker.Client.inspect_container', - fake_inspect_container_tty): - with mock.patch('docker.Client._stream_raw_result', - m): - self.client.logs(fake_api.FAKE_CONTAINER_ID, - stream=True) - - self.assertTrue(m.called) - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/logs', - params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, - 'tail': 'all'}, - timeout=DEFAULT_TIMEOUT_SECONDS, - stream=True - ) - - def test_diff(self): - self.client.diff(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/changes', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_diff_with_dict_instead_of_id(self): - self.client.diff({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/changes', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_port(self): - self.client.port({'Id': fake_api.FAKE_CONTAINER_ID}, 1111) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/json', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_stop_container(self): - timeout = 2 - - self.client.stop(fake_api.FAKE_CONTAINER_ID, timeout=timeout) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/stop', - params={'t': timeout}, - timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) - ) - - def test_stop_container_with_dict_instead_of_id(self): - timeout = 2 - - self.client.stop({'Id': fake_api.FAKE_CONTAINER_ID}, - timeout=timeout) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/stop', - params={'t': timeout}, - timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) - ) - - def test_exec_create(self): - self.client.exec_create(fake_api.FAKE_CONTAINER_ID, ['ls', '-1']) - - args = fake_request.call_args - self.assertEqual( - 'POST', - args[0][0], url_prefix + 'containers/{0}/exec'.format( - fake_api.FAKE_CONTAINER_ID - ) - ) - - self.assertEqual( - json.loads(args[1]['data']), { - 'Tty': False, - 'AttachStdout': True, - 'Container': fake_api.FAKE_CONTAINER_ID, - 'Cmd': ['ls', '-1'], - 'Privileged': False, - 'AttachStdin': False, - 'AttachStderr': True, - 'User': '' - } - ) - - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_exec_start(self): - self.client.exec_start(fake_api.FAKE_EXEC_ID) - - args = fake_request.call_args - self.assertEqual( - args[0][1], url_prefix + 'exec/{0}/start'.format( - fake_api.FAKE_EXEC_ID - ) - ) - - self.assertEqual( - json.loads(args[1]['data']), { - 'Tty': False, - 'Detach': False, - } - ) - - self.assertEqual(args[1]['headers'], - {'Content-Type': 'application/json'}) - - def test_exec_inspect(self): - self.client.exec_inspect(fake_api.FAKE_EXEC_ID) - - args = fake_request.call_args - self.assertEqual( - args[0][1], url_prefix + 'exec/{0}/json'.format( - fake_api.FAKE_EXEC_ID - ) - ) - - def test_exec_resize(self): - self.client.exec_resize(fake_api.FAKE_EXEC_ID, height=20, width=60) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'exec/{0}/resize'.format(fake_api.FAKE_EXEC_ID), - params={'h': 20, 'w': 60}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_pause_container(self): - self.client.pause(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/pause', - timeout=(DEFAULT_TIMEOUT_SECONDS) - ) - - def test_unpause_container(self): - self.client.unpause(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/unpause', - timeout=(DEFAULT_TIMEOUT_SECONDS) - ) - - def test_kill_container(self): - self.client.kill(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', - params={}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_kill_container_with_dict_instead_of_id(self): - self.client.kill({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', - params={}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_kill_container_with_signal(self): - self.client.kill(fake_api.FAKE_CONTAINER_ID, signal=signal.SIGTERM) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/kill', - params={'signal': signal.SIGTERM}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_restart_container(self): - self.client.restart(fake_api.FAKE_CONTAINER_ID, timeout=2) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/restart', - params={'t': 2}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_restart_container_with_dict_instead_of_id(self): - self.client.restart({'Id': fake_api.FAKE_CONTAINER_ID}, timeout=2) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'containers/3cc2351ab11b/restart', - params={'t': 2}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_remove_container(self): - self.client.remove_container(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'DELETE', - url_prefix + 'containers/3cc2351ab11b', - params={'v': False, 'link': False, 'force': False}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_remove_container_with_dict_instead_of_id(self): - self.client.remove_container({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'DELETE', - url_prefix + 'containers/3cc2351ab11b', - params={'v': False, 'link': False, 'force': False}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_remove_link(self): - self.client.remove_container(fake_api.FAKE_CONTAINER_ID, link=True) - - fake_request.assert_called_with( - 'DELETE', - url_prefix + 'containers/3cc2351ab11b', - params={'v': False, 'link': True, 'force': False}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_export(self): - self.client.export(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/export', - stream=True, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_export_with_dict_instead_of_id(self): - self.client.export({'Id': fake_api.FAKE_CONTAINER_ID}) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/export', - stream=True, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_inspect_container(self): - self.client.inspect_container(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/json', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_inspect_container_undefined_id(self): - for arg in None, '', {True: True}: - with pytest.raises(docker.errors.NullResource) as excinfo: - self.client.inspect_container(arg) - - self.assertEqual( - excinfo.value.args[0], 'image or container param is undefined' - ) - - def test_container_stats(self): - self.client.stats(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/stats', - timeout=60, - stream=True - ) - - def test_container_top(self): - self.client.top(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/top', - params={}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_container_top_with_psargs(self): - self.client.top(fake_api.FAKE_CONTAINER_ID, 'waux') - - fake_request.assert_called_with( - 'GET', - url_prefix + 'containers/3cc2351ab11b/top', - params={'ps_args': 'waux'}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - ################## - # IMAGES TESTS # - ################## - - def test_pull(self): - self.client.pull('joffrey/test001') - - args = fake_request.call_args - self.assertEqual( - args[0][1], - url_prefix + 'images/create' - ) - self.assertEqual( - args[1]['params'], - {'tag': None, 'fromImage': 'joffrey/test001'} - ) - self.assertFalse(args[1]['stream']) - - def test_pull_stream(self): - self.client.pull('joffrey/test001', stream=True) - - args = fake_request.call_args - self.assertEqual( - args[0][1], - url_prefix + 'images/create' - ) - self.assertEqual( - args[1]['params'], - {'tag': None, 'fromImage': 'joffrey/test001'} - ) - self.assertTrue(args[1]['stream']) - - def test_commit(self): - self.client.commit(fake_api.FAKE_CONTAINER_ID) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'commit', - data='{}', - headers={'Content-Type': 'application/json'}, - params={ - 'repo': None, - 'comment': None, - 'tag': None, - 'container': '3cc2351ab11b', - 'author': None - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_remove_image(self): - self.client.remove_image(fake_api.FAKE_IMAGE_ID) - - fake_request.assert_called_with( - 'DELETE', - url_prefix + 'images/e9aa60c60128', - params={'force': False, 'noprune': False}, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_image_history(self): - self.client.history(fake_api.FAKE_IMAGE_NAME) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/test_image/history', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_import_image(self): - self.client.import_image( - fake_api.FAKE_TARBALL_PATH, - repository=fake_api.FAKE_REPO_NAME, - tag=fake_api.FAKE_TAG_NAME - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/create', - params={ - 'repo': fake_api.FAKE_REPO_NAME, - 'tag': fake_api.FAKE_TAG_NAME, - 'fromSrc': fake_api.FAKE_TARBALL_PATH - }, - data=None, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_import_image_from_bytes(self): - stream = (i for i in range(0, 100)) - - self.client.import_image( - stream, - repository=fake_api.FAKE_REPO_NAME, - tag=fake_api.FAKE_TAG_NAME - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/create', - params={ - 'repo': fake_api.FAKE_REPO_NAME, - 'tag': fake_api.FAKE_TAG_NAME, - 'fromSrc': '-', - }, - headers={ - 'Content-Type': 'application/tar', - }, - data=stream, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_import_image_from_image(self): - self.client.import_image( - image=fake_api.FAKE_IMAGE_NAME, - repository=fake_api.FAKE_REPO_NAME, - tag=fake_api.FAKE_TAG_NAME - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/create', - params={ - 'repo': fake_api.FAKE_REPO_NAME, - 'tag': fake_api.FAKE_TAG_NAME, - 'fromImage': fake_api.FAKE_IMAGE_NAME - }, - data=None, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_inspect_image(self): - self.client.inspect_image(fake_api.FAKE_IMAGE_NAME) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/test_image/json', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_inspect_image_undefined_id(self): - for arg in None, '', {True: True}: - with pytest.raises(docker.errors.NullResource) as excinfo: - self.client.inspect_image(arg) - - self.assertEqual( - excinfo.value.args[0], 'image or container param is undefined' - ) - - def test_insert_image(self): - try: - self.client.insert(fake_api.FAKE_IMAGE_NAME, - fake_api.FAKE_URL, fake_api.FAKE_PATH) - except docker.errors.DeprecatedMethod: - self.assertTrue( - docker.utils.compare_version('1.12', self.client._version) >= 0 - ) - return - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/test_image/insert', - params={ - 'url': fake_api.FAKE_URL, - 'path': fake_api.FAKE_PATH - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_push_image(self): - with mock.patch('docker.auth.auth.resolve_authconfig', - fake_resolve_authconfig): - self.client.push(fake_api.FAKE_IMAGE_NAME) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/test_image/push', - params={ - 'tag': None - }, - data='{}', - headers={'Content-Type': 'application/json'}, - stream=False, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_push_image_with_tag(self): - with mock.patch('docker.auth.auth.resolve_authconfig', - fake_resolve_authconfig): - self.client.push( - fake_api.FAKE_IMAGE_NAME, tag=fake_api.FAKE_TAG_NAME - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/test_image/push', - params={ - 'tag': fake_api.FAKE_TAG_NAME, - }, - data='{}', - headers={'Content-Type': 'application/json'}, - stream=False, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_push_image_stream(self): - with mock.patch('docker.auth.auth.resolve_authconfig', - fake_resolve_authconfig): - self.client.push(fake_api.FAKE_IMAGE_NAME, stream=True) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/test_image/push', - params={ - 'tag': None - }, - data='{}', - headers={'Content-Type': 'application/json'}, - stream=True, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_tag_image(self): - self.client.tag(fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/e9aa60c60128/tag', - params={ - 'tag': None, - 'repo': 'repo', - 'force': 0 - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_tag_image_tag(self): - self.client.tag( - fake_api.FAKE_IMAGE_ID, - fake_api.FAKE_REPO_NAME, - tag=fake_api.FAKE_TAG_NAME - ) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/e9aa60c60128/tag', - params={ - 'tag': 'tag', - 'repo': 'repo', - 'force': 0 - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_tag_image_force(self): - self.client.tag( - fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME, force=True) - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/e9aa60c60128/tag', - params={ - 'tag': None, - 'repo': 'repo', - 'force': 1 - }, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_get_image(self): - self.client.get_image(fake_api.FAKE_IMAGE_ID) - - fake_request.assert_called_with( - 'GET', - url_prefix + 'images/e9aa60c60128/get', - stream=True, - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - def test_load_image(self): - self.client.load_image('Byte Stream....') - - fake_request.assert_called_with( - 'POST', - url_prefix + 'images/load', - data='Byte Stream....', - timeout=DEFAULT_TIMEOUT_SECONDS - ) - - ################# - # BUILDER TESTS # - ################# - - def test_build_container(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - - self.client.build(fileobj=script) - - def test_build_container_pull(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - - self.client.build(fileobj=script, pull=True) - - def test_build_container_stream(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - - self.client.build(fileobj=script, stream=True) - - def test_build_container_custom_context(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - context = docker.utils.mkbuildcontext(script) - - self.client.build(fileobj=context, custom_context=True) - - def test_build_container_custom_context_gzip(self): - script = io.BytesIO('\n'.join([ - 'FROM busybox', - 'MAINTAINER docker-py', - 'RUN mkdir -p /tmp/test', - 'EXPOSE 8080', - 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' - ' /tmp/silence.tar.gz' - ]).encode('ascii')) - context = docker.utils.mkbuildcontext(script) - gz_context = gzip.GzipFile(fileobj=context) - - self.client.build( - fileobj=gz_context, - custom_context=True, - encoding="gzip" - ) - - def test_build_remote_with_registry_auth(self): - self.client._auth_configs = { - 'https://example.com': { - 'user': 'example', - 'password': 'example', - 'email': 'example@example.com' - } - } - - self.client.build(path='https://github.com/docker-library/mongo') - - def test_build_container_with_named_dockerfile(self): - self.client.build('.', dockerfile='nameddockerfile') - - def test_build_container_with_container_limits(self): - self.client.build('.', container_limits={ - 'memory': 1024 * 1024, - 'cpusetcpus': 1, - 'cpushares': 1000, - 'memswap': 1024 * 1024 * 8 - }) - - def test_build_container_invalid_container_limits(self): - self.assertRaises( - docker.errors.DockerException, - lambda: self.client.build('.', container_limits={ - 'foo': 'bar' - }) - ) - - ################### - # VOLUMES TESTS # - ################### - - @base.requires_api_version('1.21') - def test_list_volumes(self): - volumes = self.client.volumes() - self.assertIn('Volumes', volumes) - self.assertEqual(len(volumes['Volumes']), 2) - args = fake_request.call_args - - self.assertEqual(args[0][0], 'GET') - self.assertEqual(args[0][1], url_prefix + 'volumes') - - @base.requires_api_version('1.21') - def test_create_volume(self): - name = 'perfectcherryblossom' - result = self.client.create_volume(name) - self.assertIn('Name', result) - self.assertEqual(result['Name'], name) - self.assertIn('Driver', result) - self.assertEqual(result['Driver'], 'local') - args = fake_request.call_args - - self.assertEqual(args[0][0], 'POST') - self.assertEqual(args[0][1], url_prefix + 'volumes') - self.assertEqual(json.loads(args[1]['data']), {'Name': name}) - - @base.requires_api_version('1.21') - def test_create_volume_with_driver(self): - name = 'perfectcherryblossom' - driver_name = 'sshfs' - self.client.create_volume(name, driver=driver_name) - args = fake_request.call_args - - self.assertEqual(args[0][0], 'POST') - self.assertEqual(args[0][1], url_prefix + 'volumes') - data = json.loads(args[1]['data']) - self.assertIn('Driver', data) - self.assertEqual(data['Driver'], driver_name) - - @base.requires_api_version('1.21') - def test_create_volume_invalid_opts_type(self): - with pytest.raises(TypeError): - self.client.create_volume( - 'perfectcherryblossom', driver_opts='hello=world' - ) - - with pytest.raises(TypeError): - self.client.create_volume( - 'perfectcherryblossom', driver_opts=['hello=world'] - ) - - with pytest.raises(TypeError): - self.client.create_volume( - 'perfectcherryblossom', driver_opts='' - ) - - @base.requires_api_version('1.21') - def test_inspect_volume(self): - name = 'perfectcherryblossom' - result = self.client.inspect_volume(name) - self.assertIn('Name', result) - self.assertEqual(result['Name'], name) - self.assertIn('Driver', result) - self.assertEqual(result['Driver'], 'local') - args = fake_request.call_args - - self.assertEqual(args[0][0], 'GET') - self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) - - @base.requires_api_version('1.21') - def test_remove_volume(self): - name = 'perfectcherryblossom' - result = self.client.remove_volume(name) - self.assertTrue(result) - args = fake_request.call_args - - self.assertEqual(args[0][0], 'DELETE') - self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) - - ##################### - # NETWORK TESTS # - ##################### - - @base.requires_api_version('1.21') - def test_create_network(self): - network_data = { - "id": 'abc12345', - "warning": "", - } - - network_response = response(status_code=200, content=network_data) - post = mock.Mock(return_value=network_response) - - with mock.patch('docker.Client.post', post): - result = self.client.create_network('foo') - self.assertEqual(result, network_data) - - self.assertEqual( - post.call_args[0][0], - url_prefix + 'networks/create') - - self.assertEqual( - json.loads(post.call_args[1]['data']), - {"name": "foo"}) - - self.client.create_network('foo', 'bridge') - - self.assertEqual( - json.loads(post.call_args[1]['data']), - {"name": "foo", "driver": "bridge"}) - - @base.requires_api_version('1.21') - def test_remove_network(self): - network_id = 'abc12345' - delete = mock.Mock(return_value=response(status_code=200)) - - with mock.patch('docker.Client.delete', delete): - self.client.remove_network(network_id) - - args = delete.call_args - self.assertEqual(args[0][0], - url_prefix + 'networks/{0}'.format(network_id)) - - @base.requires_api_version('1.21') - def test_inspect_network(self): - network_id = 'abc12345' - network_name = 'foo' - network_data = { - six.u('name'): network_name, - six.u('id'): network_id, - six.u('driver'): 'bridge', - six.u('containers'): {}, - } - - network_response = response(status_code=200, content=network_data) - get = mock.Mock(return_value=network_response) - - with mock.patch('docker.Client.get', get): - result = self.client.inspect_network(network_id) - self.assertEqual(result, network_data) - - args = get.call_args - self.assertEqual(args[0][0], - url_prefix + 'networks/{0}'.format(network_id)) - - @base.requires_api_version('1.21') - def test_connect_container_to_network(self): - network_id = 'abc12345' - container_id = 'def45678' - - post = mock.Mock(return_value=response(status_code=201)) - - with mock.patch('docker.Client.post', post): - self.client.connect_container_to_network( - {'Id': container_id}, network_id) - - self.assertEqual( - post.call_args[0][0], - url_prefix + 'networks/{0}/connect'.format(network_id)) - - self.assertEqual( - json.loads(post.call_args[1]['data']), - {'container': container_id}) - - @base.requires_api_version('1.21') - def test_disconnect_container_from_network(self): - network_id = 'abc12345' - container_id = 'def45678' - - post = mock.Mock(return_value=response(status_code=201)) - - with mock.patch('docker.Client.post', post): - self.client.disconnect_container_from_network( - {'Id': container_id}, network_id) - - self.assertEqual( - post.call_args[0][0], - url_prefix + 'networks/{0}/disconnect'.format(network_id)) - - self.assertEqual( - json.loads(post.call_args[1]['data']), - {'container': container_id}) - - ####################### - # PY SPECIFIC TESTS # - ####################### - - def test_load_config_no_file(self): - folder = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, folder) - cfg = docker.auth.load_config(folder) - self.assertTrue(cfg is not None) - - def test_load_config(self): - folder = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, folder) - dockercfg_path = os.path.join(folder, '.dockercfg') - with open(dockercfg_path, 'w') as f: - auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') - f.write('auth = {0}\n'.format(auth_)) - f.write('email = sakuya@scarlet.net') - cfg = docker.auth.load_config(dockercfg_path) - self.assertTrue(docker.auth.INDEX_NAME in cfg) - self.assertNotEqual(cfg[docker.auth.INDEX_NAME], None) - cfg = cfg[docker.auth.INDEX_NAME] - self.assertEqual(cfg['username'], 'sakuya') - self.assertEqual(cfg['password'], 'izayoi') - self.assertEqual(cfg['email'], 'sakuya@scarlet.net') - self.assertEqual(cfg.get('auth'), None) - - def test_load_config_with_random_name(self): - folder = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, folder) - - dockercfg_path = os.path.join(folder, - '.{0}.dockercfg'.format( - random.randrange(100000))) - registry = 'https://your.private.registry.io' - auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') - config = { - registry: { - 'auth': '{0}'.format(auth_), - 'email': 'sakuya@scarlet.net' - } - } - - with open(dockercfg_path, 'w') as f: - f.write(json.dumps(config)) - - cfg = docker.auth.load_config(dockercfg_path) - self.assertTrue(registry in cfg) - self.assertNotEqual(cfg[registry], None) - cfg = cfg[registry] - self.assertEqual(cfg['username'], 'sakuya') - self.assertEqual(cfg['password'], 'izayoi') - self.assertEqual(cfg['email'], 'sakuya@scarlet.net') - self.assertEqual(cfg.get('auth'), None) - - def test_tar_with_excludes(self): - dirs = [ - 'foo', - 'foo/bar', - 'bar', - ] - - files = [ - 'Dockerfile', - 'Dockerfile.alt', - '.dockerignore', - 'a.py', - 'a.go', - 'b.py', - 'cde.py', - 'foo/a.py', - 'foo/b.py', - 'foo/bar/a.py', - 'bar/a.py', - ] - - exclude = [ - '*.py', - '!b.py', - '!a.go', - 'foo', - 'Dockerfile*', - '.dockerignore', - ] - - expected_names = set([ - 'Dockerfile', - '.dockerignore', - 'a.go', - 'b.py', - 'bar', - 'bar/a.py', - ]) - - base = make_tree(dirs, files) - self.addCleanup(shutil.rmtree, base) - - with docker.utils.tar(base, exclude=exclude) as archive: - tar = tarfile.open(fileobj=archive) - assert sorted(tar.getnames()) == sorted(expected_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)) - with docker.utils.tar(base) as archive: - 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')) - with docker.utils.tar(base) as archive: - 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')) - with docker.utils.tar(base) as archive: - tar = tarfile.open(fileobj=archive) - self.assertEqual(sorted(tar.getnames()), ['bar', 'bar/foo', 'foo']) - - ####################### - # HOST CONFIG TESTS # - ####################### - - def test_create_host_config_secopt(self): - security_opt = ['apparmor:test_profile'] - result = self.client.create_host_config(security_opt=security_opt) - self.assertIn('SecurityOpt', result) - self.assertEqual(result['SecurityOpt'], security_opt) - - self.assertRaises( - docker.errors.DockerException, self.client.create_host_config, - security_opt='wrong' - ) - - -class StreamTest(Cleanup, base.BaseTestCase): - - def setUp(self): - socket_dir = tempfile.mkdtemp() - self.build_context = tempfile.mkdtemp() - self.addCleanup(shutil.rmtree, socket_dir) - self.addCleanup(shutil.rmtree, self.build_context) - self.socket_file = os.path.join(socket_dir, 'test_sock.sock') - self.server_socket = self._setup_socket() - self.stop_server = False - server_thread = threading.Thread(target=self.run_server) - server_thread.setDaemon(True) - server_thread.start() - self.response = None - self.request_handler = None - self.addCleanup(server_thread.join) - self.addCleanup(self.stop) - - def stop(self): - self.stop_server = True - - def _setup_socket(self): - server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) - server_sock.bind(self.socket_file) - # Non-blocking mode so that we can shut the test down easily - server_sock.setblocking(0) - server_sock.listen(5) - return server_sock - - def run_server(self): - try: - while not self.stop_server: - try: - connection, client_address = self.server_socket.accept() - except socket.error: - # Probably no connection to accept yet - time.sleep(0.01) - continue - - connection.setblocking(1) - try: - self.request_handler(connection) - finally: - connection.close() - finally: - self.server_socket.close() - - def early_response_sending_handler(self, connection): - data = b'' - headers = None - - connection.sendall(self.response) - while not headers: - data += connection.recv(2048) - parts = data.split(b'\r\n\r\n', 1) - if len(parts) == 2: - headers, data = parts - - mo = re.search(r'Content-Length: ([0-9]+)', headers.decode()) - assert mo - content_length = int(mo.group(1)) - - while True: - if len(data) >= content_length: - break - - data += connection.recv(2048) - - def test_early_stream_response(self): - self.request_handler = self.early_response_sending_handler - lines = [] - for i in range(0, 50): - line = str(i).encode() - lines += [('%x' % len(line)).encode(), line] - lines.append(b'0') - lines.append(b'') - - self.response = ( - b'HTTP/1.1 200 OK\r\n' - b'Transfer-Encoding: chunked\r\n' - b'\r\n' - ) + b'\r\n'.join(lines) - - with docker.Client(base_url="http+unix://" + self.socket_file) \ - as client: - for i in range(5): - try: - stream = client.build( - path=self.build_context, - stream=True - ) - break - except requests.ConnectionError as e: - if i == 4: - raise e - - self.assertEqual(list(stream), [ - str(i).encode() for i in range(50)]) diff -Nru python-docker-1.5.0/tests/unit/api_test.py python-docker-1.8.0/tests/unit/api_test.py --- python-docker-1.5.0/tests/unit/api_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/api_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,417 @@ +# Copyright 2013 dotCloud inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import datetime +import json +import os +import re +import shutil +import socket +import sys +import tempfile +import threading +import time + +import docker +import requests +import six + +from .. import base +from . import fake_api + +import pytest + +try: + from unittest import mock +except ImportError: + import mock + + +DEFAULT_TIMEOUT_SECONDS = docker.constants.DEFAULT_TIMEOUT_SECONDS + + +def response(status_code=200, content='', headers=None, reason=None, elapsed=0, + request=None): + res = requests.Response() + res.status_code = status_code + if not isinstance(content, six.binary_type): + content = json.dumps(content).encode('ascii') + res._content = content + res.headers = requests.structures.CaseInsensitiveDict(headers or {}) + res.reason = reason + res.elapsed = datetime.timedelta(elapsed) + res.request = request + return res + + +def fake_resolve_authconfig(authconfig, registry=None): + return None + + +def fake_inspect_container(self, container, tty=False): + return fake_api.get_fake_inspect_container(tty=tty)[1] + + +def fake_resp(method, url, *args, **kwargs): + key = None + if url in fake_api.fake_responses: + key = url + elif (url, method) in fake_api.fake_responses: + key = (url, method) + if not key: + raise Exception('{0} {1}'.format(method, url)) + status_code, content = fake_api.fake_responses[key]() + return response(status_code=status_code, content=content) + + +fake_request = mock.Mock(side_effect=fake_resp) + + +def fake_get(self, url, *args, **kwargs): + return fake_request('GET', url, *args, **kwargs) + + +def fake_post(self, url, *args, **kwargs): + return fake_request('POST', url, *args, **kwargs) + + +def fake_put(self, url, *args, **kwargs): + return fake_request('PUT', url, *args, **kwargs) + + +def fake_delete(self, url, *args, **kwargs): + return fake_request('DELETE', url, *args, **kwargs) + +url_base = 'http+docker://localunixsocket/' +url_prefix = '{0}v{1}/'.format( + url_base, + docker.constants.DEFAULT_DOCKER_API_VERSION) + + +class DockerClientTest(base.Cleanup, base.BaseTestCase): + def setUp(self): + self.patcher = mock.patch.multiple( + 'docker.Client', get=fake_get, post=fake_post, put=fake_put, + delete=fake_delete + ) + self.patcher.start() + self.client = docker.Client() + # Force-clear authconfig to avoid tampering with the tests + self.client._cfg = {'Configs': {}} + + def tearDown(self): + self.client.close() + self.patcher.stop() + + def assertIn(self, object, collection): + if six.PY2 and sys.version_info[1] <= 6: + return self.assertTrue(object in collection) + return super(DockerClientTest, self).assertIn(object, collection) + + def base_create_payload(self, img='busybox', cmd=None): + if not cmd: + cmd = ['true'] + return {"Tty": False, "Image": img, "Cmd": cmd, + "AttachStdin": False, + "AttachStderr": True, "AttachStdout": True, + "StdinOnce": False, + "OpenStdin": False, "NetworkDisabled": False, + } + + +class DockerApiTest(DockerClientTest): + def test_ctor(self): + with pytest.raises(docker.errors.DockerException) as excinfo: + docker.Client(version=1.12) + + self.assertEqual( + str(excinfo.value), + 'Version parameter must be a string or None. Found float' + ) + + def test_url_valid_resource(self): + url = self.client._url('/hello/{0}/world', 'somename') + self.assertEqual( + url, '{0}{1}'.format(url_prefix, 'hello/somename/world') + ) + + url = self.client._url( + '/hello/{0}/world/{1}', 'somename', 'someothername' + ) + self.assertEqual( + url, + '{0}{1}'.format(url_prefix, 'hello/somename/world/someothername') + ) + + url = self.client._url('/hello/{0}/world', '/some?name') + self.assertEqual( + url, '{0}{1}'.format(url_prefix, 'hello/%2Fsome%3Fname/world') + ) + + def test_url_invalid_resource(self): + with pytest.raises(ValueError): + self.client._url('/hello/{0}/world', ['sakuya', 'izayoi']) + + def test_url_no_resource(self): + url = self.client._url('/simple') + self.assertEqual(url, '{0}{1}'.format(url_prefix, 'simple')) + + def test_url_unversioned_api(self): + url = self.client._url( + '/hello/{0}/world', 'somename', versioned_api=False + ) + self.assertEqual( + url, '{0}{1}'.format(url_base, 'hello/somename/world') + ) + + def test_version(self): + self.client.version() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'version', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_version_no_api_version(self): + self.client.version(False) + + fake_request.assert_called_with( + 'GET', + url_base + 'version', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_retrieve_server_version(self): + client = docker.Client(version="auto") + self.assertTrue(isinstance(client._version, six.string_types)) + self.assertFalse(client._version == "auto") + client.close() + + def test_auto_retrieve_server_version(self): + version = self.client._retrieve_server_version() + self.assertTrue(isinstance(version, six.string_types)) + + def test_info(self): + self.client.info() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'info', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_search(self): + self.client.search('busybox') + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/search', + params={'term': 'busybox'}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_events(self): + self.client.events() + + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={'since': None, 'until': None, 'filters': None}, + stream=True + ) + + def test_events_with_since_until(self): + ts = 1356048000 + now = datetime.datetime.utcfromtimestamp(ts) + since = now - datetime.timedelta(seconds=10) + until = now + datetime.timedelta(seconds=10) + + self.client.events(since=since, until=until) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={ + 'since': ts - 10, + 'until': ts + 10, + 'filters': None + }, + stream=True + ) + + def test_events_with_filters(self): + filters = {'event': ['die', 'stop'], + 'container': fake_api.FAKE_CONTAINER_ID} + + self.client.events(filters=filters) + + expected_filters = docker.utils.convert_filters(filters) + fake_request.assert_called_with( + 'GET', + url_prefix + 'events', + params={ + 'since': None, + 'until': None, + 'filters': expected_filters + }, + stream=True + ) + + def _socket_path_for_client_session(self, client): + socket_adapter = client.get_adapter('http+docker://') + return socket_adapter.socket_path + + def test_url_compatibility_unix(self): + c = docker.Client(base_url="unix://socket") + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_unix_triple_slash(self): + c = docker.Client(base_url="unix:///socket") + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_http_unix_triple_slash(self): + c = docker.Client(base_url="http+unix:///socket") + + assert self._socket_path_for_client_session(c) == '/socket' + + def test_url_compatibility_http(self): + c = docker.Client(base_url="http://hostname:1234") + + assert c.base_url == "http://hostname:1234" + + def test_url_compatibility_tcp(self): + c = docker.Client(base_url="tcp://hostname:1234") + + assert c.base_url == "http://hostname:1234" + + def test_remove_link(self): + self.client.remove_container(fake_api.FAKE_CONTAINER_ID, link=True) + + fake_request.assert_called_with( + 'DELETE', + url_prefix + 'containers/3cc2351ab11b', + params={'v': False, 'link': True, 'force': False}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_host_config_secopt(self): + security_opt = ['apparmor:test_profile'] + result = self.client.create_host_config(security_opt=security_opt) + self.assertIn('SecurityOpt', result) + self.assertEqual(result['SecurityOpt'], security_opt) + self.assertRaises( + TypeError, self.client.create_host_config, security_opt='wrong' + ) + + +class StreamTest(base.Cleanup, base.BaseTestCase): + def setUp(self): + socket_dir = tempfile.mkdtemp() + self.build_context = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, socket_dir) + self.addCleanup(shutil.rmtree, self.build_context) + self.socket_file = os.path.join(socket_dir, 'test_sock.sock') + self.server_socket = self._setup_socket() + self.stop_server = False + server_thread = threading.Thread(target=self.run_server) + server_thread.setDaemon(True) + server_thread.start() + self.response = None + self.request_handler = None + self.addCleanup(server_thread.join) + self.addCleanup(self.stop) + + def stop(self): + self.stop_server = True + + def _setup_socket(self): + server_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) + server_sock.bind(self.socket_file) + # Non-blocking mode so that we can shut the test down easily + server_sock.setblocking(0) + server_sock.listen(5) + return server_sock + + def run_server(self): + try: + while not self.stop_server: + try: + connection, client_address = self.server_socket.accept() + except socket.error: + # Probably no connection to accept yet + time.sleep(0.01) + continue + + connection.setblocking(1) + try: + self.request_handler(connection) + finally: + connection.close() + finally: + self.server_socket.close() + + def early_response_sending_handler(self, connection): + data = b'' + headers = None + + connection.sendall(self.response) + while not headers: + data += connection.recv(2048) + parts = data.split(b'\r\n\r\n', 1) + if len(parts) == 2: + headers, data = parts + + mo = re.search(r'Content-Length: ([0-9]+)', headers.decode()) + assert mo + content_length = int(mo.group(1)) + + while True: + if len(data) >= content_length: + break + + data += connection.recv(2048) + + def test_early_stream_response(self): + self.request_handler = self.early_response_sending_handler + lines = [] + for i in range(0, 50): + line = str(i).encode() + lines += [('%x' % len(line)).encode(), line] + lines.append(b'0') + lines.append(b'') + + self.response = ( + b'HTTP/1.1 200 OK\r\n' + b'Transfer-Encoding: chunked\r\n' + b'\r\n' + ) + b'\r\n'.join(lines) + + with docker.Client(base_url="http+unix://" + self.socket_file) \ + as client: + for i in range(5): + try: + stream = client.build( + path=self.build_context, + stream=True + ) + break + except requests.ConnectionError as e: + if i == 4: + raise e + + self.assertEqual(list(stream), [ + str(i).encode() for i in range(50)]) diff -Nru python-docker-1.5.0/tests/unit/auth_test.py python-docker-1.8.0/tests/unit/auth_test.py --- python-docker-1.5.0/tests/unit/auth_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/auth_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,463 @@ +# -*- coding: utf-8 -*- + +import base64 +import json +import os +import os.path +import random +import shutil +import tempfile + +from docker import auth +from docker.auth.auth import parse_auth +from docker import errors + +from .. import base + +try: + from unittest import mock +except ImportError: + import mock + + +class RegressionTest(base.BaseTestCase): + def test_803_urlsafe_encode(self): + auth_data = { + 'username': 'root', + 'password': 'GR?XGR?XGR?XGR?X' + } + encoded = auth.encode_header(auth_data) + assert b'/' not in encoded + assert b'_' in encoded + + +class ResolveRepositoryNameTest(base.BaseTestCase): + def test_resolve_repository_name_hub_library_image(self): + self.assertEqual( + auth.resolve_repository_name('image'), + ('docker.io', 'image'), + ) + + def test_resolve_repository_name_dotted_hub_library_image(self): + self.assertEqual( + auth.resolve_repository_name('image.valid'), + ('docker.io', 'image.valid') + ) + + def test_resolve_repository_name_hub_image(self): + self.assertEqual( + auth.resolve_repository_name('username/image'), + ('docker.io', 'username/image'), + ) + + def test_explicit_hub_index_library_image(self): + self.assertEqual( + auth.resolve_repository_name('docker.io/image'), + ('docker.io', 'image') + ) + + def test_explicit_legacy_hub_index_library_image(self): + self.assertEqual( + auth.resolve_repository_name('index.docker.io/image'), + ('docker.io', 'image') + ) + + def test_resolve_repository_name_private_registry(self): + self.assertEqual( + auth.resolve_repository_name('my.registry.net/image'), + ('my.registry.net', 'image'), + ) + + def test_resolve_repository_name_private_registry_with_port(self): + self.assertEqual( + auth.resolve_repository_name('my.registry.net:5000/image'), + ('my.registry.net:5000', 'image'), + ) + + def test_resolve_repository_name_private_registry_with_username(self): + self.assertEqual( + auth.resolve_repository_name('my.registry.net/username/image'), + ('my.registry.net', 'username/image'), + ) + + def test_resolve_repository_name_no_dots_but_port(self): + self.assertEqual( + auth.resolve_repository_name('hostname:5000/image'), + ('hostname:5000', 'image'), + ) + + def test_resolve_repository_name_no_dots_but_port_and_username(self): + self.assertEqual( + auth.resolve_repository_name('hostname:5000/username/image'), + ('hostname:5000', 'username/image'), + ) + + def test_resolve_repository_name_localhost(self): + self.assertEqual( + auth.resolve_repository_name('localhost/image'), + ('localhost', 'image'), + ) + + def test_resolve_repository_name_localhost_with_username(self): + self.assertEqual( + auth.resolve_repository_name('localhost/username/image'), + ('localhost', 'username/image'), + ) + + def test_invalid_index_name(self): + self.assertRaises( + errors.InvalidRepository, + lambda: auth.resolve_repository_name('-gecko.com/image') + ) + + +def encode_auth(auth_info): + return base64.b64encode( + auth_info.get('username', '').encode('utf-8') + b':' + + auth_info.get('password', '').encode('utf-8')) + + +class ResolveAuthTest(base.BaseTestCase): + index_config = {'auth': encode_auth({'username': 'indexuser'})} + private_config = {'auth': encode_auth({'username': 'privateuser'})} + legacy_config = {'auth': encode_auth({'username': 'legacyauth'})} + + auth_config = parse_auth({ + 'https://index.docker.io/v1/': index_config, + 'my.registry.net': private_config, + 'http://legacy.registry.url/v1/': legacy_config, + }) + + def test_resolve_authconfig_hostname_only(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'my.registry.net' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_no_protocol(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'my.registry.net/v1/' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_no_path(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_no_path_trailing_slash(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net/' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_no_path_wrong_secure_proto(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'https://my.registry.net' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_no_path_wrong_insecure_proto(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'http://index.docker.io' + )['username'], + 'indexuser' + ) + + def test_resolve_authconfig_path_wrong_proto(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'https://my.registry.net/v1/' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_default_registry(self): + self.assertEqual( + auth.resolve_authconfig(self.auth_config)['username'], + 'indexuser' + ) + + def test_resolve_authconfig_default_explicit_none(self): + self.assertEqual( + auth.resolve_authconfig(self.auth_config, None)['username'], + 'indexuser' + ) + + def test_resolve_authconfig_fully_explicit(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'http://my.registry.net/v1/' + )['username'], + 'privateuser' + ) + + def test_resolve_authconfig_legacy_config(self): + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, 'legacy.registry.url' + )['username'], + 'legacyauth' + ) + + def test_resolve_authconfig_no_match(self): + self.assertTrue( + auth.resolve_authconfig(self.auth_config, 'does.not.exist') is None + ) + + def test_resolve_registry_and_auth_library_image(self): + image = 'image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'], + 'indexuser', + ) + + def test_resolve_registry_and_auth_hub_image(self): + image = 'username/image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'], + 'indexuser', + ) + + def test_resolve_registry_and_auth_explicit_hub(self): + image = 'docker.io/username/image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'], + 'indexuser', + ) + + def test_resolve_registry_and_auth_explicit_legacy_hub(self): + image = 'index.docker.io/username/image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'], + 'indexuser', + ) + + def test_resolve_registry_and_auth_private_registry(self): + image = 'my.registry.net/image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + )['username'], + 'privateuser', + ) + + def test_resolve_registry_and_auth_unauthenticated_registry(self): + image = 'other.registry.net/image' + self.assertEqual( + auth.resolve_authconfig( + self.auth_config, auth.resolve_repository_name(image)[0] + ), + None, + ) + + +class LoadConfigTest(base.Cleanup, base.BaseTestCase): + def test_load_config_no_file(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + cfg = auth.load_config(folder) + self.assertTrue(cfg is not None) + + def test_load_config(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, '.dockercfg') + with open(dockercfg_path, 'w') as f: + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + f.write('auth = {0}\n'.format(auth_)) + f.write('email = sakuya@scarlet.net') + cfg = auth.load_config(dockercfg_path) + assert auth.INDEX_NAME in cfg + self.assertNotEqual(cfg[auth.INDEX_NAME], None) + cfg = cfg[auth.INDEX_NAME] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('auth'), None) + + def test_load_config_with_random_name(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, + '.{0}.dockercfg'.format( + random.randrange(100000))) + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + registry: { + 'auth': '{0}'.format(auth_), + 'email': 'sakuya@scarlet.net' + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert registry in cfg + self.assertNotEqual(cfg[registry], None) + cfg = cfg[registry] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('auth'), None) + + def test_load_config_custom_config_env(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + registry: { + 'auth': '{0}'.format(auth_), + 'email': 'sakuya@scarlet.net' + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert registry in cfg + self.assertNotEqual(cfg[registry], None) + cfg = cfg[registry] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('auth'), None) + + def test_load_config_custom_config_env_with_auths(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode(b'sakuya:izayoi').decode('ascii') + config = { + 'auths': { + registry: { + 'auth': '{0}'.format(auth_), + 'email': 'sakuya@scarlet.net' + } + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert registry in cfg + self.assertNotEqual(cfg[registry], None) + cfg = cfg[registry] + self.assertEqual(cfg['username'], 'sakuya') + self.assertEqual(cfg['password'], 'izayoi') + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('auth'), None) + + def test_load_config_custom_config_env_utf8(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + registry = 'https://your.private.registry.io' + auth_ = base64.b64encode( + b'sakuya\xc3\xa6:izayoi\xc3\xa6').decode('ascii') + config = { + 'auths': { + registry: { + 'auth': '{0}'.format(auth_), + 'email': 'sakuya@scarlet.net' + } + } + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert registry in cfg + self.assertNotEqual(cfg[registry], None) + cfg = cfg[registry] + self.assertEqual(cfg['username'], b'sakuya\xc3\xa6'.decode('utf8')) + self.assertEqual(cfg['password'], b'izayoi\xc3\xa6'.decode('utf8')) + self.assertEqual(cfg['email'], 'sakuya@scarlet.net') + self.assertEqual(cfg.get('auth'), None) + + def test_load_config_custom_config_env_with_headers(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + + dockercfg_path = os.path.join(folder, 'config.json') + config = { + 'HttpHeaders': { + 'Name': 'Spike', + 'Surname': 'Spiegel' + }, + } + + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + with mock.patch.dict(os.environ, {'DOCKER_CONFIG': folder}): + cfg = auth.load_config(None) + assert 'HttpHeaders' in cfg + self.assertNotEqual(cfg['HttpHeaders'], None) + cfg = cfg['HttpHeaders'] + + self.assertEqual(cfg['Name'], 'Spike') + self.assertEqual(cfg['Surname'], 'Spiegel') + + def test_load_config_unknown_keys(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config = { + 'detachKeys': 'ctrl-q, ctrl-u, ctrl-i' + } + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert cfg == {} + + def test_load_config_invalid_auth_dict(self): + folder = tempfile.mkdtemp() + self.addCleanup(shutil.rmtree, folder) + dockercfg_path = os.path.join(folder, 'config.json') + config = { + 'auths': { + 'scarlet.net': {'sakuya': 'izayoi'} + } + } + with open(dockercfg_path, 'w') as f: + json.dump(config, f) + + cfg = auth.load_config(dockercfg_path) + assert cfg == {} diff -Nru python-docker-1.5.0/tests/unit/build_test.py python-docker-1.8.0/tests/unit/build_test.py --- python-docker-1.5.0/tests/unit/build_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/build_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,105 @@ +import gzip +import io + +import docker + +from .api_test import DockerClientTest + + +class BuildTest(DockerClientTest): + def test_build_container(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + + self.client.build(fileobj=script) + + def test_build_container_pull(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + + self.client.build(fileobj=script, pull=True) + + def test_build_container_stream(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + + self.client.build(fileobj=script, stream=True) + + def test_build_container_custom_context(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + context = docker.utils.mkbuildcontext(script) + + self.client.build(fileobj=context, custom_context=True) + + def test_build_container_custom_context_gzip(self): + script = io.BytesIO('\n'.join([ + 'FROM busybox', + 'MAINTAINER docker-py', + 'RUN mkdir -p /tmp/test', + 'EXPOSE 8080', + 'ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz' + ' /tmp/silence.tar.gz' + ]).encode('ascii')) + context = docker.utils.mkbuildcontext(script) + gz_context = gzip.GzipFile(fileobj=context) + + self.client.build( + fileobj=gz_context, + custom_context=True, + encoding="gzip" + ) + + def test_build_remote_with_registry_auth(self): + self.client._auth_configs = { + 'https://example.com': { + 'user': 'example', + 'password': 'example', + 'email': 'example@example.com' + } + } + + self.client.build(path='https://github.com/docker-library/mongo') + + def test_build_container_with_named_dockerfile(self): + self.client.build('.', dockerfile='nameddockerfile') + + def test_build_container_with_container_limits(self): + self.client.build('.', container_limits={ + 'memory': 1024 * 1024, + 'cpusetcpus': 1, + 'cpushares': 1000, + 'memswap': 1024 * 1024 * 8 + }) + + def test_build_container_invalid_container_limits(self): + self.assertRaises( + docker.errors.DockerException, + lambda: self.client.build('.', container_limits={ + 'foo': 'bar' + }) + ) diff -Nru python-docker-1.5.0/tests/unit/client_test.py python-docker-1.8.0/tests/unit/client_test.py --- python-docker-1.5.0/tests/unit/client_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/client_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,26 @@ +import os +from docker.client import Client +from .. import base + +TEST_CERT_DIR = os.path.join( + os.path.dirname(__file__), + 'testdata/certs', +) + + +class ClientTest(base.BaseTestCase): + def setUp(self): + self.os_environ = os.environ.copy() + + def tearDown(self): + os.environ = self.os_environ + + def test_from_env(self): + """Test that environment variables are passed through to + utils.kwargs_from_env(). KwargsFromEnvTest tests that environment + variables are parsed correctly.""" + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='1') + client = Client.from_env() + self.assertEqual(client.base_url, "https://192.168.59.103:2376") diff -Nru python-docker-1.5.0/tests/unit/container_test.py python-docker-1.8.0/tests/unit/container_test.py --- python-docker-1.5.0/tests/unit/container_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/container_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,1530 @@ +import datetime +import json +import signal + +import docker +import pytest +import six + +from . import fake_api +from ..base import requires_api_version +from .api_test import ( + DockerClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, + fake_inspect_container +) + +try: + from unittest import mock +except ImportError: + import mock + + +def fake_inspect_container_tty(self, container): + return fake_inspect_container(self, container, tty=True) + + +class StartContainerTest(DockerClientTest): + def test_start_container(self): + self.client.start(fake_api.FAKE_CONTAINER_ID) + + args = fake_request.call_args + self.assertEqual( + args[0][1], + url_prefix + 'containers/3cc2351ab11b/start' + ) + self.assertEqual(json.loads(args[1]['data']), {}) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_start_container_none(self): + with pytest.raises(ValueError) as excinfo: + self.client.start(container=None) + + self.assertEqual( + str(excinfo.value), + 'image or container param is undefined', + ) + + with pytest.raises(ValueError) as excinfo: + self.client.start(None) + + self.assertEqual( + str(excinfo.value), + 'image or container param is undefined', + ) + + def test_start_container_regression_573(self): + self.client.start(**{'container': fake_api.FAKE_CONTAINER_ID}) + + def test_start_container_with_lxc_conf(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, + lxc_conf={'lxc.conf.k': 'lxc.conf.value'} + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_lxc_conf_compat(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, + lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_binds_ro(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, binds={ + '/tmp': { + "bind": '/mnt', + "ro": True + } + } + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_binds_rw(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, binds={ + '/tmp': {"bind": '/mnt', "ro": False} + } + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_port_binds(self): + self.maxDiff = None + + def call_start(): + self.client.start(fake_api.FAKE_CONTAINER_ID, port_bindings={ + 1111: None, + 2222: 2222, + '3333/udp': (3333,), + 4444: ('127.0.0.1',), + 5555: ('127.0.0.1', 5555), + 6666: [('127.0.0.1',), ('192.168.0.1',)] + }) + + pytest.deprecated_call(call_start) + + def test_start_container_with_links(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, links={'path': 'alias'} + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_multiple_links(self): + def call_start(): + self.client.start( + fake_api.FAKE_CONTAINER_ID, + links={ + 'path1': 'alias1', + 'path2': 'alias2' + } + ) + + pytest.deprecated_call(call_start) + + def test_start_container_with_links_as_list_of_tuples(self): + def call_start(): + self.client.start(fake_api.FAKE_CONTAINER_ID, + links=[('path', 'alias')]) + + pytest.deprecated_call(call_start) + + def test_start_container_privileged(self): + def call_start(): + self.client.start(fake_api.FAKE_CONTAINER_ID, privileged=True) + + pytest.deprecated_call(call_start) + + def test_start_container_with_dict_instead_of_id(self): + self.client.start({'Id': fake_api.FAKE_CONTAINER_ID}) + + args = fake_request.call_args + self.assertEqual( + args[0][1], + url_prefix + 'containers/3cc2351ab11b/start' + ) + self.assertEqual(json.loads(args[1]['data']), {}) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + +class CreateContainerTest(DockerClientTest): + def test_create_container(self): + self.client.create_container('busybox', 'true') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", "Cmd": ["true"], + "AttachStdin": false, + "AttachStderr": true, "AttachStdout": true, + "StdinOnce": false, + "OpenStdin": false, "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_binds(self): + mount_dest = '/mnt' + + self.client.create_container('busybox', ['ls', mount_dest], + volumes=[mount_dest]) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls", "/mnt"], "AttachStdin": false, + "Volumes": {"/mnt": {}}, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_volume_string(self): + mount_dest = '/mnt' + + self.client.create_container('busybox', ['ls', mount_dest], + volumes=mount_dest) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls", "/mnt"], "AttachStdin": false, + "Volumes": {"/mnt": {}}, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_ports(self): + self.client.create_container('busybox', 'ls', + ports=[1111, (2222, 'udp'), (3333,)]) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "ExposedPorts": { + "1111/tcp": {}, + "2222/udp": {}, + "3333/tcp": {} + }, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_entrypoint(self): + self.client.create_container('busybox', 'hello', + entrypoint='cowsay entry') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["hello"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "Entrypoint": ["cowsay", "entry"]}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_cpu_shares(self): + self.client.create_container('busybox', 'ls', + cpu_shares=5) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "CpuShares": 5}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_cpuset(self): + self.client.create_container('busybox', 'ls', + cpuset='0,1') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "Cpuset": "0,1", + "CpusetCpus": "0,1"}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_cgroup_parent(self): + self.client.create_container( + 'busybox', 'ls', host_config=self.client.create_host_config( + cgroup_parent='test' + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + data = json.loads(args[1]['data']) + self.assertIn('HostConfig', data) + self.assertIn('CgroupParent', data['HostConfig']) + self.assertEqual(data['HostConfig']['CgroupParent'], 'test') + + def test_create_container_with_working_dir(self): + self.client.create_container('busybox', 'ls', + working_dir='/root') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "WorkingDir": "/root"}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_stdin_open(self): + self.client.create_container('busybox', 'true', stdin_open=True) + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", "Cmd": ["true"], + "AttachStdin": true, + "AttachStderr": true, "AttachStdout": true, + "StdinOnce": true, + "OpenStdin": true, "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_with_volumes_from(self): + vol_names = ['foo', 'bar'] + try: + self.client.create_container('busybox', 'true', + volumes_from=vol_names) + except docker.errors.DockerException: + self.assertTrue( + docker.utils.compare_version('1.10', self.client._version) >= 0 + ) + return + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data'])['VolumesFrom'], + ','.join(vol_names)) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_create_container_empty_volumes_from(self): + self.client.create_container('busybox', 'true', volumes_from=[]) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertTrue('VolumesFrom' not in data) + + def test_create_named_container(self): + self.client.create_container('busybox', 'true', + name='marisa-kirisame') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", "Cmd": ["true"], + "AttachStdin": false, + "AttachStderr": true, "AttachStdout": true, + "StdinOnce": false, + "OpenStdin": false, "NetworkDisabled": false}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual(args[1]['params'], {'name': 'marisa-kirisame'}) + + def test_create_container_with_mem_limit_as_int(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + mem_limit=128.0 + ) + ) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertEqual(data['HostConfig']['Memory'], 128.0) + + def test_create_container_with_mem_limit_as_string(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + mem_limit='128' + ) + ) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertEqual(data['HostConfig']['Memory'], 128.0) + + def test_create_container_with_mem_limit_as_string_with_k_unit(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + mem_limit='128k' + ) + ) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024) + + def test_create_container_with_mem_limit_as_string_with_m_unit(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + mem_limit='128m' + ) + ) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024 * 1024) + + def test_create_container_with_mem_limit_as_string_with_g_unit(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + mem_limit='128g' + ) + ) + + args = fake_request.call_args + data = json.loads(args[1]['data']) + self.assertEqual( + data['HostConfig']['Memory'], 128.0 * 1024 * 1024 * 1024 + ) + + def test_create_container_with_mem_limit_as_string_with_wrong_value(self): + self.assertRaises( + docker.errors.DockerException, + self.client.create_host_config, mem_limit='128p' + ) + + self.assertRaises( + docker.errors.DockerException, + self.client.create_host_config, mem_limit='1f28' + ) + + def test_create_container_with_lxc_conf(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + lxc_conf={'lxc.conf.k': 'lxc.conf.value'} + ) + ) + + args = fake_request.call_args + self.assertEqual( + args[0][1], + url_prefix + 'containers/create' + ) + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['LxcConf'] = [ + {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} + ] + + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], + {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_lxc_conf_compat(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + lxc_conf=[{'Key': 'lxc.conf.k', 'Value': 'lxc.conf.value'}] + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['LxcConf'] = [ + {"Value": "lxc.conf.value", "Key": "lxc.conf.k"} + ] + self.assertEqual( + json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_binds_ro(self): + mount_dest = '/mnt' + mount_origin = '/tmp' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + binds={mount_origin: { + "bind": mount_dest, + "ro": True + }} + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:ro"] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_binds_rw(self): + mount_dest = '/mnt' + mount_origin = '/tmp' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + binds={mount_origin: { + "bind": mount_dest, + "ro": False + }} + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:rw"] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_binds_mode(self): + mount_dest = '/mnt' + mount_origin = '/tmp' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + binds={mount_origin: { + "bind": mount_dest, + "mode": "z", + }} + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Binds'] = ["/tmp:/mnt:z"] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_binds_mode_and_ro_error(self): + with pytest.raises(ValueError): + mount_dest = '/mnt' + mount_origin = '/tmp' + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + binds={mount_origin: { + "bind": mount_dest, + "mode": "z", + "ro": True, + }} + ) + ) + + def test_create_container_with_binds_list(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + binds=[ + "/tmp:/mnt/1:ro", + "/tmp:/mnt/2", + ], + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Binds'] = [ + "/tmp:/mnt/1:ro", + "/tmp:/mnt/2", + ] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_port_binds(self): + self.maxDiff = None + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + port_bindings={ + 1111: None, + 2222: 2222, + '3333/udp': (3333,), + 4444: ('127.0.0.1',), + 5555: ('127.0.0.1', 5555), + 6666: [('127.0.0.1',), ('192.168.0.1',)] + } + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + data = json.loads(args[1]['data']) + port_bindings = data['HostConfig']['PortBindings'] + self.assertTrue('1111/tcp' in port_bindings) + self.assertTrue('2222/tcp' in port_bindings) + self.assertTrue('3333/udp' in port_bindings) + self.assertTrue('4444/tcp' in port_bindings) + self.assertTrue('5555/tcp' in port_bindings) + self.assertTrue('6666/tcp' in port_bindings) + self.assertEqual( + [{"HostPort": "", "HostIp": ""}], + port_bindings['1111/tcp'] + ) + self.assertEqual( + [{"HostPort": "2222", "HostIp": ""}], + port_bindings['2222/tcp'] + ) + self.assertEqual( + [{"HostPort": "3333", "HostIp": ""}], + port_bindings['3333/udp'] + ) + self.assertEqual( + [{"HostPort": "", "HostIp": "127.0.0.1"}], + port_bindings['4444/tcp'] + ) + self.assertEqual( + [{"HostPort": "5555", "HostIp": "127.0.0.1"}], + port_bindings['5555/tcp'] + ) + self.assertEqual(len(port_bindings['6666/tcp']), 2) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_mac_address(self): + mac_address_expected = "02:42:ac:11:00:0a" + + container = self.client.create_container( + 'busybox', ['sleep', '60'], mac_address=mac_address_expected) + + res = self.client.inspect_container(container['Id']) + self.assertEqual(mac_address_expected, + res['NetworkSettings']['MacAddress']) + + def test_create_container_with_links(self): + link_path = 'path' + alias = 'alias' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + links={link_path: alias} + ) + ) + + args = fake_request.call_args + self.assertEqual( + args[0][1], url_prefix + 'containers/create' + ) + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Links'] = ['path:alias'] + + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + + def test_create_container_with_multiple_links(self): + link_path = 'path' + alias = 'alias' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + links={ + link_path + '1': alias + '1', + link_path + '2': alias + '2' + } + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Links'] = [ + 'path1:alias1', 'path2:alias2' + ] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + + def test_create_container_with_links_as_list_of_tuples(self): + link_path = 'path' + alias = 'alias' + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + links=[(link_path, alias)] + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Links'] = ['path:alias'] + + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + + def test_create_container_privileged(self): + self.client.create_container( + 'busybox', 'true', + host_config=self.client.create_host_config(privileged=True) + ) + + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Privileged'] = True + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_restart_policy(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + restart_policy={ + "Name": "always", + "MaximumRetryCount": 0 + } + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['RestartPolicy'] = { + "MaximumRetryCount": 0, "Name": "always" + } + self.assertEqual(json.loads(args[1]['data']), expected_payload) + + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_added_capabilities(self): + self.client.create_container( + 'busybox', 'true', + host_config=self.client.create_host_config(cap_add=['MKNOD']) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['CapAdd'] = ['MKNOD'] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_dropped_capabilities(self): + self.client.create_container( + 'busybox', 'true', + host_config=self.client.create_host_config(cap_drop=['MKNOD']) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['CapDrop'] = ['MKNOD'] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_devices(self): + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + devices=['/dev/sda:/dev/xvda:rwm', + '/dev/sdb:/dev/xvdb', + '/dev/sdc'] + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Devices'] = [ + {'CgroupPermissions': 'rwm', + 'PathInContainer': '/dev/xvda', + 'PathOnHost': '/dev/sda'}, + {'CgroupPermissions': 'rwm', + 'PathInContainer': '/dev/xvdb', + 'PathOnHost': '/dev/sdb'}, + {'CgroupPermissions': 'rwm', + 'PathInContainer': '/dev/sdc', + 'PathOnHost': '/dev/sdc'} + ] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_labels_dict(self): + labels_dict = { + six.text_type('foo'): six.text_type('1'), + six.text_type('bar'): six.text_type('2'), + } + + self.client.create_container( + 'busybox', 'true', + labels=labels_dict, + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_labels_list(self): + labels_list = [ + six.text_type('foo'), + six.text_type('bar'), + ] + labels_dict = { + six.text_type('foo'): six.text_type(), + six.text_type('bar'): six.text_type(), + } + + self.client.create_container( + 'busybox', 'true', + labels=labels_list, + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data'])['Labels'], labels_dict) + self.assertEqual( + args[1]['headers'], {'Content-Type': 'application/json'} + ) + self.assertEqual( + args[1]['timeout'], DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_named_volume(self): + mount_dest = '/mnt' + volume_name = 'name' + + self.client.create_container( + 'busybox', 'true', + host_config=self.client.create_host_config( + binds={volume_name: { + "bind": mount_dest, + "ro": False + }}), + volume_driver='foodriver', + ) + + args = fake_request.call_args + self.assertEqual( + args[0][1], url_prefix + 'containers/create' + ) + expected_payload = self.base_create_payload() + expected_payload['VolumeDriver'] = 'foodriver' + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Binds'] = ["name:/mnt:rw"] + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + def test_create_container_with_stop_signal(self): + self.client.create_container('busybox', 'ls', + stop_signal='SIGINT') + + args = fake_request.call_args + self.assertEqual(args[0][1], + url_prefix + 'containers/create') + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "StopSignal": "SIGINT"}''')) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + @requires_api_version('1.22') + def test_create_container_with_aliases(self): + self.client.create_container( + 'busybox', 'ls', + host_config=self.client.create_host_config( + network_mode='some-network', + ), + networking_config=self.client.create_networking_config({ + 'some-network': self.client.create_endpoint_config( + aliases=['foo', 'bar'], + ), + }), + ) + + args = fake_request.call_args + self.assertEqual(json.loads(args[1]['data']), + json.loads(''' + {"Tty": false, "Image": "busybox", + "Cmd": ["ls"], "AttachStdin": false, + "AttachStderr": true, + "AttachStdout": true, "OpenStdin": false, + "StdinOnce": false, + "NetworkDisabled": false, + "HostConfig": { + "NetworkMode": "some-network" + }, + "NetworkingConfig": { + "EndpointsConfig": { + "some-network": {"Aliases": ["foo", "bar"]} + } + }}''')) + + @requires_api_version('1.22') + def test_create_container_with_tmpfs_list(self): + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + tmpfs=[ + "/tmp", + "/mnt:size=3G,uid=100" + ] + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Tmpfs'] = { + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + @requires_api_version('1.22') + def test_create_container_with_tmpfs_dict(self): + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + tmpfs={ + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Tmpfs'] = { + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + +class ContainerTest(DockerClientTest): + def test_list_containers(self): + self.client.containers(all=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/json', + params={ + 'all': 1, + 'since': None, + 'size': 0, + 'limit': -1, + 'trunc_cmd': 0, + 'before': None + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_resize_container(self): + self.client.resize( + {'Id': fake_api.FAKE_CONTAINER_ID}, + height=15, + width=120 + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/resize', + params={'h': 15, 'w': 120}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_rename_container(self): + self.client.rename( + {'Id': fake_api.FAKE_CONTAINER_ID}, + name='foobar' + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/rename', + params={'name': 'foobar'}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_wait(self): + self.client.wait(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/wait', + timeout=None + ) + + def test_wait_with_dict_instead_of_id(self): + self.client.wait({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/wait', + timeout=None + ) + + def test_logs(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + logs = self.client.logs(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + self.assertEqual( + logs, + 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') + ) + + def test_logs_with_dict_instead_of_id(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + logs = self.client.logs({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + self.assertEqual( + logs, + 'Flowering Nights\n(Sakuya Iyazoi)\n'.encode('ascii') + ) + + def test_log_streaming(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True, + follow=False) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=True + ) + + def test_log_following(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, + follow=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + def test_log_following_backwards(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=True + ) + + def test_log_streaming_and_following(self): + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True, + follow=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=True + ) + + def test_log_tail(self): + + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, + follow=False, tail=10) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 10}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + def test_log_since(self): + ts = 809222400 + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, + follow=False, since=ts) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all', 'since': ts}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + def test_log_since_with_datetime(self): + ts = 809222400 + time = datetime.datetime.utcfromtimestamp(ts) + with mock.patch('docker.Client.inspect_container', + fake_inspect_container): + self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, + follow=False, since=time) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 0, 'stderr': 1, 'stdout': 1, + 'tail': 'all', 'since': ts}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=False + ) + + def test_log_tty(self): + m = mock.Mock() + with mock.patch('docker.Client.inspect_container', + fake_inspect_container_tty): + with mock.patch('docker.Client._stream_raw_result', + m): + self.client.logs(fake_api.FAKE_CONTAINER_ID, + follow=True, stream=True) + + self.assertTrue(m.called) + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/logs', + params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1, + 'tail': 'all'}, + timeout=DEFAULT_TIMEOUT_SECONDS, + stream=True + ) + + def test_diff(self): + self.client.diff(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/changes', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_diff_with_dict_instead_of_id(self): + self.client.diff({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/changes', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_port(self): + self.client.port({'Id': fake_api.FAKE_CONTAINER_ID}, 1111) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/json', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_stop_container(self): + timeout = 2 + + self.client.stop(fake_api.FAKE_CONTAINER_ID, timeout=timeout) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/stop', + params={'t': timeout}, + timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) + ) + + def test_stop_container_with_dict_instead_of_id(self): + timeout = 2 + + self.client.stop({'Id': fake_api.FAKE_CONTAINER_ID}, + timeout=timeout) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/stop', + params={'t': timeout}, + timeout=(DEFAULT_TIMEOUT_SECONDS + timeout) + ) + + def test_pause_container(self): + self.client.pause(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/pause', + timeout=(DEFAULT_TIMEOUT_SECONDS) + ) + + def test_unpause_container(self): + self.client.unpause(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/unpause', + timeout=(DEFAULT_TIMEOUT_SECONDS) + ) + + def test_kill_container(self): + self.client.kill(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/kill', + params={}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_kill_container_with_dict_instead_of_id(self): + self.client.kill({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/kill', + params={}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_kill_container_with_signal(self): + self.client.kill(fake_api.FAKE_CONTAINER_ID, signal=signal.SIGTERM) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/kill', + params={'signal': signal.SIGTERM}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_restart_container(self): + self.client.restart(fake_api.FAKE_CONTAINER_ID, timeout=2) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/restart', + params={'t': 2}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_restart_container_with_dict_instead_of_id(self): + self.client.restart({'Id': fake_api.FAKE_CONTAINER_ID}, timeout=2) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'containers/3cc2351ab11b/restart', + params={'t': 2}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_remove_container(self): + self.client.remove_container(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'DELETE', + url_prefix + 'containers/3cc2351ab11b', + params={'v': False, 'link': False, 'force': False}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_remove_container_with_dict_instead_of_id(self): + self.client.remove_container({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'DELETE', + url_prefix + 'containers/3cc2351ab11b', + params={'v': False, 'link': False, 'force': False}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_export(self): + self.client.export(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/export', + stream=True, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_export_with_dict_instead_of_id(self): + self.client.export({'Id': fake_api.FAKE_CONTAINER_ID}) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/export', + stream=True, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_inspect_container(self): + self.client.inspect_container(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/json', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_inspect_container_undefined_id(self): + for arg in None, '', {True: True}: + with pytest.raises(docker.errors.NullResource) as excinfo: + self.client.inspect_container(arg) + + self.assertEqual( + excinfo.value.args[0], 'image or container param is undefined' + ) + + def test_container_stats(self): + self.client.stats(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/stats', + timeout=60, + stream=True + ) + + def test_container_top(self): + self.client.top(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/top', + params={}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_container_top_with_psargs(self): + self.client.top(fake_api.FAKE_CONTAINER_ID, 'waux') + + fake_request.assert_called_with( + 'GET', + url_prefix + 'containers/3cc2351ab11b/top', + params={'ps_args': 'waux'}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + @requires_api_version('1.22') + def test_container_update(self): + self.client.update_container( + fake_api.FAKE_CONTAINER_ID, mem_limit='2k', cpu_shares=124, + blkio_weight=345 + ) + args = fake_request.call_args + self.assertEqual( + args[0][1], url_prefix + 'containers/3cc2351ab11b/update' + ) + self.assertEqual( + json.loads(args[1]['data']), + {'Memory': 2 * 1024, 'CpuShares': 124, 'BlkioWeight': 345} + ) + self.assertEqual( + args[1]['headers']['Content-Type'], 'application/json' + ) diff -Nru python-docker-1.5.0/tests/unit/exec_test.py python-docker-1.8.0/tests/unit/exec_test.py --- python-docker-1.5.0/tests/unit/exec_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/exec_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,75 @@ +import json + +from . import fake_api +from .api_test import ( + DockerClientTest, url_prefix, fake_request, DEFAULT_TIMEOUT_SECONDS, +) + + +class ExecTest(DockerClientTest): + def test_exec_create(self): + self.client.exec_create(fake_api.FAKE_CONTAINER_ID, ['ls', '-1']) + + args = fake_request.call_args + self.assertEqual( + 'POST', + args[0][0], url_prefix + 'containers/{0}/exec'.format( + fake_api.FAKE_CONTAINER_ID + ) + ) + + self.assertEqual( + json.loads(args[1]['data']), { + 'Tty': False, + 'AttachStdout': True, + 'Container': fake_api.FAKE_CONTAINER_ID, + 'Cmd': ['ls', '-1'], + 'Privileged': False, + 'AttachStdin': False, + 'AttachStderr': True, + 'User': '' + } + ) + + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_exec_start(self): + self.client.exec_start(fake_api.FAKE_EXEC_ID) + + args = fake_request.call_args + self.assertEqual( + args[0][1], url_prefix + 'exec/{0}/start'.format( + fake_api.FAKE_EXEC_ID + ) + ) + + self.assertEqual( + json.loads(args[1]['data']), { + 'Tty': False, + 'Detach': False, + } + ) + + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + + def test_exec_inspect(self): + self.client.exec_inspect(fake_api.FAKE_EXEC_ID) + + args = fake_request.call_args + self.assertEqual( + args[0][1], url_prefix + 'exec/{0}/json'.format( + fake_api.FAKE_EXEC_ID + ) + ) + + def test_exec_resize(self): + self.client.exec_resize(fake_api.FAKE_EXEC_ID, height=20, width=60) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'exec/{0}/resize'.format(fake_api.FAKE_EXEC_ID), + params={'h': 20, 'w': 60}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) diff -Nru python-docker-1.5.0/tests/unit/fake_api.py python-docker-1.8.0/tests/unit/fake_api.py --- python-docker-1.5.0/tests/unit/fake_api.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/fake_api.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,549 @@ +# Copyright 2013 dotCloud inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from . import fake_stat +from docker import constants + +CURRENT_VERSION = 'v{0}'.format(constants.DEFAULT_DOCKER_API_VERSION) + +FAKE_CONTAINER_ID = '3cc2351ab11b' +FAKE_IMAGE_ID = 'e9aa60c60128' +FAKE_EXEC_ID = 'd5d177f121dc' +FAKE_IMAGE_NAME = 'test_image' +FAKE_TARBALL_PATH = '/path/to/tarball' +FAKE_REPO_NAME = 'repo' +FAKE_TAG_NAME = 'tag' +FAKE_FILE_NAME = 'file' +FAKE_URL = 'myurl' +FAKE_PATH = '/path' +FAKE_VOLUME_NAME = 'perfectcherryblossom' + +# Each method is prefixed with HTTP method (get, post...) +# for clarity and readability + + +def get_fake_raw_version(): + status_code = 200 + response = { + "ApiVersion": "1.18", + "GitCommit": "fake-commit", + "GoVersion": "go1.3.3", + "Version": "1.5.0" + } + return status_code, response + + +def get_fake_version(): + status_code = 200 + response = {'GoVersion': '1', 'Version': '1.1.1', + 'GitCommit': 'deadbeef+CHANGES'} + return status_code, response + + +def get_fake_info(): + status_code = 200 + response = {'Containers': 1, 'Images': 1, 'Debug': False, + 'MemoryLimit': False, 'SwapLimit': False, + 'IPv4Forwarding': True} + return status_code, response + + +def get_fake_search(): + status_code = 200 + response = [{'Name': 'busybox', 'Description': 'Fake Description'}] + return status_code, response + + +def get_fake_images(): + status_code = 200 + response = [{ + 'Id': FAKE_IMAGE_ID, + 'Created': '2 days ago', + 'Repository': 'busybox', + 'RepoTags': ['busybox:latest', 'busybox:1.0'], + }] + return status_code, response + + +def get_fake_image_history(): + status_code = 200 + response = [ + { + "Id": "b750fe79269d", + "Created": 1364102658, + "CreatedBy": "/bin/bash" + }, + { + "Id": "27cf78414709", + "Created": 1364068391, + "CreatedBy": "" + } + ] + + return status_code, response + + +def post_fake_import_image(): + status_code = 200 + response = 'Import messages...' + + return status_code, response + + +def get_fake_containers(): + status_code = 200 + response = [{ + 'Id': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + 'Created': '2 days ago', + 'Command': 'true', + 'Status': 'fake status' + }] + return status_code, response + + +def post_fake_start_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_resize_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_create_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def get_fake_inspect_container(tty=False): + status_code = 200 + response = { + 'Id': FAKE_CONTAINER_ID, + 'Config': {'Privileged': True, 'Tty': tty}, + 'ID': FAKE_CONTAINER_ID, + 'Image': 'busybox:latest', + "State": { + "Running": True, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-09-25T14:01:18.869545111+02:00", + "Ghost": False + }, + "MacAddress": "02:42:ac:11:00:0a" + } + return status_code, response + + +def get_fake_inspect_image(): + status_code = 200 + response = { + 'id': FAKE_IMAGE_ID, + 'parent': "27cf784147099545", + 'created': "2013-03-23T22:24:18.818426-07:00", + 'container': FAKE_CONTAINER_ID, + 'container_config': + { + "Hostname": "", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": False, + "AttachStdout": False, + "AttachStderr": False, + "PortSpecs": "", + "Tty": True, + "OpenStdin": True, + "StdinOnce": False, + "Env": "", + "Cmd": ["/bin/bash"], + "Dns": "", + "Image": "base", + "Volumes": "", + "VolumesFrom": "", + "WorkingDir": "" + }, + 'Size': 6823592 + } + return status_code, response + + +def get_fake_port(): + status_code = 200 + response = { + 'HostConfig': { + 'Binds': None, + 'ContainerIDFile': '', + 'Links': None, + 'LxcConf': None, + 'PortBindings': { + '1111': None, + '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], + '2222': None + }, + 'Privileged': False, + 'PublishAllPorts': False + }, + 'NetworkSettings': { + 'Bridge': 'docker0', + 'PortMapping': None, + 'Ports': { + '1111': None, + '1111/tcp': [{'HostIp': '127.0.0.1', 'HostPort': '4567'}], + '2222': None}, + 'MacAddress': '02:42:ac:11:00:0a' + } + } + return status_code, response + + +def get_fake_insert_image(): + status_code = 200 + response = {'StatusCode': 0} + return status_code, response + + +def get_fake_wait(): + status_code = 200 + response = {'StatusCode': 0} + return status_code, response + + +def get_fake_logs(): + status_code = 200 + response = (b'\x01\x00\x00\x00\x00\x00\x00\x11Flowering Nights\n' + b'\x01\x00\x00\x00\x00\x00\x00\x10(Sakuya Iyazoi)\n') + return status_code, response + + +def get_fake_diff(): + status_code = 200 + response = [{'Path': '/test', 'Kind': 1}] + return status_code, response + + +def get_fake_events(): + status_code = 200 + response = [{'status': 'stop', 'id': FAKE_CONTAINER_ID, + 'from': FAKE_IMAGE_ID, 'time': 1423247867}] + return status_code, response + + +def get_fake_export(): + status_code = 200 + response = 'Byte Stream....' + return status_code, response + + +def post_fake_exec_create(): + status_code = 200 + response = {'Id': FAKE_EXEC_ID} + return status_code, response + + +def post_fake_exec_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_exec_resize(): + status_code = 201 + return status_code, '' + + +def get_fake_exec_inspect(): + return 200, { + 'OpenStderr': True, + 'OpenStdout': True, + 'Container': get_fake_inspect_container()[1], + 'Running': False, + 'ProcessConfig': { + 'arguments': ['hello world'], + 'tty': False, + 'entrypoint': 'echo', + 'privileged': False, + 'user': '' + }, + 'ExitCode': 0, + 'ID': FAKE_EXEC_ID, + 'OpenStdin': False + } + + +def post_fake_stop_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_kill_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + 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} + return status_code, response + + +def post_fake_rename_container(): + status_code = 204 + return status_code, None + + +def delete_fake_remove_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_image_create(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def delete_fake_remove_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def get_fake_get_image(): + status_code = 200 + response = 'Byte Stream....' + return status_code, response + + +def post_fake_load_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def post_fake_commit(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_push(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def post_fake_build_container(): + status_code = 200 + response = {'Id': FAKE_CONTAINER_ID} + return status_code, response + + +def post_fake_tag_image(): + status_code = 200 + response = {'Id': FAKE_IMAGE_ID} + return status_code, response + + +def get_fake_stats(): + status_code = 200 + response = fake_stat.OBJ + return status_code, response + + +def get_fake_top(): + return 200, { + 'Processes': [ + [ + 'root', + '26501', + '6907', + '0', + '10:32', + 'pts/55', + '00:00:00', + 'sleep 60', + ], + ], + 'Titles': [ + 'UID', + 'PID', + 'PPID', + 'C', + 'STIME', + 'TTY', + 'TIME', + 'CMD', + ], + } + + +def get_fake_volume_list(): + status_code = 200 + response = { + 'Volumes': [ + { + 'Name': 'perfectcherryblossom', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' + }, { + 'Name': 'subterraneananimism', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/subterraneananimism' + } + ] + } + return status_code, response + + +def get_fake_volume(): + status_code = 200 + response = { + 'Name': 'perfectcherryblossom', + 'Driver': 'local', + 'Mountpoint': '/var/lib/docker/volumes/perfectcherryblossom' + } + return status_code, response + + +def fake_remove_volume(): + return 204, None + + +def post_fake_update_container(): + return 200, {'Warnings': []} + + +# Maps real api url to fake response callback +prefix = 'http+docker://localunixsocket' +fake_responses = { + '{0}/version'.format(prefix): + get_fake_raw_version, + '{1}/{0}/version'.format(CURRENT_VERSION, prefix): + get_fake_version, + '{1}/{0}/info'.format(CURRENT_VERSION, prefix): + get_fake_info, + '{1}/{0}/images/search'.format(CURRENT_VERSION, prefix): + get_fake_search, + '{1}/{0}/images/json'.format(CURRENT_VERSION, prefix): + get_fake_images, + '{1}/{0}/images/test_image/history'.format(CURRENT_VERSION, prefix): + get_fake_image_history, + '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): + post_fake_import_image, + '{1}/{0}/containers/json'.format(CURRENT_VERSION, prefix): + get_fake_containers, + '{1}/{0}/containers/3cc2351ab11b/start'.format(CURRENT_VERSION, prefix): + post_fake_start_container, + '{1}/{0}/containers/3cc2351ab11b/resize'.format(CURRENT_VERSION, prefix): + post_fake_resize_container, + '{1}/{0}/containers/3cc2351ab11b/json'.format(CURRENT_VERSION, prefix): + get_fake_inspect_container, + '{1}/{0}/containers/3cc2351ab11b/rename'.format(CURRENT_VERSION, prefix): + post_fake_rename_container, + '{1}/{0}/images/e9aa60c60128/tag'.format(CURRENT_VERSION, prefix): + post_fake_tag_image, + '{1}/{0}/containers/3cc2351ab11b/wait'.format(CURRENT_VERSION, prefix): + get_fake_wait, + '{1}/{0}/containers/3cc2351ab11b/logs'.format(CURRENT_VERSION, prefix): + get_fake_logs, + '{1}/{0}/containers/3cc2351ab11b/changes'.format(CURRENT_VERSION, prefix): + get_fake_diff, + '{1}/{0}/containers/3cc2351ab11b/export'.format(CURRENT_VERSION, prefix): + get_fake_export, + '{1}/{0}/containers/3cc2351ab11b/update'.format(CURRENT_VERSION, prefix): + post_fake_update_container, + '{1}/{0}/containers/3cc2351ab11b/exec'.format(CURRENT_VERSION, prefix): + post_fake_exec_create, + '{1}/{0}/exec/d5d177f121dc/start'.format(CURRENT_VERSION, prefix): + post_fake_exec_start, + '{1}/{0}/exec/d5d177f121dc/json'.format(CURRENT_VERSION, prefix): + get_fake_exec_inspect, + '{1}/{0}/exec/d5d177f121dc/resize'.format(CURRENT_VERSION, prefix): + post_fake_exec_resize, + + '{1}/{0}/containers/3cc2351ab11b/stats'.format(CURRENT_VERSION, prefix): + get_fake_stats, + '{1}/{0}/containers/3cc2351ab11b/top'.format(CURRENT_VERSION, prefix): + get_fake_top, + '{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): + post_fake_restart_container, + '{1}/{0}/containers/3cc2351ab11b'.format(CURRENT_VERSION, prefix): + delete_fake_remove_container, + '{1}/{0}/images/create'.format(CURRENT_VERSION, prefix): + post_fake_image_create, + '{1}/{0}/images/e9aa60c60128'.format(CURRENT_VERSION, prefix): + delete_fake_remove_image, + '{1}/{0}/images/e9aa60c60128/get'.format(CURRENT_VERSION, prefix): + get_fake_get_image, + '{1}/{0}/images/load'.format(CURRENT_VERSION, prefix): + post_fake_load_image, + '{1}/{0}/images/test_image/json'.format(CURRENT_VERSION, prefix): + get_fake_inspect_image, + '{1}/{0}/images/test_image/insert'.format(CURRENT_VERSION, prefix): + get_fake_insert_image, + '{1}/{0}/images/test_image/push'.format(CURRENT_VERSION, prefix): + post_fake_push, + '{1}/{0}/commit'.format(CURRENT_VERSION, prefix): + post_fake_commit, + '{1}/{0}/containers/create'.format(CURRENT_VERSION, prefix): + post_fake_create_container, + '{1}/{0}/build'.format(CURRENT_VERSION, prefix): + post_fake_build_container, + '{1}/{0}/events'.format(CURRENT_VERSION, prefix): + get_fake_events, + ('{1}/{0}/volumes'.format(CURRENT_VERSION, prefix), 'GET'): + get_fake_volume_list, + ('{1}/{0}/volumes/create'.format(CURRENT_VERSION, prefix), 'POST'): + get_fake_volume, + ('{1}/{0}/volumes/{2}'.format( + CURRENT_VERSION, prefix, FAKE_VOLUME_NAME + ), 'GET'): + get_fake_volume, + ('{1}/{0}/volumes/{2}'.format( + CURRENT_VERSION, prefix, FAKE_VOLUME_NAME + ), 'DELETE'): + fake_remove_volume, +} diff -Nru python-docker-1.5.0/tests/unit/fake_stat.py python-docker-1.8.0/tests/unit/fake_stat.py --- python-docker-1.5.0/tests/unit/fake_stat.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/fake_stat.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,133 @@ +OBJ = { + "read": "2015-02-11T19:20:46.667237763+02:00", + "network": { + "rx_bytes": 567224, + "rx_packets": 3773, + "rx_errors": 0, + "rx_dropped": 0, + "tx_bytes": 1176, + "tx_packets": 13, + "tx_errors": 0, + "tx_dropped": 0 + }, + "cpu_stats": { + "cpu_usage": { + "total_usage": 157260874053, + "percpu_usage": [ + 52196306950, + 24118413549, + 53292684398, + 27653469156 + ], + "usage_in_kernelmode": 37140000000, + "usage_in_usermode": 62140000000 + }, + "system_cpu_usage": 3.0881377e+14, + "throttling_data": { + "periods": 0, + "throttled_periods": 0, + "throttled_time": 0 + } + }, + "memory_stats": { + "usage": 179314688, + "max_usage": 258166784, + "stats": { + "active_anon": 90804224, + "active_file": 2195456, + "cache": 3096576, + "hierarchical_memory_limit": 1.844674407371e+19, + "inactive_anon": 85516288, + "inactive_file": 798720, + "mapped_file": 2646016, + "pgfault": 101034, + "pgmajfault": 1207, + "pgpgin": 115814, + "pgpgout": 75613, + "rss": 176218112, + "rss_huge": 12582912, + "total_active_anon": 90804224, + "total_active_file": 2195456, + "total_cache": 3096576, + "total_inactive_anon": 85516288, + "total_inactive_file": 798720, + "total_mapped_file": 2646016, + "total_pgfault": 101034, + "total_pgmajfault": 1207, + "total_pgpgin": 115814, + "total_pgpgout": 75613, + "total_rss": 176218112, + "total_rss_huge": 12582912, + "total_unevictable": 0, + "total_writeback": 0, + "unevictable": 0, + "writeback": 0 + }, + "failcnt": 0, + "limit": 8039038976 + }, + "blkio_stats": { + "io_service_bytes_recursive": [ + { + "major": 8, + "minor": 0, + "op": "Read", + "value": 72843264 + }, { + "major": 8, + "minor": 0, + "op": "Write", + "value": 4096 + }, { + "major": 8, + "minor": 0, + "op": "Sync", + "value": 4096 + }, { + "major": 8, + "minor": 0, + "op": "Async", + "value": 72843264 + }, { + "major": 8, + "minor": 0, + "op": "Total", + "value": 72847360 + } + ], + "io_serviced_recursive": [ + { + "major": 8, + "minor": 0, + "op": "Read", + "value": 10581 + }, { + "major": 8, + "minor": 0, + "op": "Write", + "value": 1 + }, { + "major": 8, + "minor": 0, + "op": "Sync", + "value": 1 + }, { + "major": 8, + "minor": 0, + "op": "Async", + "value": 10581 + }, { + "major": 8, + "minor": 0, + "op": "Total", + "value": 10582 + } + ], + "io_queue_recursive": [], + "io_service_time_recursive": [], + "io_wait_time_recursive": [], + "io_merged_recursive": [], + "io_time_recursive": [], + "sectors_recursive": [] + } +} diff -Nru python-docker-1.5.0/tests/unit/image_test.py python-docker-1.8.0/tests/unit/image_test.py --- python-docker-1.5.0/tests/unit/image_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/image_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,347 @@ +import docker +import pytest + +from . import fake_api +from .api_test import ( + DockerClientTest, fake_request, DEFAULT_TIMEOUT_SECONDS, url_prefix, + fake_resolve_authconfig +) + +try: + from unittest import mock +except ImportError: + import mock + + +class ImageTest(DockerClientTest): + def test_image_viz(self): + with pytest.raises(Exception): + self.client.images('busybox', viz=True) + self.fail('Viz output should not be supported!') + + def test_images(self): + self.client.images(all=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/json', + params={'filter': None, 'only_ids': 0, 'all': 1}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_images_quiet(self): + self.client.images(all=True, quiet=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/json', + params={'filter': None, 'only_ids': 1, 'all': 1}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_image_ids(self): + self.client.images(quiet=True) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/json', + params={'filter': None, 'only_ids': 1, 'all': 0}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_images_filters(self): + self.client.images(filters={'dangling': True}) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/json', + params={'filter': None, 'only_ids': 0, 'all': 0, + 'filters': '{"dangling": ["true"]}'}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_pull(self): + self.client.pull('joffrey/test001') + + args = fake_request.call_args + self.assertEqual( + args[0][1], + url_prefix + 'images/create' + ) + self.assertEqual( + args[1]['params'], + {'tag': None, 'fromImage': 'joffrey/test001'} + ) + self.assertFalse(args[1]['stream']) + + def test_pull_stream(self): + self.client.pull('joffrey/test001', stream=True) + + args = fake_request.call_args + self.assertEqual( + args[0][1], + url_prefix + 'images/create' + ) + self.assertEqual( + args[1]['params'], + {'tag': None, 'fromImage': 'joffrey/test001'} + ) + self.assertTrue(args[1]['stream']) + + def test_commit(self): + self.client.commit(fake_api.FAKE_CONTAINER_ID) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'commit', + data='{}', + headers={'Content-Type': 'application/json'}, + params={ + 'repo': None, + 'comment': None, + 'tag': None, + 'container': '3cc2351ab11b', + 'author': None, + 'changes': None + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_remove_image(self): + self.client.remove_image(fake_api.FAKE_IMAGE_ID) + + fake_request.assert_called_with( + 'DELETE', + url_prefix + 'images/e9aa60c60128', + params={'force': False, 'noprune': False}, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_image_history(self): + self.client.history(fake_api.FAKE_IMAGE_NAME) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/test_image/history', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_import_image(self): + self.client.import_image( + fake_api.FAKE_TARBALL_PATH, + repository=fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/create', + params={ + 'repo': fake_api.FAKE_REPO_NAME, + 'tag': fake_api.FAKE_TAG_NAME, + 'fromSrc': fake_api.FAKE_TARBALL_PATH + }, + data=None, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_import_image_from_bytes(self): + stream = (i for i in range(0, 100)) + + self.client.import_image( + stream, + repository=fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/create', + params={ + 'repo': fake_api.FAKE_REPO_NAME, + 'tag': fake_api.FAKE_TAG_NAME, + 'fromSrc': '-', + }, + headers={ + 'Content-Type': 'application/tar', + }, + data=stream, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_import_image_from_image(self): + self.client.import_image( + image=fake_api.FAKE_IMAGE_NAME, + repository=fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/create', + params={ + 'repo': fake_api.FAKE_REPO_NAME, + 'tag': fake_api.FAKE_TAG_NAME, + 'fromImage': fake_api.FAKE_IMAGE_NAME + }, + data=None, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_inspect_image(self): + self.client.inspect_image(fake_api.FAKE_IMAGE_NAME) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/test_image/json', + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_inspect_image_undefined_id(self): + for arg in None, '', {True: True}: + with pytest.raises(docker.errors.NullResource) as excinfo: + self.client.inspect_image(arg) + + self.assertEqual( + excinfo.value.args[0], 'image or container param is undefined' + ) + + def test_insert_image(self): + try: + self.client.insert(fake_api.FAKE_IMAGE_NAME, + fake_api.FAKE_URL, fake_api.FAKE_PATH) + except docker.errors.DeprecatedMethod: + self.assertTrue( + docker.utils.compare_version('1.12', self.client._version) >= 0 + ) + return + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/test_image/insert', + params={ + 'url': fake_api.FAKE_URL, + 'path': fake_api.FAKE_PATH + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_push_image(self): + with mock.patch('docker.auth.auth.resolve_authconfig', + fake_resolve_authconfig): + self.client.push(fake_api.FAKE_IMAGE_NAME) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/test_image/push', + params={ + 'tag': None + }, + data='{}', + headers={'Content-Type': 'application/json'}, + stream=False, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_push_image_with_tag(self): + with mock.patch('docker.auth.auth.resolve_authconfig', + fake_resolve_authconfig): + self.client.push( + fake_api.FAKE_IMAGE_NAME, tag=fake_api.FAKE_TAG_NAME + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/test_image/push', + params={ + 'tag': fake_api.FAKE_TAG_NAME, + }, + data='{}', + headers={'Content-Type': 'application/json'}, + stream=False, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_push_image_stream(self): + with mock.patch('docker.auth.auth.resolve_authconfig', + fake_resolve_authconfig): + self.client.push(fake_api.FAKE_IMAGE_NAME, stream=True) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/test_image/push', + params={ + 'tag': None + }, + data='{}', + headers={'Content-Type': 'application/json'}, + stream=True, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_tag_image(self): + self.client.tag(fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/e9aa60c60128/tag', + params={ + 'tag': None, + 'repo': 'repo', + 'force': 0 + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_tag_image_tag(self): + self.client.tag( + fake_api.FAKE_IMAGE_ID, + fake_api.FAKE_REPO_NAME, + tag=fake_api.FAKE_TAG_NAME + ) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/e9aa60c60128/tag', + params={ + 'tag': 'tag', + 'repo': 'repo', + 'force': 0 + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_tag_image_force(self): + self.client.tag( + fake_api.FAKE_IMAGE_ID, fake_api.FAKE_REPO_NAME, force=True) + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/e9aa60c60128/tag', + params={ + 'tag': None, + 'repo': 'repo', + 'force': 1 + }, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_get_image(self): + self.client.get_image(fake_api.FAKE_IMAGE_ID) + + fake_request.assert_called_with( + 'GET', + url_prefix + 'images/e9aa60c60128/get', + stream=True, + timeout=DEFAULT_TIMEOUT_SECONDS + ) + + def test_load_image(self): + self.client.load_image('Byte Stream....') + + fake_request.assert_called_with( + 'POST', + url_prefix + 'images/load', + data='Byte Stream....', + timeout=DEFAULT_TIMEOUT_SECONDS + ) diff -Nru python-docker-1.5.0/tests/unit/network_test.py python-docker-1.8.0/tests/unit/network_test.py --- python-docker-1.5.0/tests/unit/network_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/network_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,187 @@ +import json + +import six + +from .. import base +from .api_test import DockerClientTest, url_prefix, response +from docker.utils import create_ipam_config, create_ipam_pool + +try: + from unittest import mock +except ImportError: + import mock + + +class NetworkTest(DockerClientTest): + @base.requires_api_version('1.21') + def test_list_networks(self): + networks = [ + { + "name": "none", + "id": "8e4e55c6863ef424", + "type": "null", + "endpoints": [] + }, + { + "name": "host", + "id": "062b6d9ea7913fde", + "type": "host", + "endpoints": [] + }, + ] + + get = mock.Mock(return_value=response( + status_code=200, content=json.dumps(networks).encode('utf-8'))) + + with mock.patch('docker.Client.get', get): + self.assertEqual(self.client.networks(), networks) + + self.assertEqual(get.call_args[0][0], url_prefix + 'networks') + + filters = json.loads(get.call_args[1]['params']['filters']) + self.assertFalse(filters) + + self.client.networks(names=['foo']) + filters = json.loads(get.call_args[1]['params']['filters']) + self.assertEqual(filters, {'name': ['foo']}) + + self.client.networks(ids=['123']) + filters = json.loads(get.call_args[1]['params']['filters']) + self.assertEqual(filters, {'id': ['123']}) + + @base.requires_api_version('1.21') + def test_create_network(self): + network_data = { + "id": 'abc12345', + "warning": "", + } + + network_response = response(status_code=200, content=network_data) + post = mock.Mock(return_value=network_response) + + with mock.patch('docker.Client.post', post): + result = self.client.create_network('foo') + self.assertEqual(result, network_data) + + self.assertEqual( + post.call_args[0][0], + url_prefix + 'networks/create') + + self.assertEqual( + json.loads(post.call_args[1]['data']), + {"Name": "foo"}) + + opts = { + 'com.docker.network.bridge.enable_icc': False, + 'com.docker.network.bridge.enable_ip_masquerade': False, + } + self.client.create_network('foo', 'bridge', opts) + + self.assertEqual( + json.loads(post.call_args[1]['data']), + {"Name": "foo", "Driver": "bridge", "Options": opts}) + + ipam_pool_config = create_ipam_pool(subnet="192.168.52.0/24", + gateway="192.168.52.254") + ipam_config = create_ipam_config(pool_configs=[ipam_pool_config]) + + self.client.create_network("bar", driver="bridge", + ipam=ipam_config) + + self.assertEqual( + json.loads(post.call_args[1]['data']), + { + "Name": "bar", + "Driver": "bridge", + "IPAM": { + "Driver": "default", + "Config": [{ + "IPRange": None, + "Gateway": "192.168.52.254", + "Subnet": "192.168.52.0/24", + "AuxiliaryAddresses": None, + }] + } + }) + + @base.requires_api_version('1.21') + def test_remove_network(self): + network_id = 'abc12345' + delete = mock.Mock(return_value=response(status_code=200)) + + with mock.patch('docker.Client.delete', delete): + self.client.remove_network(network_id) + + args = delete.call_args + self.assertEqual(args[0][0], + url_prefix + 'networks/{0}'.format(network_id)) + + @base.requires_api_version('1.21') + def test_inspect_network(self): + network_id = 'abc12345' + network_name = 'foo' + network_data = { + six.u('name'): network_name, + six.u('id'): network_id, + six.u('driver'): 'bridge', + six.u('containers'): {}, + } + + network_response = response(status_code=200, content=network_data) + get = mock.Mock(return_value=network_response) + + with mock.patch('docker.Client.get', get): + result = self.client.inspect_network(network_id) + self.assertEqual(result, network_data) + + args = get.call_args + self.assertEqual(args[0][0], + url_prefix + 'networks/{0}'.format(network_id)) + + @base.requires_api_version('1.21') + def test_connect_container_to_network(self): + network_id = 'abc12345' + container_id = 'def45678' + + post = mock.Mock(return_value=response(status_code=201)) + + with mock.patch('docker.Client.post', post): + self.client.connect_container_to_network( + {'Id': container_id}, + network_id, + aliases=['foo', 'bar'], + links=[('baz', 'quux')] + ) + + self.assertEqual( + post.call_args[0][0], + url_prefix + 'networks/{0}/connect'.format(network_id)) + + self.assertEqual( + json.loads(post.call_args[1]['data']), + { + 'Container': container_id, + 'EndpointConfig': { + 'Aliases': ['foo', 'bar'], + 'Links': ['baz:quux'], + }, + }) + + @base.requires_api_version('1.21') + def test_disconnect_container_from_network(self): + network_id = 'abc12345' + container_id = 'def45678' + + post = mock.Mock(return_value=response(status_code=201)) + + with mock.patch('docker.Client.post', post): + self.client.disconnect_container_from_network( + {'Id': container_id}, network_id) + + self.assertEqual( + post.call_args[0][0], + url_prefix + 'networks/{0}/disconnect'.format(network_id)) + + self.assertEqual( + json.loads(post.call_args[1]['data']), + {'container': container_id}) diff -Nru python-docker-1.5.0/tests/unit/ssladapter_test.py python-docker-1.8.0/tests/unit/ssladapter_test.py --- python-docker-1.5.0/tests/unit/ssladapter_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/ssladapter_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,73 @@ +from docker.ssladapter import ssladapter +from docker.ssladapter.ssl_match_hostname import ( + match_hostname, CertificateError +) + +try: + from ssl import OP_NO_SSLv3, OP_NO_SSLv2, OP_NO_TLSv1 +except ImportError: + OP_NO_SSLv2 = 0x1000000 + OP_NO_SSLv3 = 0x2000000 + OP_NO_TLSv1 = 0x4000000 + +from .. import base + + +class SSLAdapterTest(base.BaseTestCase): + def test_only_uses_tls(self): + ssl_context = ssladapter.urllib3.util.ssl_.create_urllib3_context() + + assert ssl_context.options & OP_NO_SSLv3 + assert ssl_context.options & OP_NO_SSLv2 + assert not ssl_context.options & OP_NO_TLSv1 + + +class MatchHostnameTest(base.BaseTestCase): + cert = { + 'issuer': ( + (('countryName', u'US'),), + (('stateOrProvinceName', u'California'),), + (('localityName', u'San Francisco'),), + (('organizationName', u'Docker Inc'),), + (('organizationalUnitName', u'Docker-Python'),), + (('commonName', u'localhost'),), + (('emailAddress', u'info@docker.com'),) + ), + 'notAfter': 'Mar 25 23:08:23 2030 GMT', + 'notBefore': u'Mar 25 23:08:23 2016 GMT', + 'serialNumber': u'BD5F894C839C548F', + 'subject': ( + (('countryName', u'US'),), + (('stateOrProvinceName', u'California'),), + (('localityName', u'San Francisco'),), + (('organizationName', u'Docker Inc'),), + (('organizationalUnitName', u'Docker-Python'),), + (('commonName', u'localhost'),), + (('emailAddress', u'info@docker.com'),) + ), + 'subjectAltName': ( + ('DNS', u'localhost'), + ('DNS', u'*.gensokyo.jp'), + ('IP Address', u'127.0.0.1'), + ), + 'version': 3 + } + + def test_match_ip_address_success(self): + assert match_hostname(self.cert, '127.0.0.1') is None + + def test_match_localhost_success(self): + assert match_hostname(self.cert, 'localhost') is None + + def test_match_dns_success(self): + assert match_hostname(self.cert, 'touhou.gensokyo.jp') is None + + def test_match_ip_address_failure(self): + self.assertRaises( + CertificateError, match_hostname, self.cert, '192.168.0.25' + ) + + def test_match_dns_failure(self): + self.assertRaises( + CertificateError, match_hostname, self.cert, 'foobar.co.uk' + ) diff -Nru python-docker-1.5.0/tests/unit/utils_test.py python-docker-1.8.0/tests/unit/utils_test.py --- python-docker-1.5.0/tests/unit/utils_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/utils_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,956 @@ +# -*- coding: utf-8 -*- + +import base64 +import json +import os +import os.path +import shutil +import sys +import tarfile +import tempfile + +import pytest +import six + +from docker.client import Client +from docker.constants import DEFAULT_DOCKER_API_VERSION +from docker.errors import DockerException, InvalidVersion +from docker.utils import ( + parse_repository_tag, parse_host, convert_filters, kwargs_from_env, + create_host_config, Ulimit, LogConfig, parse_bytes, parse_env_file, + exclude_paths, convert_volume_binds, decode_json_header, tar, + split_command, create_ipam_config, create_ipam_pool, parse_devices, +) +from docker.utils.utils import create_endpoint_config +from docker.utils.ports import build_port_bindings, split_port + +from .. import base +from ..helpers import make_tree + + +TEST_CERT_DIR = os.path.join( + os.path.dirname(__file__), + 'testdata/certs', +) + + +class HostConfigTest(base.BaseTestCase): + def test_create_host_config_no_options(self): + config = create_host_config(version='1.19') + self.assertFalse('NetworkMode' in config) + + def test_create_host_config_no_options_newer_api_version(self): + config = create_host_config(version='1.20') + self.assertEqual(config['NetworkMode'], 'default') + + def test_create_host_config_invalid_cpu_cfs_types(self): + with pytest.raises(TypeError): + create_host_config(version='1.20', cpu_quota='0') + + with pytest.raises(TypeError): + create_host_config(version='1.20', cpu_period='0') + + with pytest.raises(TypeError): + create_host_config(version='1.20', cpu_quota=23.11) + + with pytest.raises(TypeError): + create_host_config(version='1.20', cpu_period=1999.0) + + def test_create_host_config_with_cpu_quota(self): + config = create_host_config(version='1.20', cpu_quota=1999) + self.assertEqual(config.get('CpuQuota'), 1999) + + def test_create_host_config_with_cpu_period(self): + config = create_host_config(version='1.20', cpu_period=1999) + self.assertEqual(config.get('CpuPeriod'), 1999) + + def test_create_host_config_with_shm_size(self): + config = create_host_config(version='1.22', shm_size=67108864) + self.assertEqual(config.get('ShmSize'), 67108864) + + def test_create_host_config_with_shm_size_in_mb(self): + config = create_host_config(version='1.22', shm_size='64M') + self.assertEqual(config.get('ShmSize'), 67108864) + + def test_create_host_config_with_oom_kill_disable(self): + config = create_host_config(version='1.20', oom_kill_disable=True) + self.assertEqual(config.get('OomKillDisable'), True) + self.assertRaises( + InvalidVersion, lambda: create_host_config(version='1.18.3', + oom_kill_disable=True)) + + def test_create_host_config_with_oom_score_adj(self): + config = create_host_config(version='1.22', oom_score_adj=100) + self.assertEqual(config.get('OomScoreAdj'), 100) + self.assertRaises( + InvalidVersion, lambda: create_host_config(version='1.21', + oom_score_adj=100)) + self.assertRaises( + TypeError, lambda: create_host_config(version='1.22', + oom_score_adj='100')) + + def test_create_endpoint_config_with_aliases(self): + config = create_endpoint_config(version='1.22', aliases=['foo', 'bar']) + assert config == {'Aliases': ['foo', 'bar']} + + with pytest.raises(InvalidVersion): + create_endpoint_config(version='1.21', aliases=['foo', 'bar']) + + +class UlimitTest(base.BaseTestCase): + def test_create_host_config_dict_ulimit(self): + ulimit_dct = {'name': 'nofile', 'soft': 8096} + config = create_host_config( + ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION + ) + self.assertIn('Ulimits', config) + self.assertEqual(len(config['Ulimits']), 1) + ulimit_obj = config['Ulimits'][0] + self.assertTrue(isinstance(ulimit_obj, Ulimit)) + self.assertEqual(ulimit_obj.name, ulimit_dct['name']) + self.assertEqual(ulimit_obj.soft, ulimit_dct['soft']) + self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) + + def test_create_host_config_dict_ulimit_capitals(self): + ulimit_dct = {'Name': 'nofile', 'Soft': 8096, 'Hard': 8096 * 4} + config = create_host_config( + ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION + ) + self.assertIn('Ulimits', config) + self.assertEqual(len(config['Ulimits']), 1) + ulimit_obj = config['Ulimits'][0] + self.assertTrue(isinstance(ulimit_obj, Ulimit)) + self.assertEqual(ulimit_obj.name, ulimit_dct['Name']) + self.assertEqual(ulimit_obj.soft, ulimit_dct['Soft']) + self.assertEqual(ulimit_obj.hard, ulimit_dct['Hard']) + self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) + + def test_create_host_config_obj_ulimit(self): + ulimit_dct = Ulimit(name='nofile', soft=8096) + config = create_host_config( + ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION + ) + self.assertIn('Ulimits', config) + self.assertEqual(len(config['Ulimits']), 1) + ulimit_obj = config['Ulimits'][0] + self.assertTrue(isinstance(ulimit_obj, Ulimit)) + self.assertEqual(ulimit_obj, ulimit_dct) + + def test_ulimit_invalid_type(self): + self.assertRaises(ValueError, lambda: Ulimit(name=None)) + self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123')) + self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456')) + + +class LogConfigTest(base.BaseTestCase): + def test_create_host_config_dict_logconfig(self): + dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}} + config = create_host_config( + version=DEFAULT_DOCKER_API_VERSION, log_config=dct + ) + self.assertIn('LogConfig', config) + self.assertTrue(isinstance(config['LogConfig'], LogConfig)) + self.assertEqual(dct['type'], config['LogConfig'].type) + + def test_create_host_config_obj_logconfig(self): + obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'}) + config = create_host_config( + version=DEFAULT_DOCKER_API_VERSION, log_config=obj + ) + self.assertIn('LogConfig', config) + self.assertTrue(isinstance(config['LogConfig'], LogConfig)) + self.assertEqual(obj, config['LogConfig']) + + def test_logconfig_invalid_config_type(self): + with pytest.raises(ValueError): + LogConfig(type=LogConfig.types.JSON, config='helloworld') + + +class KwargsFromEnvTest(base.BaseTestCase): + def setUp(self): + self.os_environ = os.environ.copy() + + def tearDown(self): + os.environ = self.os_environ + + def test_kwargs_from_env_empty(self): + os.environ.update(DOCKER_HOST='', + DOCKER_CERT_PATH='') + os.environ.pop('DOCKER_TLS_VERIFY', None) + + kwargs = kwargs_from_env() + self.assertEqual(None, kwargs.get('base_url')) + self.assertEqual(None, kwargs.get('tls')) + + def test_kwargs_from_env_tls(self): + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='1') + kwargs = kwargs_from_env(assert_hostname=False) + self.assertEqual('https://192.168.59.103:2376', kwargs['base_url']) + self.assertTrue('ca.pem' in kwargs['tls'].ca_cert) + self.assertTrue('cert.pem' in kwargs['tls'].cert[0]) + self.assertTrue('key.pem' in kwargs['tls'].cert[1]) + self.assertEqual(False, kwargs['tls'].assert_hostname) + self.assertTrue(kwargs['tls'].verify) + try: + client = Client(**kwargs) + self.assertEqual(kwargs['base_url'], client.base_url) + self.assertEqual(kwargs['tls'].ca_cert, client.verify) + self.assertEqual(kwargs['tls'].cert, client.cert) + except TypeError as e: + self.fail(e) + + def test_kwargs_from_env_tls_verify_false(self): + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='') + kwargs = kwargs_from_env(assert_hostname=True) + self.assertEqual('https://192.168.59.103:2376', kwargs['base_url']) + self.assertTrue('ca.pem' in kwargs['tls'].ca_cert) + self.assertTrue('cert.pem' in kwargs['tls'].cert[0]) + self.assertTrue('key.pem' in kwargs['tls'].cert[1]) + self.assertEqual(True, kwargs['tls'].assert_hostname) + self.assertEqual(False, kwargs['tls'].verify) + try: + client = Client(**kwargs) + self.assertEqual(kwargs['base_url'], client.base_url) + self.assertEqual(kwargs['tls'].cert, client.cert) + self.assertFalse(kwargs['tls'].verify) + except TypeError as e: + self.fail(e) + + def test_kwargs_from_env_tls_verify_false_no_cert(self): + temp_dir = tempfile.mkdtemp() + cert_dir = os.path.join(temp_dir, '.docker') + shutil.copytree(TEST_CERT_DIR, cert_dir) + + os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', + HOME=temp_dir, + DOCKER_TLS_VERIFY='') + os.environ.pop('DOCKER_CERT_PATH', None) + kwargs = kwargs_from_env(assert_hostname=True) + self.assertEqual('tcp://192.168.59.103:2376', kwargs['base_url']) + + def test_kwargs_from_env_no_cert_path(self): + try: + temp_dir = tempfile.mkdtemp() + cert_dir = os.path.join(temp_dir, '.docker') + shutil.copytree(TEST_CERT_DIR, cert_dir) + + os.environ.update(HOME=temp_dir, + DOCKER_CERT_PATH='', + DOCKER_TLS_VERIFY='1') + + kwargs = kwargs_from_env() + self.assertTrue(kwargs['tls'].verify) + self.assertIn(cert_dir, kwargs['tls'].ca_cert) + self.assertIn(cert_dir, kwargs['tls'].cert[0]) + self.assertIn(cert_dir, kwargs['tls'].cert[1]) + finally: + if temp_dir: + shutil.rmtree(temp_dir) + + def test_kwargs_from_env_alternate_env(self): + # Values in os.environ are entirely ignored if an alternate is + # provided + os.environ.update( + DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='' + ) + kwargs = kwargs_from_env(environment={ + 'DOCKER_HOST': 'http://docker.gensokyo.jp:2581', + }) + assert 'http://docker.gensokyo.jp:2581' == kwargs['base_url'] + assert 'tls' not in kwargs + + +class ConverVolumeBindsTest(base.BaseTestCase): + def test_convert_volume_binds_empty(self): + self.assertEqual(convert_volume_binds({}), []) + self.assertEqual(convert_volume_binds([]), []) + + def test_convert_volume_binds_list(self): + data = ['/a:/a:ro', '/b:/c:z'] + self.assertEqual(convert_volume_binds(data), data) + + def test_convert_volume_binds_complete(self): + data = { + '/mnt/vol1': { + 'bind': '/data', + 'mode': 'ro' + } + } + self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:ro']) + + def test_convert_volume_binds_compact(self): + data = { + '/mnt/vol1': '/data' + } + self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) + + def test_convert_volume_binds_no_mode(self): + data = { + '/mnt/vol1': { + 'bind': '/data' + } + } + self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) + + def test_convert_volume_binds_unicode_bytes_input(self): + if six.PY2: + expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] + + data = { + '/mnt/지연': { + 'bind': '/unicode/박', + 'mode': 'rw' + } + } + self.assertEqual( + convert_volume_binds(data), expected + ) + else: + expected = ['/mnt/지연:/unicode/박:rw'] + + data = { + bytes('/mnt/지연', 'utf-8'): { + 'bind': bytes('/unicode/박', 'utf-8'), + 'mode': 'rw' + } + } + self.assertEqual( + convert_volume_binds(data), expected + ) + + def test_convert_volume_binds_unicode_unicode_input(self): + if six.PY2: + expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] + + data = { + unicode('/mnt/지연', 'utf-8'): { + 'bind': unicode('/unicode/박', 'utf-8'), + 'mode': 'rw' + } + } + self.assertEqual( + convert_volume_binds(data), expected + ) + else: + expected = ['/mnt/지연:/unicode/박:rw'] + + data = { + '/mnt/지연': { + 'bind': '/unicode/박', + 'mode': 'rw' + } + } + self.assertEqual( + convert_volume_binds(data), expected + ) + + +class ParseEnvFileTest(base.BaseTestCase): + def generate_tempfile(self, file_content=None): + """ + Generates a temporary file for tests with the content + of 'file_content' and returns the filename. + Don't forget to unlink the file with os.unlink() after. + """ + local_tempfile = tempfile.NamedTemporaryFile(delete=False) + local_tempfile.write(file_content.encode('UTF-8')) + local_tempfile.close() + return local_tempfile.name + + def test_parse_env_file_proper(self): + env_file = self.generate_tempfile( + file_content='USER=jdoe\nPASS=secret') + get_parse_env_file = parse_env_file(env_file) + self.assertEqual(get_parse_env_file, + {'USER': 'jdoe', 'PASS': 'secret'}) + os.unlink(env_file) + + def test_parse_env_file_commented_line(self): + env_file = self.generate_tempfile( + file_content='USER=jdoe\n#PASS=secret') + get_parse_env_file = parse_env_file((env_file)) + self.assertEqual(get_parse_env_file, {'USER': 'jdoe'}) + os.unlink(env_file) + + def test_parse_env_file_invalid_line(self): + env_file = self.generate_tempfile( + file_content='USER jdoe') + self.assertRaises( + DockerException, parse_env_file, env_file) + os.unlink(env_file) + + +class ParseHostTest(base.BaseTestCase): + def test_parse_host(self): + invalid_hosts = [ + '0.0.0.0', + 'tcp://', + 'udp://127.0.0.1', + 'udp://127.0.0.1:2375', + ] + + valid_hosts = { + '0.0.0.1:5555': 'http://0.0.0.1:5555', + ':6666': 'http://127.0.0.1:6666', + 'tcp://:7777': 'http://127.0.0.1:7777', + 'http://:7777': 'http://127.0.0.1:7777', + 'https://kokia.jp:2375': 'https://kokia.jp:2375', + 'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock', + 'unix://': 'http+unix://var/run/docker.sock', + 'somehost.net:80/service/swarm': ( + 'http://somehost.net:80/service/swarm' + ), + } + + for host in invalid_hosts: + with pytest.raises(DockerException): + parse_host(host, None) + + for host, expected in valid_hosts.items(): + self.assertEqual(parse_host(host, None), expected, msg=host) + + def test_parse_host_empty_value(self): + unix_socket = 'http+unix://var/run/docker.sock' + tcp_port = 'http://127.0.0.1:2375' + + for val in [None, '']: + for platform in ['darwin', 'linux2', None]: + assert parse_host(val, platform) == unix_socket + + assert parse_host(val, 'win32') == tcp_port + + def test_parse_host_tls(self): + host_value = 'myhost.docker.net:3348' + expected_result = 'https://myhost.docker.net:3348' + assert parse_host(host_value, tls=True) == expected_result + + def test_parse_host_tls_tcp_proto(self): + host_value = 'tcp://myhost.docker.net:3348' + expected_result = 'https://myhost.docker.net:3348' + assert parse_host(host_value, tls=True) == expected_result + + +class ParseRepositoryTagTest(base.BaseTestCase): + sha = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' + + def test_index_image_no_tag(self): + self.assertEqual( + parse_repository_tag("root"), ("root", None) + ) + + def test_index_image_tag(self): + self.assertEqual( + parse_repository_tag("root:tag"), ("root", "tag") + ) + + def test_index_user_image_no_tag(self): + self.assertEqual( + parse_repository_tag("user/repo"), ("user/repo", None) + ) + + def test_index_user_image_tag(self): + self.assertEqual( + parse_repository_tag("user/repo:tag"), ("user/repo", "tag") + ) + + def test_private_reg_image_no_tag(self): + self.assertEqual( + parse_repository_tag("url:5000/repo"), ("url:5000/repo", None) + ) + + def test_private_reg_image_tag(self): + self.assertEqual( + parse_repository_tag("url:5000/repo:tag"), ("url:5000/repo", "tag") + ) + + def test_index_image_sha(self): + self.assertEqual( + parse_repository_tag("root@sha256:{0}".format(self.sha)), + ("root", "sha256:{0}".format(self.sha)) + ) + + def test_private_reg_image_sha(self): + self.assertEqual( + parse_repository_tag("url:5000/repo@sha256:{0}".format(self.sha)), + ("url:5000/repo", "sha256:{0}".format(self.sha)) + ) + + +class ParseDeviceTest(base.BaseTestCase): + def test_dict(self): + devices = parse_devices([{ + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + }]) + self.assertEqual(devices[0], { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + }) + + def test_partial_string_definition(self): + devices = parse_devices(['/dev/sda1']) + self.assertEqual(devices[0], { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/sda1', + 'CgroupPermissions': 'rwm' + }) + + def test_permissionless_string_definition(self): + devices = parse_devices(['/dev/sda1:/dev/mnt1']) + self.assertEqual(devices[0], { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'rwm' + }) + + def test_full_string_definition(self): + devices = parse_devices(['/dev/sda1:/dev/mnt1:r']) + self.assertEqual(devices[0], { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'r' + }) + + def test_hybrid_list(self): + devices = parse_devices([ + '/dev/sda1:/dev/mnt1:rw', + { + 'PathOnHost': '/dev/sda2', + 'PathInContainer': '/dev/mnt2', + 'CgroupPermissions': 'r' + } + ]) + + self.assertEqual(devices[0], { + 'PathOnHost': '/dev/sda1', + 'PathInContainer': '/dev/mnt1', + 'CgroupPermissions': 'rw' + }) + self.assertEqual(devices[1], { + 'PathOnHost': '/dev/sda2', + 'PathInContainer': '/dev/mnt2', + 'CgroupPermissions': 'r' + }) + + +class ParseBytesTest(base.BaseTestCase): + def test_parse_bytes_valid(self): + self.assertEqual(parse_bytes("512MB"), 536870912) + self.assertEqual(parse_bytes("512M"), 536870912) + self.assertEqual(parse_bytes("512m"), 536870912) + + def test_parse_bytes_invalid(self): + self.assertRaises(DockerException, parse_bytes, "512MK") + self.assertRaises(DockerException, parse_bytes, "512L") + self.assertRaises(DockerException, parse_bytes, "127.0.0.1K") + + def test_parse_bytes_float(self): + self.assertRaises(DockerException, parse_bytes, "1.5k") + + def test_parse_bytes_maxint(self): + self.assertEqual( + parse_bytes("{0}k".format(sys.maxsize)), sys.maxsize * 1024 + ) + + +class UtilsTest(base.BaseTestCase): + longMessage = True + + def test_convert_filters(self): + tests = [ + ({'dangling': True}, '{"dangling": ["true"]}'), + ({'dangling': "true"}, '{"dangling": ["true"]}'), + ({'exited': 0}, '{"exited": [0]}'), + ({'exited': [0, 1]}, '{"exited": [0, 1]}'), + ] + + for filters, expected in tests: + self.assertEqual(convert_filters(filters), expected) + + def test_decode_json_header(self): + obj = {'a': 'b', 'c': 1} + data = None + if six.PY3: + data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) + else: + data = base64.urlsafe_b64encode(json.dumps(obj)) + decoded_data = decode_json_header(data) + self.assertEqual(obj, decoded_data) + + def test_create_ipam_config(self): + ipam_pool = create_ipam_pool(subnet='192.168.52.0/24', + gateway='192.168.52.254') + + ipam_config = create_ipam_config(pool_configs=[ipam_pool]) + self.assertEqual(ipam_config, { + 'Driver': 'default', + 'Config': [{ + 'Subnet': '192.168.52.0/24', + 'Gateway': '192.168.52.254', + 'AuxiliaryAddresses': None, + 'IPRange': None, + }] + }) + + +class SplitCommandTest(base.BaseTestCase): + + def test_split_command_with_unicode(self): + if six.PY2: + self.assertEqual( + split_command(unicode('echo μμ', 'utf-8')), + ['echo', 'μμ'] + ) + else: + self.assertEqual(split_command('echo μμ'), ['echo', 'μμ']) + + @pytest.mark.skipif(six.PY3, reason="shlex doesn't support bytes in py3") + def test_split_command_with_bytes(self): + self.assertEqual(split_command('echo μμ'), ['echo', 'μμ']) + + +class PortsTest(base.BaseTestCase): + def test_split_port_with_host_ip(self): + internal_port, external_port = split_port("127.0.0.1:1000:2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, [("127.0.0.1", "1000")]) + + def test_split_port_with_protocol(self): + internal_port, external_port = split_port("127.0.0.1:1000:2000/udp") + self.assertEqual(internal_port, ["2000/udp"]) + self.assertEqual(external_port, [("127.0.0.1", "1000")]) + + def test_split_port_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, [("127.0.0.1", None)]) + + def test_split_port_range_with_host_ip_no_port(self): + internal_port, external_port = split_port("127.0.0.1::2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, + [("127.0.0.1", None), ("127.0.0.1", None)]) + + def test_split_port_with_host_port(self): + internal_port, external_port = split_port("1000:2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, ["1000"]) + + def test_split_port_range_with_host_port(self): + internal_port, external_port = split_port("1000-1001:2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, ["1000", "1001"]) + + def test_split_port_no_host_port(self): + internal_port, external_port = split_port("2000") + self.assertEqual(internal_port, ["2000"]) + self.assertEqual(external_port, None) + + def test_split_port_range_no_host_port(self): + internal_port, external_port = split_port("2000-2001") + self.assertEqual(internal_port, ["2000", "2001"]) + self.assertEqual(external_port, None) + + def test_split_port_range_with_protocol(self): + internal_port, external_port = split_port( + "127.0.0.1:1000-1001:2000-2001/udp") + self.assertEqual(internal_port, ["2000/udp", "2001/udp"]) + self.assertEqual(external_port, + [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) + + def test_split_port_invalid(self): + self.assertRaises(ValueError, + lambda: split_port("0.0.0.0:1000:2000:tcp")) + + def test_non_matching_length_port_ranges(self): + self.assertRaises( + ValueError, + lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp") + ) + + def test_port_and_range_invalid(self): + self.assertRaises(ValueError, + lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) + + def test_port_only_with_colon(self): + self.assertRaises(ValueError, + lambda: split_port(":80")) + + def test_host_only_with_colon(self): + self.assertRaises(ValueError, + lambda: split_port("localhost:")) + + def test_build_port_bindings_with_one_port(self): + port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + + def test_build_port_bindings_with_matching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"]) + self.assertEqual(port_bindings["1000"], + [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) + + def test_build_port_bindings_with_nonmatching_internal_ports(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) + + def test_build_port_bindings_with_port_range(self): + port_bindings = build_port_bindings(["127.0.0.1:1000-1001:1000-1001"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["1001"], [("127.0.0.1", "1001")]) + + def test_build_port_bindings_with_matching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000-1001:1000-1001", "127.0.0.1:2000-2001:1000-1001"]) + self.assertEqual(port_bindings["1000"], + [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) + self.assertEqual(port_bindings["1001"], + [("127.0.0.1", "1001"), ("127.0.0.1", "2001")]) + + def test_build_port_bindings_with_nonmatching_internal_port_ranges(self): + port_bindings = build_port_bindings( + ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) + self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) + self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) + + +class ExcludePathsTest(base.BaseTestCase): + dirs = [ + 'foo', + 'foo/bar', + 'bar', + ] + + files = [ + 'Dockerfile', + 'Dockerfile.alt', + '.dockerignore', + 'a.py', + 'a.go', + 'b.py', + 'cde.py', + 'foo/a.py', + 'foo/b.py', + 'foo/bar/a.py', + 'bar/a.py', + 'foo/Dockerfile3', + ] + + all_paths = set(dirs + files) + + def setUp(self): + self.base = make_tree(self.dirs, self.files) + + def tearDown(self): + shutil.rmtree(self.base) + + def exclude(self, patterns, dockerfile=None): + return set(exclude_paths(self.base, patterns, dockerfile=dockerfile)) + + def test_no_excludes(self): + assert self.exclude(['']) == self.all_paths + + def test_no_dupes(self): + paths = exclude_paths(self.base, ['!a.py']) + assert sorted(paths) == sorted(set(paths)) + + def test_wildcard_exclude(self): + assert self.exclude(['*']) == set(['Dockerfile', '.dockerignore']) + + def test_exclude_dockerfile_dockerignore(self): + """ + Even if the .dockerignore file explicitly says to exclude + Dockerfile and/or .dockerignore, don't exclude them from + the actual tar file. + """ + assert self.exclude(['Dockerfile', '.dockerignore']) == self.all_paths + + def test_exclude_custom_dockerfile(self): + """ + If we're using a custom Dockerfile, make sure that's not + excluded. + """ + assert self.exclude(['*'], dockerfile='Dockerfile.alt') == \ + set(['Dockerfile.alt', '.dockerignore']) + + assert self.exclude(['*'], dockerfile='foo/Dockerfile3') == \ + set(['foo/Dockerfile3', '.dockerignore']) + + def test_exclude_dockerfile_child(self): + includes = self.exclude(['foo/'], dockerfile='foo/Dockerfile3') + assert 'foo/Dockerfile3' in includes + assert 'foo/a.py' not in includes + + def test_single_filename(self): + assert self.exclude(['a.py']) == self.all_paths - set(['a.py']) + + # As odd as it sounds, a filename pattern with a trailing slash on the + # end *will* result in that file being excluded. + def test_single_filename_trailing_slash(self): + assert self.exclude(['a.py/']) == self.all_paths - set(['a.py']) + + def test_wildcard_filename_start(self): + assert self.exclude(['*.py']) == self.all_paths - set([ + 'a.py', 'b.py', 'cde.py', + ]) + + def test_wildcard_with_exception(self): + assert self.exclude(['*.py', '!b.py']) == self.all_paths - set([ + 'a.py', 'cde.py', + ]) + + def test_wildcard_with_wildcard_exception(self): + assert self.exclude(['*.*', '!*.go']) == self.all_paths - set([ + 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', + ]) + + def test_wildcard_filename_end(self): + assert self.exclude(['a.*']) == self.all_paths - set(['a.py', 'a.go']) + + def test_question_mark(self): + assert self.exclude(['?.py']) == self.all_paths - set(['a.py', 'b.py']) + + def test_single_subdir_single_filename(self): + assert self.exclude(['foo/a.py']) == self.all_paths - set(['foo/a.py']) + + def test_single_subdir_wildcard_filename(self): + assert self.exclude(['foo/*.py']) == self.all_paths - set([ + 'foo/a.py', 'foo/b.py', + ]) + + def test_wildcard_subdir_single_filename(self): + assert self.exclude(['*/a.py']) == self.all_paths - set([ + 'foo/a.py', 'bar/a.py', + ]) + + def test_wildcard_subdir_wildcard_filename(self): + assert self.exclude(['*/*.py']) == self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'bar/a.py', + ]) + + def test_directory(self): + assert self.exclude(['foo']) == self.all_paths - set([ + 'foo', 'foo/a.py', 'foo/b.py', + 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' + ]) + + def test_directory_with_trailing_slash(self): + assert self.exclude(['foo']) == self.all_paths - set([ + 'foo', 'foo/a.py', 'foo/b.py', + 'foo/bar', 'foo/bar/a.py', 'foo/Dockerfile3' + ]) + + def test_directory_with_single_exception(self): + assert self.exclude(['foo', '!foo/bar/a.py']) == self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', 'foo/bar', + 'foo/Dockerfile3' + ]) + + def test_directory_with_subdir_exception(self): + assert self.exclude(['foo', '!foo/bar']) == self.all_paths - set([ + 'foo/a.py', 'foo/b.py', 'foo', + 'foo/Dockerfile3' + ]) + + def test_directory_with_wildcard_exception(self): + assert self.exclude(['foo', '!foo/*.py']) == self.all_paths - set([ + 'foo/bar', 'foo/bar/a.py', 'foo', + 'foo/Dockerfile3' + ]) + + def test_subdirectory(self): + assert self.exclude(['foo/bar']) == self.all_paths - set([ + 'foo/bar', 'foo/bar/a.py', + ]) + + +class TarTest(base.Cleanup, base.BaseTestCase): + def test_tar_with_excludes(self): + dirs = [ + 'foo', + 'foo/bar', + 'bar', + ] + + files = [ + 'Dockerfile', + 'Dockerfile.alt', + '.dockerignore', + 'a.py', + 'a.go', + 'b.py', + 'cde.py', + 'foo/a.py', + 'foo/b.py', + 'foo/bar/a.py', + 'bar/a.py', + ] + + exclude = [ + '*.py', + '!b.py', + '!a.go', + 'foo', + 'Dockerfile*', + '.dockerignore', + ] + + expected_names = set([ + 'Dockerfile', + '.dockerignore', + 'a.go', + 'b.py', + 'bar', + 'bar/a.py', + ]) + + base = make_tree(dirs, files) + self.addCleanup(shutil.rmtree, base) + + with tar(base, exclude=exclude) as archive: + tar_data = tarfile.open(fileobj=archive) + assert sorted(tar_data.getnames()) == sorted(expected_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)) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + self.assertEqual(sorted(tar_data.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')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + self.assertEqual( + sorted(tar_data.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')) + with tar(base) as archive: + tar_data = tarfile.open(fileobj=archive) + self.assertEqual( + sorted(tar_data.getnames()), ['bar', 'bar/foo', 'foo'] + ) diff -Nru python-docker-1.5.0/tests/unit/volume_test.py python-docker-1.8.0/tests/unit/volume_test.py --- python-docker-1.5.0/tests/unit/volume_test.py 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/tests/unit/volume_test.py 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,96 @@ +import json + +import pytest + +from .. import base +from .api_test import DockerClientTest, url_prefix, fake_request + + +class VolumeTest(DockerClientTest): + @base.requires_api_version('1.21') + def test_list_volumes(self): + volumes = self.client.volumes() + self.assertIn('Volumes', volumes) + self.assertEqual(len(volumes['Volumes']), 2) + args = fake_request.call_args + + self.assertEqual(args[0][0], 'GET') + self.assertEqual(args[0][1], url_prefix + 'volumes') + + @base.requires_api_version('1.21') + def test_list_volumes_and_filters(self): + volumes = self.client.volumes(filters={'dangling': True}) + assert 'Volumes' in volumes + assert len(volumes['Volumes']) == 2 + args = fake_request.call_args + + assert args[0][0] == 'GET' + assert args[0][1] == url_prefix + 'volumes' + assert args[1] == {'params': {'filters': '{"dangling": ["true"]}'}, + 'timeout': 60} + + @base.requires_api_version('1.21') + def test_create_volume(self): + name = 'perfectcherryblossom' + result = self.client.create_volume(name) + self.assertIn('Name', result) + self.assertEqual(result['Name'], name) + self.assertIn('Driver', result) + self.assertEqual(result['Driver'], 'local') + args = fake_request.call_args + + self.assertEqual(args[0][0], 'POST') + self.assertEqual(args[0][1], url_prefix + 'volumes/create') + self.assertEqual(json.loads(args[1]['data']), {'Name': name}) + + @base.requires_api_version('1.21') + def test_create_volume_with_driver(self): + name = 'perfectcherryblossom' + driver_name = 'sshfs' + self.client.create_volume(name, driver=driver_name) + args = fake_request.call_args + + self.assertEqual(args[0][0], 'POST') + self.assertEqual(args[0][1], url_prefix + 'volumes/create') + data = json.loads(args[1]['data']) + self.assertIn('Driver', data) + self.assertEqual(data['Driver'], driver_name) + + @base.requires_api_version('1.21') + def test_create_volume_invalid_opts_type(self): + with pytest.raises(TypeError): + self.client.create_volume( + 'perfectcherryblossom', driver_opts='hello=world' + ) + + with pytest.raises(TypeError): + self.client.create_volume( + 'perfectcherryblossom', driver_opts=['hello=world'] + ) + + with pytest.raises(TypeError): + self.client.create_volume( + 'perfectcherryblossom', driver_opts='' + ) + + @base.requires_api_version('1.21') + def test_inspect_volume(self): + name = 'perfectcherryblossom' + result = self.client.inspect_volume(name) + self.assertIn('Name', result) + self.assertEqual(result['Name'], name) + self.assertIn('Driver', result) + self.assertEqual(result['Driver'], 'local') + args = fake_request.call_args + + self.assertEqual(args[0][0], 'GET') + self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) + + @base.requires_api_version('1.21') + def test_remove_volume(self): + name = 'perfectcherryblossom' + self.client.remove_volume(name) + args = fake_request.call_args + + self.assertEqual(args[0][0], 'DELETE') + self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name)) diff -Nru python-docker-1.5.0/tests/utils_test.py python-docker-1.8.0/tests/utils_test.py --- python-docker-1.5.0/tests/utils_test.py 2015-10-12 20:51:00.000000000 +0000 +++ python-docker-1.8.0/tests/utils_test.py 1970-01-01 00:00:00.000000000 +0000 @@ -1,792 +0,0 @@ -# -*- coding: utf-8 -*- - -import base64 -import json -import os -import os.path -import shutil -import tempfile - -import pytest -import six - -from docker.client import Client -from docker.constants import DEFAULT_DOCKER_API_VERSION -from docker.errors import DockerException -from docker.utils import ( - parse_repository_tag, parse_host, convert_filters, kwargs_from_env, - create_host_config, Ulimit, LogConfig, parse_bytes, parse_env_file, - exclude_paths, convert_volume_binds, decode_json_header -) -from docker.utils.ports import build_port_bindings, split_port -from docker.auth import ( - resolve_repository_name, resolve_authconfig, encode_header -) - -from . import base -from .helpers import make_tree - - -TEST_CERT_DIR = os.path.join( - os.path.dirname(__file__), - 'testdata/certs', -) - - -class HostConfigTest(base.BaseTestCase): - def test_create_host_config_no_options(self): - config = create_host_config(version='1.19') - self.assertFalse('NetworkMode' in config) - - def test_create_host_config_no_options_newer_api_version(self): - config = create_host_config(version='1.20') - self.assertEqual(config['NetworkMode'], 'default') - - def test_create_host_config_invalid_cpu_cfs_types(self): - with pytest.raises(TypeError): - create_host_config(version='1.20', cpu_quota='0') - - with pytest.raises(TypeError): - create_host_config(version='1.20', cpu_period='0') - - with pytest.raises(TypeError): - create_host_config(version='1.20', cpu_quota=23.11) - - with pytest.raises(TypeError): - create_host_config(version='1.20', cpu_period=1999.0) - - def test_create_host_config_with_cpu_quota(self): - config = create_host_config(version='1.20', cpu_quota=1999) - self.assertEqual(config.get('CpuQuota'), 1999) - - def test_create_host_config_with_cpu_period(self): - config = create_host_config(version='1.20', cpu_period=1999) - self.assertEqual(config.get('CpuPeriod'), 1999) - - -class UlimitTest(base.BaseTestCase): - def test_create_host_config_dict_ulimit(self): - ulimit_dct = {'name': 'nofile', 'soft': 8096} - config = create_host_config( - ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION - ) - self.assertIn('Ulimits', config) - self.assertEqual(len(config['Ulimits']), 1) - ulimit_obj = config['Ulimits'][0] - self.assertTrue(isinstance(ulimit_obj, Ulimit)) - self.assertEqual(ulimit_obj.name, ulimit_dct['name']) - self.assertEqual(ulimit_obj.soft, ulimit_dct['soft']) - self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) - - def test_create_host_config_dict_ulimit_capitals(self): - ulimit_dct = {'Name': 'nofile', 'Soft': 8096, 'Hard': 8096 * 4} - config = create_host_config( - ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION - ) - self.assertIn('Ulimits', config) - self.assertEqual(len(config['Ulimits']), 1) - ulimit_obj = config['Ulimits'][0] - self.assertTrue(isinstance(ulimit_obj, Ulimit)) - self.assertEqual(ulimit_obj.name, ulimit_dct['Name']) - self.assertEqual(ulimit_obj.soft, ulimit_dct['Soft']) - self.assertEqual(ulimit_obj.hard, ulimit_dct['Hard']) - self.assertEqual(ulimit_obj['Soft'], ulimit_obj.soft) - - def test_create_host_config_obj_ulimit(self): - ulimit_dct = Ulimit(name='nofile', soft=8096) - config = create_host_config( - ulimits=[ulimit_dct], version=DEFAULT_DOCKER_API_VERSION - ) - self.assertIn('Ulimits', config) - self.assertEqual(len(config['Ulimits']), 1) - ulimit_obj = config['Ulimits'][0] - self.assertTrue(isinstance(ulimit_obj, Ulimit)) - self.assertEqual(ulimit_obj, ulimit_dct) - - def test_ulimit_invalid_type(self): - self.assertRaises(ValueError, lambda: Ulimit(name=None)) - self.assertRaises(ValueError, lambda: Ulimit(name='hello', soft='123')) - self.assertRaises(ValueError, lambda: Ulimit(name='hello', hard='456')) - - -class LogConfigTest(base.BaseTestCase): - def test_create_host_config_dict_logconfig(self): - dct = {'type': LogConfig.types.SYSLOG, 'config': {'key1': 'val1'}} - config = create_host_config( - version=DEFAULT_DOCKER_API_VERSION, log_config=dct - ) - self.assertIn('LogConfig', config) - self.assertTrue(isinstance(config['LogConfig'], LogConfig)) - self.assertEqual(dct['type'], config['LogConfig'].type) - - def test_create_host_config_obj_logconfig(self): - obj = LogConfig(type=LogConfig.types.SYSLOG, config={'key1': 'val1'}) - config = create_host_config( - version=DEFAULT_DOCKER_API_VERSION, log_config=obj - ) - self.assertIn('LogConfig', config) - self.assertTrue(isinstance(config['LogConfig'], LogConfig)) - self.assertEqual(obj, config['LogConfig']) - - def test_logconfig_invalid_config_type(self): - with pytest.raises(ValueError): - LogConfig(type=LogConfig.types.JSON, config='helloworld') - - -class KwargsFromEnvTest(base.BaseTestCase): - def setUp(self): - self.os_environ = os.environ.copy() - - def tearDown(self): - os.environ = self.os_environ - - def test_kwargs_from_env_empty(self): - os.environ.update(DOCKER_HOST='', - DOCKER_CERT_PATH='', - DOCKER_TLS_VERIFY='') - - kwargs = kwargs_from_env() - self.assertEqual(None, kwargs.get('base_url')) - self.assertEqual(None, kwargs.get('tls')) - - def test_kwargs_from_env_tls(self): - os.environ.update(DOCKER_HOST='tcp://192.168.59.103:2376', - DOCKER_CERT_PATH=TEST_CERT_DIR, - DOCKER_TLS_VERIFY='1') - kwargs = kwargs_from_env(assert_hostname=False) - self.assertEqual('https://192.168.59.103:2376', kwargs['base_url']) - self.assertTrue('ca.pem' in kwargs['tls'].verify) - self.assertTrue('cert.pem' in kwargs['tls'].cert[0]) - self.assertTrue('key.pem' in kwargs['tls'].cert[1]) - self.assertEqual(False, kwargs['tls'].assert_hostname) - try: - client = Client(**kwargs) - self.assertEqual(kwargs['base_url'], client.base_url) - self.assertEqual(kwargs['tls'].verify, client.verify) - self.assertEqual(kwargs['tls'].cert, client.cert) - except TypeError as e: - self.fail(e) - - def test_kwargs_from_env_no_cert_path(self): - try: - temp_dir = tempfile.mkdtemp() - cert_dir = os.path.join(temp_dir, '.docker') - shutil.copytree(TEST_CERT_DIR, cert_dir) - - os.environ.update(HOME=temp_dir, - DOCKER_CERT_PATH='', - DOCKER_TLS_VERIFY='1') - - kwargs = kwargs_from_env() - self.assertIn(cert_dir, kwargs['tls'].verify) - self.assertIn(cert_dir, kwargs['tls'].cert[0]) - self.assertIn(cert_dir, kwargs['tls'].cert[1]) - finally: - if temp_dir: - shutil.rmtree(temp_dir) - - -class UtilsTest(base.BaseTestCase): - longMessage = True - - def generate_tempfile(self, file_content=None): - """ - Generates a temporary file for tests with the content - of 'file_content' and returns the filename. - Don't forget to unlink the file with os.unlink() after. - """ - local_tempfile = tempfile.NamedTemporaryFile(delete=False) - local_tempfile.write(file_content.encode('UTF-8')) - local_tempfile.close() - return local_tempfile.name - - def test_convert_volume_binds_empty(self): - self.assertEqual(convert_volume_binds({}), []) - self.assertEqual(convert_volume_binds([]), []) - - def test_convert_volume_binds_list(self): - data = ['/a:/a:ro', '/b:/c:z'] - self.assertEqual(convert_volume_binds(data), data) - - def test_convert_volume_binds_complete(self): - data = { - '/mnt/vol1': { - 'bind': '/data', - 'mode': 'ro' - } - } - self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:ro']) - - def test_convert_volume_binds_compact(self): - data = { - '/mnt/vol1': '/data' - } - self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) - - def test_convert_volume_binds_no_mode(self): - data = { - '/mnt/vol1': { - 'bind': '/data' - } - } - self.assertEqual(convert_volume_binds(data), ['/mnt/vol1:/data:rw']) - - def test_convert_volume_binds_unicode_bytes_input(self): - if six.PY2: - expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] - - data = { - '/mnt/지연': { - 'bind': '/unicode/박', - 'mode': 'rw' - } - } - self.assertEqual( - convert_volume_binds(data), expected - ) - else: - expected = ['/mnt/지연:/unicode/박:rw'] - - data = { - bytes('/mnt/지연', 'utf-8'): { - 'bind': bytes('/unicode/박', 'utf-8'), - 'mode': 'rw' - } - } - self.assertEqual( - convert_volume_binds(data), expected - ) - - def test_convert_volume_binds_unicode_unicode_input(self): - if six.PY2: - expected = [unicode('/mnt/지연:/unicode/박:rw', 'utf-8')] - - data = { - unicode('/mnt/지연', 'utf-8'): { - 'bind': unicode('/unicode/박', 'utf-8'), - 'mode': 'rw' - } - } - self.assertEqual( - convert_volume_binds(data), expected - ) - else: - expected = ['/mnt/지연:/unicode/박:rw'] - - data = { - '/mnt/지연': { - 'bind': '/unicode/박', - 'mode': 'rw' - } - } - self.assertEqual( - convert_volume_binds(data), expected - ) - - def test_parse_repository_tag(self): - self.assertEqual(parse_repository_tag("root"), - ("root", None)) - self.assertEqual(parse_repository_tag("root:tag"), - ("root", "tag")) - self.assertEqual(parse_repository_tag("user/repo"), - ("user/repo", None)) - self.assertEqual(parse_repository_tag("user/repo:tag"), - ("user/repo", "tag")) - self.assertEqual(parse_repository_tag("url:5000/repo"), - ("url:5000/repo", None)) - self.assertEqual(parse_repository_tag("url:5000/repo:tag"), - ("url:5000/repo", "tag")) - - def test_parse_bytes(self): - self.assertEqual(parse_bytes("512MB"), (536870912)) - self.assertEqual(parse_bytes("512M"), (536870912)) - self.assertRaises(DockerException, parse_bytes, "512MK") - self.assertRaises(DockerException, parse_bytes, "512L") - - def test_parse_host(self): - invalid_hosts = [ - '0.0.0.0', - 'tcp://', - 'udp://127.0.0.1', - 'udp://127.0.0.1:2375', - ] - - valid_hosts = { - '0.0.0.1:5555': 'http://0.0.0.1:5555', - ':6666': 'http://127.0.0.1:6666', - 'tcp://:7777': 'http://127.0.0.1:7777', - 'http://:7777': 'http://127.0.0.1:7777', - 'https://kokia.jp:2375': 'https://kokia.jp:2375', - 'unix:///var/run/docker.sock': 'http+unix:///var/run/docker.sock', - 'unix://': 'http+unix://var/run/docker.sock', - 'somehost.net:80/service/swarm': ( - 'http://somehost.net:80/service/swarm' - ), - } - - for host in invalid_hosts: - with pytest.raises(DockerException): - parse_host(host, None) - - for host, expected in valid_hosts.items(): - self.assertEqual(parse_host(host, None), expected, msg=host) - - def test_parse_host_empty_value(self): - unix_socket = 'http+unix://var/run/docker.sock' - tcp_port = 'http://127.0.0.1:2375' - - for val in [None, '']: - for platform in ['darwin', 'linux2', None]: - assert parse_host(val, platform) == unix_socket - - assert parse_host(val, 'win32') == tcp_port - - def test_parse_env_file_proper(self): - env_file = self.generate_tempfile( - file_content='USER=jdoe\nPASS=secret') - get_parse_env_file = parse_env_file(env_file) - self.assertEqual(get_parse_env_file, - {'USER': 'jdoe', 'PASS': 'secret'}) - os.unlink(env_file) - - def test_parse_env_file_commented_line(self): - env_file = self.generate_tempfile( - file_content='USER=jdoe\n#PASS=secret') - get_parse_env_file = parse_env_file((env_file)) - self.assertEqual(get_parse_env_file, {'USER': 'jdoe'}) - os.unlink(env_file) - - def test_parse_env_file_invalid_line(self): - env_file = self.generate_tempfile( - file_content='USER jdoe') - self.assertRaises( - DockerException, parse_env_file, env_file) - os.unlink(env_file) - - def test_convert_filters(self): - tests = [ - ({'dangling': True}, '{"dangling": ["true"]}'), - ({'dangling': "true"}, '{"dangling": ["true"]}'), - ({'exited': 0}, '{"exited": [0]}'), - ({'exited': [0, 1]}, '{"exited": [0, 1]}'), - ] - - for filters, expected in tests: - self.assertEqual(convert_filters(filters), expected) - - def test_decode_json_header(self): - obj = {'a': 'b', 'c': 1} - data = None - if six.PY3: - data = base64.urlsafe_b64encode(bytes(json.dumps(obj), 'utf-8')) - else: - data = base64.urlsafe_b64encode(json.dumps(obj)) - decoded_data = decode_json_header(data) - self.assertEqual(obj, decoded_data) - - def test_803_urlsafe_encode(self): - auth_data = { - 'username': 'root', - 'password': 'GR?XGR?XGR?XGR?X' - } - encoded = encode_header(auth_data) - assert b'/' not in encoded - assert b'_' in encoded - - def test_resolve_repository_name(self): - # docker hub library image - self.assertEqual( - resolve_repository_name('image'), - ('index.docker.io', 'image'), - ) - - # docker hub image - self.assertEqual( - resolve_repository_name('username/image'), - ('index.docker.io', 'username/image'), - ) - - # private registry - self.assertEqual( - resolve_repository_name('my.registry.net/image'), - ('my.registry.net', 'image'), - ) - - # private registry with port - self.assertEqual( - resolve_repository_name('my.registry.net:5000/image'), - ('my.registry.net:5000', 'image'), - ) - - # private registry with username - self.assertEqual( - resolve_repository_name('my.registry.net/username/image'), - ('my.registry.net', 'username/image'), - ) - - # no dots but port - self.assertEqual( - resolve_repository_name('hostname:5000/image'), - ('hostname:5000', 'image'), - ) - - # no dots but port and username - self.assertEqual( - resolve_repository_name('hostname:5000/username/image'), - ('hostname:5000', 'username/image'), - ) - - # localhost - self.assertEqual( - resolve_repository_name('localhost/image'), - ('localhost', 'image'), - ) - - # localhost with username - self.assertEqual( - resolve_repository_name('localhost/username/image'), - ('localhost', 'username/image'), - ) - - def test_resolve_authconfig(self): - auth_config = { - 'https://index.docker.io/v1/': {'auth': 'indexuser'}, - 'my.registry.net': {'auth': 'privateuser'}, - 'http://legacy.registry.url/v1/': {'auth': 'legacyauth'} - } - # hostname only - self.assertEqual( - resolve_authconfig(auth_config, 'my.registry.net'), - {'auth': 'privateuser'} - ) - # no protocol - self.assertEqual( - resolve_authconfig(auth_config, 'my.registry.net/v1/'), - {'auth': 'privateuser'} - ) - # no path - self.assertEqual( - resolve_authconfig(auth_config, 'http://my.registry.net'), - {'auth': 'privateuser'} - ) - # no path, trailing slash - self.assertEqual( - resolve_authconfig(auth_config, 'http://my.registry.net/'), - {'auth': 'privateuser'} - ) - # no path, wrong secure protocol - self.assertEqual( - resolve_authconfig(auth_config, 'https://my.registry.net'), - {'auth': 'privateuser'} - ) - # no path, wrong insecure protocol - self.assertEqual( - resolve_authconfig(auth_config, 'http://index.docker.io'), - {'auth': 'indexuser'} - ) - # with path, wrong protocol - self.assertEqual( - resolve_authconfig(auth_config, 'https://my.registry.net/v1/'), - {'auth': 'privateuser'} - ) - # default registry - self.assertEqual( - resolve_authconfig(auth_config), {'auth': 'indexuser'} - ) - # default registry (explicit None) - self.assertEqual( - resolve_authconfig(auth_config, None), {'auth': 'indexuser'} - ) - # fully explicit - self.assertEqual( - resolve_authconfig(auth_config, 'http://my.registry.net/v1/'), - {'auth': 'privateuser'} - ) - # legacy entry in config - self.assertEqual( - resolve_authconfig(auth_config, 'legacy.registry.url'), - {'auth': 'legacyauth'} - ) - # no matching entry - self.assertTrue( - resolve_authconfig(auth_config, 'does.not.exist') is None - ) - - def test_resolve_registry_and_auth(self): - auth_config = { - 'https://index.docker.io/v1/': {'auth': 'indexuser'}, - 'my.registry.net': {'auth': 'privateuser'}, - } - - # library image - image = 'image' - self.assertEqual( - resolve_authconfig(auth_config, resolve_repository_name(image)[0]), - {'auth': 'indexuser'}, - ) - - # docker hub image - image = 'username/image' - self.assertEqual( - resolve_authconfig(auth_config, resolve_repository_name(image)[0]), - {'auth': 'indexuser'}, - ) - - # private registry - image = 'my.registry.net/image' - self.assertEqual( - resolve_authconfig(auth_config, resolve_repository_name(image)[0]), - {'auth': 'privateuser'}, - ) - - # unauthenticated registry - image = 'other.registry.net/image' - self.assertEqual( - resolve_authconfig(auth_config, resolve_repository_name(image)[0]), - None, - ) - - -class PortsTest(base.BaseTestCase): - def test_split_port_with_host_ip(self): - internal_port, external_port = split_port("127.0.0.1:1000:2000") - self.assertEqual(internal_port, ["2000"]) - self.assertEqual(external_port, [("127.0.0.1", "1000")]) - - def test_split_port_with_protocol(self): - internal_port, external_port = split_port("127.0.0.1:1000:2000/udp") - self.assertEqual(internal_port, ["2000/udp"]) - self.assertEqual(external_port, [("127.0.0.1", "1000")]) - - def test_split_port_with_host_ip_no_port(self): - internal_port, external_port = split_port("127.0.0.1::2000") - self.assertEqual(internal_port, ["2000"]) - self.assertEqual(external_port, [("127.0.0.1", None)]) - - def test_split_port_range_with_host_ip_no_port(self): - internal_port, external_port = split_port("127.0.0.1::2000-2001") - self.assertEqual(internal_port, ["2000", "2001"]) - self.assertEqual(external_port, - [("127.0.0.1", None), ("127.0.0.1", None)]) - - def test_split_port_with_host_port(self): - internal_port, external_port = split_port("1000:2000") - self.assertEqual(internal_port, ["2000"]) - self.assertEqual(external_port, ["1000"]) - - def test_split_port_range_with_host_port(self): - internal_port, external_port = split_port("1000-1001:2000-2001") - self.assertEqual(internal_port, ["2000", "2001"]) - self.assertEqual(external_port, ["1000", "1001"]) - - def test_split_port_no_host_port(self): - internal_port, external_port = split_port("2000") - self.assertEqual(internal_port, ["2000"]) - self.assertEqual(external_port, None) - - def test_split_port_range_no_host_port(self): - internal_port, external_port = split_port("2000-2001") - self.assertEqual(internal_port, ["2000", "2001"]) - self.assertEqual(external_port, None) - - def test_split_port_range_with_protocol(self): - internal_port, external_port = split_port( - "127.0.0.1:1000-1001:2000-2001/udp") - self.assertEqual(internal_port, ["2000/udp", "2001/udp"]) - self.assertEqual(external_port, - [("127.0.0.1", "1000"), ("127.0.0.1", "1001")]) - - def test_split_port_invalid(self): - self.assertRaises(ValueError, - lambda: split_port("0.0.0.0:1000:2000:tcp")) - - def test_non_matching_length_port_ranges(self): - self.assertRaises( - ValueError, - lambda: split_port("0.0.0.0:1000-1010:2000-2002/tcp") - ) - - def test_port_and_range_invalid(self): - self.assertRaises(ValueError, - lambda: split_port("0.0.0.0:1000:2000-2002/tcp")) - - def test_port_only_with_colon(self): - self.assertRaises(ValueError, - lambda: split_port(":80")) - - def test_host_only_with_colon(self): - self.assertRaises(ValueError, - lambda: split_port("localhost:")) - - def test_build_port_bindings_with_one_port(self): - port_bindings = build_port_bindings(["127.0.0.1:1000:1000"]) - self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) - - def test_build_port_bindings_with_matching_internal_ports(self): - port_bindings = build_port_bindings( - ["127.0.0.1:1000:1000", "127.0.0.1:2000:1000"]) - self.assertEqual(port_bindings["1000"], - [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) - - def test_build_port_bindings_with_nonmatching_internal_ports(self): - port_bindings = build_port_bindings( - ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) - self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) - self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) - - def test_build_port_bindings_with_port_range(self): - port_bindings = build_port_bindings(["127.0.0.1:1000-1001:1000-1001"]) - self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) - self.assertEqual(port_bindings["1001"], [("127.0.0.1", "1001")]) - - def test_build_port_bindings_with_matching_internal_port_ranges(self): - port_bindings = build_port_bindings( - ["127.0.0.1:1000-1001:1000-1001", "127.0.0.1:2000-2001:1000-1001"]) - self.assertEqual(port_bindings["1000"], - [("127.0.0.1", "1000"), ("127.0.0.1", "2000")]) - self.assertEqual(port_bindings["1001"], - [("127.0.0.1", "1001"), ("127.0.0.1", "2001")]) - - def test_build_port_bindings_with_nonmatching_internal_port_ranges(self): - port_bindings = build_port_bindings( - ["127.0.0.1:1000:1000", "127.0.0.1:2000:2000"]) - self.assertEqual(port_bindings["1000"], [("127.0.0.1", "1000")]) - self.assertEqual(port_bindings["2000"], [("127.0.0.1", "2000")]) - - -class ExcludePathsTest(base.BaseTestCase): - dirs = [ - 'foo', - 'foo/bar', - 'bar', - ] - - files = [ - 'Dockerfile', - 'Dockerfile.alt', - '.dockerignore', - 'a.py', - 'a.go', - 'b.py', - 'cde.py', - 'foo/a.py', - 'foo/b.py', - 'foo/bar/a.py', - 'bar/a.py', - ] - - all_paths = set(dirs + files) - - def setUp(self): - self.base = make_tree(self.dirs, self.files) - - def tearDown(self): - shutil.rmtree(self.base) - - def exclude(self, patterns, dockerfile=None): - return set(exclude_paths(self.base, patterns, dockerfile=dockerfile)) - - def test_no_excludes(self): - assert self.exclude(['']) == self.all_paths - - def test_no_dupes(self): - paths = exclude_paths(self.base, ['!a.py']) - assert sorted(paths) == sorted(set(paths)) - - def test_wildcard_exclude(self): - assert self.exclude(['*']) == set(['Dockerfile', '.dockerignore']) - - def test_exclude_dockerfile_dockerignore(self): - """ - Even if the .dockerignore file explicitly says to exclude - Dockerfile and/or .dockerignore, don't exclude them from - the actual tar file. - """ - assert self.exclude(['Dockerfile', '.dockerignore']) == self.all_paths - - def test_exclude_custom_dockerfile(self): - """ - If we're using a custom Dockerfile, make sure that's not - excluded. - """ - assert self.exclude(['*'], dockerfile='Dockerfile.alt') == \ - set(['Dockerfile.alt', '.dockerignore']) - - def test_single_filename(self): - assert self.exclude(['a.py']) == self.all_paths - set(['a.py']) - - # As odd as it sounds, a filename pattern with a trailing slash on the - # end *will* result in that file being excluded. - def test_single_filename_trailing_slash(self): - assert self.exclude(['a.py/']) == self.all_paths - set(['a.py']) - - def test_wildcard_filename_start(self): - assert self.exclude(['*.py']) == self.all_paths - set([ - 'a.py', 'b.py', 'cde.py', - ]) - - def test_wildcard_with_exception(self): - assert self.exclude(['*.py', '!b.py']) == self.all_paths - set([ - 'a.py', 'cde.py', - ]) - - def test_wildcard_with_wildcard_exception(self): - assert self.exclude(['*.*', '!*.go']) == self.all_paths - set([ - 'a.py', 'b.py', 'cde.py', 'Dockerfile.alt', - ]) - - def test_wildcard_filename_end(self): - assert self.exclude(['a.*']) == self.all_paths - set(['a.py', 'a.go']) - - def test_question_mark(self): - assert self.exclude(['?.py']) == self.all_paths - set(['a.py', 'b.py']) - - def test_single_subdir_single_filename(self): - assert self.exclude(['foo/a.py']) == self.all_paths - set(['foo/a.py']) - - def test_single_subdir_wildcard_filename(self): - assert self.exclude(['foo/*.py']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', - ]) - - def test_wildcard_subdir_single_filename(self): - assert self.exclude(['*/a.py']) == self.all_paths - set([ - 'foo/a.py', 'bar/a.py', - ]) - - def test_wildcard_subdir_wildcard_filename(self): - assert self.exclude(['*/*.py']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', 'bar/a.py', - ]) - - def test_directory(self): - assert self.exclude(['foo']) == self.all_paths - set([ - 'foo', 'foo/a.py', 'foo/b.py', - 'foo/bar', 'foo/bar/a.py', - ]) - - def test_directory_with_trailing_slash(self): - assert self.exclude(['foo']) == self.all_paths - set([ - 'foo', 'foo/a.py', 'foo/b.py', - 'foo/bar', 'foo/bar/a.py', - ]) - - def test_directory_with_single_exception(self): - assert self.exclude(['foo', '!foo/bar/a.py']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', - ]) - - def test_directory_with_subdir_exception(self): - assert self.exclude(['foo', '!foo/bar']) == self.all_paths - set([ - 'foo/a.py', 'foo/b.py', - ]) - - def test_directory_with_wildcard_exception(self): - assert self.exclude(['foo', '!foo/*.py']) == self.all_paths - set([ - 'foo/bar', 'foo/bar/a.py', - ]) - - def test_subdirectory(self): - assert self.exclude(['foo/bar']) == self.all_paths - set([ - 'foo/bar', 'foo/bar/a.py', - ]) diff -Nru python-docker-1.5.0/.travis.yml python-docker-1.8.0/.travis.yml --- python-docker-1.5.0/.travis.yml 1970-01-01 00:00:00.000000000 +0000 +++ python-docker-1.8.0/.travis.yml 2016-04-11 14:18:22.000000000 +0000 @@ -0,0 +1,14 @@ +sudo: false +language: python +python: + - "2.7" +env: + - TOX_ENV=py26 + - TOX_ENV=py27 + - TOX_ENV=py33 + - TOX_ENV=py34 + - TOX_ENV=flake8 +install: + - pip install tox +script: + - tox -e $TOX_ENV