mirror of https://github.com/docker/docker-py.git
Generic skip decorator for low API version accessible to all tests
Add simpler version comparison functions Add decorator to enforce minimum version in API methods Fix utils imports Add minimum_version decorators on API methods that needed it GroupAdd test requires API version >= 1.20 Signed-off-by: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
ba6df5a2c0
commit
acd5e634ff
|
@ -4,4 +4,4 @@ from .container import ContainerApiMixin
|
|||
from .daemon import DaemonApiMixin
|
||||
from .exec_api import ExecApiMixin
|
||||
from .image import ImageApiMixin
|
||||
from .volume import VolumeApiMixin
|
||||
from .volume import VolumeApiMixin
|
||||
|
|
|
@ -4,8 +4,8 @@ import re
|
|||
|
||||
from .. import constants
|
||||
from .. import errors
|
||||
from ..auth import auth
|
||||
from ..utils import utils
|
||||
from .. import auth
|
||||
from .. import utils
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
|
@ -2,11 +2,11 @@ import six
|
|||
import warnings
|
||||
|
||||
from .. import errors
|
||||
from ..utils import utils, check_resource
|
||||
from .. import utils
|
||||
|
||||
|
||||
class ContainerApiMixin(object):
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def attach(self, container, stdout=True, stderr=True,
|
||||
stream=False, logs=False):
|
||||
params = {
|
||||
|
@ -20,7 +20,7 @@ class ContainerApiMixin(object):
|
|||
|
||||
return self._get_result(container, stream, response)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def attach_socket(self, container, params=None, ws=False):
|
||||
if params is None:
|
||||
params = {
|
||||
|
@ -36,7 +36,7 @@ class ContainerApiMixin(object):
|
|||
return self._get_raw_response_socket(self.post(
|
||||
u, None, params=self._attach_params(params), stream=True))
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def commit(self, container, repository=None, tag=None, message=None,
|
||||
author=None, conf=None):
|
||||
params = {
|
||||
|
@ -73,7 +73,7 @@ class ContainerApiMixin(object):
|
|||
x['Id'] = x['Id'][:12]
|
||||
return res
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def copy(self, container, resource):
|
||||
res = self._post_json(
|
||||
self._url("/containers/{0}/copy".format(container)),
|
||||
|
@ -131,13 +131,13 @@ class ContainerApiMixin(object):
|
|||
kwargs['version'] = self._version
|
||||
return utils.create_host_config(*args, **kwargs)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def diff(self, container):
|
||||
return self._result(
|
||||
self._get(self._url("/containers/{0}/changes", container)), True
|
||||
)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def export(self, container):
|
||||
res = self._get(
|
||||
self._url("/containers/{0}/export", container), stream=True
|
||||
|
@ -145,13 +145,13 @@ class ContainerApiMixin(object):
|
|||
self._raise_for_status(res)
|
||||
return res.raw
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def inspect_container(self, container):
|
||||
return self._result(
|
||||
self._get(self._url("/containers/{0}/json", container)), True
|
||||
)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def kill(self, container, signal=None):
|
||||
url = self._url("/containers/{0}/kill", container)
|
||||
params = {}
|
||||
|
@ -161,7 +161,7 @@ class ContainerApiMixin(object):
|
|||
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def logs(self, container, stdout=True, stderr=True, stream=False,
|
||||
timestamps=False, tail='all'):
|
||||
if utils.compare_version('1.11', self._version) >= 0:
|
||||
|
@ -185,13 +185,13 @@ class ContainerApiMixin(object):
|
|||
logs=True
|
||||
)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def pause(self, container):
|
||||
url = self._url('/containers/{0}/pause', container)
|
||||
res = self._post(url)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def port(self, container, private_port):
|
||||
res = self._get(self._url("/containers/{0}/json", container))
|
||||
self._raise_for_status(res)
|
||||
|
@ -211,7 +211,7 @@ class ContainerApiMixin(object):
|
|||
|
||||
return h_ports
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def remove_container(self, container, v=False, link=False, force=False):
|
||||
params = {'v': v, 'link': link, 'force': force}
|
||||
res = self._delete(
|
||||
|
@ -219,32 +219,29 @@ class ContainerApiMixin(object):
|
|||
)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.minimum_version('1.17')
|
||||
@utils.check_resource
|
||||
def rename(self, container, name):
|
||||
if utils.compare_version('1.17', self._version) < 0:
|
||||
raise errors.InvalidVersion(
|
||||
'rename was only introduced in API version 1.17'
|
||||
)
|
||||
url = self._url("/containers/{0}/rename", container)
|
||||
params = {'name': name}
|
||||
res = self._post(url, params=params)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def resize(self, container, height, width):
|
||||
params = {'h': height, 'w': width}
|
||||
url = self._url("/containers/{0}/resize", container)
|
||||
res = self._post(url, params=params)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def restart(self, container, timeout=10):
|
||||
params = {'t': timeout}
|
||||
url = self._url("/containers/{0}/restart", container)
|
||||
res = self._post(url, params=params)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def start(self, container, binds=None, port_bindings=None, lxc_conf=None,
|
||||
publish_all_ports=None, links=None, privileged=None,
|
||||
dns=None, dns_search=None, volumes_from=None, network_mode=None,
|
||||
|
@ -312,16 +309,13 @@ class ContainerApiMixin(object):
|
|||
res = self._post_json(url, data=start_config)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.minimum_version('1.17')
|
||||
@utils.check_resource
|
||||
def stats(self, container, decode=None):
|
||||
if utils.compare_version('1.17', self._version) < 0:
|
||||
raise errors.InvalidVersion(
|
||||
'Stats retrieval is not supported in API < 1.17!')
|
||||
|
||||
url = self._url("/containers/{0}/stats", container)
|
||||
return self._stream_helper(self._get(url, stream=True), decode=decode)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def stop(self, container, timeout=10):
|
||||
params = {'t': timeout}
|
||||
url = self._url("/containers/{0}/stop", container)
|
||||
|
@ -330,18 +324,18 @@ class ContainerApiMixin(object):
|
|||
timeout=(timeout + (self.timeout or 0)))
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def top(self, container):
|
||||
u = self._url("/containers/{0}/top", container)
|
||||
return self._result(self._get(u), True)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def unpause(self, container):
|
||||
url = self._url('/containers/{0}/unpause', container)
|
||||
res = self._post(url)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def wait(self, container, timeout=None):
|
||||
url = self._url("/containers/{0}/wait", container)
|
||||
res = self._post(url, timeout=timeout)
|
||||
|
|
|
@ -3,15 +3,14 @@ import shlex
|
|||
import six
|
||||
|
||||
from .. import errors
|
||||
from ..utils import utils, check_resource
|
||||
from .. import utils
|
||||
|
||||
|
||||
class ExecApiMixin(object):
|
||||
@check_resource
|
||||
@utils.minimum_version('1.15')
|
||||
@utils.check_resource
|
||||
def exec_create(self, container, cmd, stdout=True, stderr=True, tty=False,
|
||||
privileged=False, user=''):
|
||||
if utils.compare_version('1.15', self._version) < 0:
|
||||
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||
if privileged and utils.compare_version('1.19', self._version) < 0:
|
||||
raise errors.InvalidVersion(
|
||||
'Privileged exec is not supported in API < 1.19'
|
||||
|
@ -38,19 +37,15 @@ class ExecApiMixin(object):
|
|||
res = self._post_json(url, data=data)
|
||||
return self._result(res, True)
|
||||
|
||||
@utils.minimum_version('1.16')
|
||||
def exec_inspect(self, exec_id):
|
||||
if utils.compare_version('1.16', self._version) < 0:
|
||||
raise errors.InvalidVersion(
|
||||
'exec_inspect is not supported in API < 1.16'
|
||||
)
|
||||
if isinstance(exec_id, dict):
|
||||
exec_id = exec_id.get('Id')
|
||||
res = self._get(self._url("/exec/{0}/json", exec_id))
|
||||
return self._result(res, True)
|
||||
|
||||
@utils.minimum_version('1.15')
|
||||
def exec_resize(self, exec_id, height=None, width=None):
|
||||
if utils.compare_version('1.15', self._version) < 0:
|
||||
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||
if isinstance(exec_id, dict):
|
||||
exec_id = exec_id.get('Id')
|
||||
|
||||
|
@ -59,9 +54,8 @@ class ExecApiMixin(object):
|
|||
res = self._post(url, params=params)
|
||||
self._raise_for_status(res)
|
||||
|
||||
@utils.minimum_version('1.15')
|
||||
def exec_start(self, exec_id, detach=False, tty=False, stream=False):
|
||||
if utils.compare_version('1.15', self._version) < 0:
|
||||
raise errors.InvalidVersion('Exec is not supported in API < 1.15')
|
||||
if isinstance(exec_id, dict):
|
||||
exec_id = exec_id.get('Id')
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ import warnings
|
|||
|
||||
from ..auth import auth
|
||||
from ..constants import INSECURE_REGISTRY_DEPRECATION_WARNING
|
||||
from ..utils import utils, check_resource
|
||||
from .. import utils
|
||||
from .. import errors
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -12,13 +12,13 @@ log = logging.getLogger(__name__)
|
|||
|
||||
class ImageApiMixin(object):
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def get_image(self, image):
|
||||
res = self._get(self._url("/images/{0}/get", image), stream=True)
|
||||
self._raise_for_status(res)
|
||||
return res.raw
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def history(self, image):
|
||||
res = self._get(self._url("/images/{0}/history", image))
|
||||
return self._result(res, True)
|
||||
|
@ -124,7 +124,7 @@ class ImageApiMixin(object):
|
|||
return self._result(
|
||||
self._post(u, data=None, params=params))
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def insert(self, image, url, path):
|
||||
if utils.compare_version('1.12', self._version) >= 0:
|
||||
raise errors.DeprecatedMethod(
|
||||
|
@ -137,7 +137,7 @@ class ImageApiMixin(object):
|
|||
}
|
||||
return self._result(self._post(api_url, params=params))
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def inspect_image(self, image):
|
||||
return self._result(
|
||||
self._get(self._url("/images/{0}/json", image)), True
|
||||
|
@ -246,7 +246,7 @@ class ImageApiMixin(object):
|
|||
|
||||
return self._result(response)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def remove_image(self, image, force=False, noprune=False):
|
||||
params = {'force': force, 'noprune': noprune}
|
||||
res = self._delete(self._url("/images/{0}", image), params=params)
|
||||
|
@ -258,7 +258,7 @@ class ImageApiMixin(object):
|
|||
True
|
||||
)
|
||||
|
||||
@check_resource
|
||||
@utils.check_resource
|
||||
def tag(self, image, repository, tag=None, force=False):
|
||||
params = {
|
||||
'tag': tag,
|
||||
|
|
|
@ -1,22 +1,8 @@
|
|||
import functools
|
||||
|
||||
from .. import errors
|
||||
from ..utils import utils
|
||||
|
||||
|
||||
def check_api_version(f):
|
||||
@functools.wraps(f)
|
||||
def wrapped(self, *args, **kwargs):
|
||||
if utils.compare_version('1.21', self._version) < 0:
|
||||
raise errors.InvalidVersion(
|
||||
'The volume API is not available for API version < 1.21'
|
||||
)
|
||||
return f(self, *args, **kwargs)
|
||||
return wrapped
|
||||
from .. import utils
|
||||
|
||||
|
||||
class VolumeApiMixin(object):
|
||||
@check_api_version
|
||||
@utils.minimum_version('1.21')
|
||||
def volumes(self, filters=None):
|
||||
params = {
|
||||
'filter': utils.convert_filters(filters) if filters else None
|
||||
|
@ -24,7 +10,7 @@ class VolumeApiMixin(object):
|
|||
url = self._url('/volumes')
|
||||
return self._result(self._get(url, params=params), True)
|
||||
|
||||
@check_api_version
|
||||
@utils.minimum_version('1.21')
|
||||
def create_volume(self, name, driver=None, driver_opts=None):
|
||||
url = self._url('/volumes')
|
||||
if driver_opts is not None and not isinstance(driver_opts, dict):
|
||||
|
@ -37,12 +23,12 @@ class VolumeApiMixin(object):
|
|||
}
|
||||
return self._result(self._post_json(url, data=data), True)
|
||||
|
||||
@check_api_version
|
||||
@utils.minimum_version('1.21')
|
||||
def inspect_volume(self, name):
|
||||
url = self._url('/volumes/{0}', name)
|
||||
return self._result(self._get(url), True)
|
||||
|
||||
@check_api_version
|
||||
@utils.minimum_version('1.21')
|
||||
def remove_volume(self, name):
|
||||
url = self._url('/volumes/{0}', name)
|
||||
resp = self._delete(url)
|
||||
|
|
|
@ -2,8 +2,9 @@ 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,
|
||||
create_container_config, parse_bytes, ping_registry, parse_env_file
|
||||
create_container_config, parse_bytes, ping_registry, parse_env_file,
|
||||
version_lt, version_gte
|
||||
) # flake8: noqa
|
||||
|
||||
from .types import Ulimit, LogConfig # flake8: noqa
|
||||
from .decorators import check_resource #flake8: noqa
|
||||
from .decorators import check_resource, minimum_version #flake8: noqa
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import functools
|
||||
|
||||
from .. import errors
|
||||
from . import utils
|
||||
|
||||
|
||||
def check_resource(f):
|
||||
|
@ -19,3 +20,18 @@ def check_resource(f):
|
|||
)
|
||||
return f(self, resource_id, *args, **kwargs)
|
||||
return wrapped
|
||||
|
||||
|
||||
def minimum_version(version):
|
||||
def decorator(f):
|
||||
@functools.wraps(f)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if utils.version_lt(self._version, version):
|
||||
raise errors.InvalidVersion(
|
||||
'{0} is not available for version < {1}'.format(
|
||||
f.__name__, version
|
||||
)
|
||||
)
|
||||
return f(self, *args, **kwargs)
|
||||
return wrapper
|
||||
return decorator
|
||||
|
|
|
@ -164,6 +164,14 @@ def compare_version(v1, v2):
|
|||
return 1
|
||||
|
||||
|
||||
def version_lt(v1, v2):
|
||||
return compare_version(v1, v2) > 0
|
||||
|
||||
|
||||
def version_gte(v1, v2):
|
||||
return not version_lt(v1, v2)
|
||||
|
||||
|
||||
def ping_registry(url):
|
||||
warnings.warn(
|
||||
'The `ping_registry` method is deprecated and will be removed.',
|
||||
|
|
|
@ -1,11 +1,23 @@
|
|||
import sys
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
import six
|
||||
|
||||
import docker
|
||||
|
||||
|
||||
class BaseTestCase(unittest.TestCase):
|
||||
def assertIn(self, object, collection):
|
||||
if six.PY2 and sys.version_info[1] <= 6:
|
||||
return self.assertTrue(object in collection)
|
||||
return super(BaseTestCase, self).assertIn(object, collection)
|
||||
|
||||
|
||||
def requires_api_version(version):
|
||||
return pytest.mark.skipif(
|
||||
docker.utils.version_lt(
|
||||
docker.constants.DEFAULT_DOCKER_API_VERSION, version
|
||||
),
|
||||
reason="API version is too low (< {0})".format(version)
|
||||
)
|
||||
|
|
|
@ -27,17 +27,18 @@ import time
|
|||
import unittest
|
||||
import warnings
|
||||
|
||||
import docker
|
||||
from docker.utils import kwargs_from_env
|
||||
import pytest
|
||||
import six
|
||||
|
||||
from six.moves import BaseHTTPServer
|
||||
from six.moves import socketserver
|
||||
|
||||
from .test import Cleanup
|
||||
import docker
|
||||
from docker.errors import APIError
|
||||
from docker.utils import kwargs_from_env
|
||||
|
||||
from .base import requires_api_version
|
||||
from .test import Cleanup
|
||||
|
||||
import pytest
|
||||
|
||||
# FIXME: missing tests for
|
||||
# export; history; insert; port; push; tag; get; load; stats
|
||||
|
@ -285,6 +286,7 @@ class TestCreateContainerWithRoBinds(BaseTestCase):
|
|||
self.assertFalse(inspect_data['VolumesRW'][mount_dest])
|
||||
|
||||
|
||||
@requires_api_version('1.20')
|
||||
class CreateContainerWithGroupAddTest(BaseTestCase):
|
||||
def test_group_id_ints(self):
|
||||
container = self.client.create_container(
|
||||
|
@ -1385,9 +1387,7 @@ class TestImportFromURL(ImportTestCase):
|
|||
# VOLUMES TESTS #
|
||||
#################
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="Volume API available for version >=1.21")
|
||||
@requires_api_version('1.21')
|
||||
class TestVolumes(BaseTestCase):
|
||||
def test_create_volume(self):
|
||||
name = 'perfectcherryblossom'
|
||||
|
|
|
@ -2107,9 +2107,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
# VOLUMES TESTS #
|
||||
###################
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_list_volumes(self):
|
||||
volumes = self.client.volumes()
|
||||
self.assertIn('Volumes', volumes)
|
||||
|
@ -2119,9 +2117,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
self.assertEqual(args[0][0], 'GET')
|
||||
self.assertEqual(args[0][1], url_prefix + 'volumes')
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_create_volume(self):
|
||||
name = 'perfectcherryblossom'
|
||||
result = self.client.create_volume(name)
|
||||
|
@ -2137,9 +2133,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
'Name': name, 'Driver': None, 'DriverOpts': None
|
||||
})
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_create_volume_with_driver(self):
|
||||
name = 'perfectcherryblossom'
|
||||
driver_name = 'sshfs'
|
||||
|
@ -2151,9 +2145,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
self.assertIn('Driver', args[1]['data'])
|
||||
self.assertEqual(args[1]['data']['Driver'], driver_name)
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_create_volume_invalid_opts_type(self):
|
||||
with pytest.raises(TypeError):
|
||||
self.client.create_volume(
|
||||
|
@ -2170,9 +2162,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
'perfectcherryblossom', driver_opts=''
|
||||
)
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_inspect_volume(self):
|
||||
name = 'perfectcherryblossom'
|
||||
result = self.client.inspect_volume(name)
|
||||
|
@ -2185,9 +2175,7 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
self.assertEqual(args[0][0], 'GET')
|
||||
self.assertEqual(args[0][1], '{0}volumes/{1}'.format(url_prefix, name))
|
||||
|
||||
@pytest.mark.skipif(docker.utils.compare_version(
|
||||
'1.21', docker.constants.DEFAULT_DOCKER_API_VERSION
|
||||
) < 0, reason="API version too low")
|
||||
@base.requires_api_version('1.21')
|
||||
def test_remove_volume(self):
|
||||
name = 'perfectcherryblossom'
|
||||
result = self.client.remove_volume(name)
|
||||
|
|
Loading…
Reference in New Issue