Merge branch 'TomasTomecek-autodetect-api-version'

This commit is contained in:
Joffrey F 2015-03-05 12:12:27 -08:00
commit 7c4ed86d8d
6 changed files with 99 additions and 15 deletions

View File

@ -17,4 +17,4 @@ from .version import version
__version__ = version __version__ = version
__title__ = 'docker-py' __title__ = 'docker-py'
from .client import Client # flake8: noqa from .client import Client, AutoVersionClient # flake8: noqa

View File

@ -40,7 +40,7 @@ STREAM_HEADER_SIZE_BYTES = 8
class Client(requests.Session): class Client(requests.Session):
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION, def __init__(self, base_url=None, version=None,
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False): timeout=DEFAULT_TIMEOUT_SECONDS, tls=False):
super(Client, self).__init__() super(Client, self).__init__()
base_url = utils.parse_host(base_url) base_url = utils.parse_host(base_url)
@ -50,15 +50,8 @@ class Client(requests.Session):
raise errors.TLSParameterError( raise errors.TLSParameterError(
'If using TLS, the base_url argument must begin with ' 'If using TLS, the base_url argument must begin with '
'"https://".') '"https://".')
if not isinstance(version, six.string_types):
raise errors.DockerException(
'version parameter must be a string. Found {0}'.format(
type(version).__name__
)
)
self.base_url = base_url self.base_url = base_url
self.timeout = timeout self.timeout = timeout
self._version = version
self._auth_configs = auth.load_config() self._auth_configs = auth.load_config()
# Use SSLAdapter for the ability to specify SSL version # Use SSLAdapter for the ability to specify SSL version
@ -69,6 +62,34 @@ class Client(requests.Session):
else: else:
self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout)) self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout))
# version detection needs to be after unix adapter mounting
if version is None:
self._version = DEFAULT_DOCKER_API_VERSION
elif isinstance(version, six.string_types):
if version.lower() == 'auto':
self._version = self._retrieve_server_version()
else:
self._version = version
else:
raise errors.DockerException(
'Version parameter must be a string or None. Found {0}'.format(
type(version).__name__
)
)
def _retrieve_server_version(self):
try:
return self.version(api_version=False)["ApiVersion"]
except KeyError:
raise errors.DockerException(
'Invalid response from docker daemon: key "ApiVersion"'
' is missing.'
)
except Exception as e:
raise errors.DockerException(
'Error while fetching server API version: {0}'.format(e)
)
def _set_request_timeout(self, kwargs): def _set_request_timeout(self, kwargs):
"""Prepare the kwargs for an HTTP request by inserting the timeout """Prepare the kwargs for an HTTP request by inserting the timeout
parameter, if not already present.""" parameter, if not already present."""
@ -84,8 +105,11 @@ class Client(requests.Session):
def _delete(self, url, **kwargs): def _delete(self, url, **kwargs):
return self.delete(url, **self._set_request_timeout(kwargs)) return self.delete(url, **self._set_request_timeout(kwargs))
def _url(self, path): def _url(self, path, versioned_api=True):
return '{0}/v{1}{2}'.format(self.base_url, self._version, path) if versioned_api:
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
else:
return '{0}{1}'.format(self.base_url, path)
def _raise_for_status(self, response, explanation=None): def _raise_for_status(self, response, explanation=None):
"""Raises stored :class:`APIError`, if one occurred.""" """Raises stored :class:`APIError`, if one occurred."""
@ -914,8 +938,9 @@ class Client(requests.Session):
u = self._url("/containers/{0}/top".format(container)) u = self._url("/containers/{0}/top".format(container))
return self._result(self._get(u), True) return self._result(self._get(u), True)
def version(self): def version(self, api_version=True):
return self._result(self._get(self._url("/version")), True) url = self._url("/version", versioned_api=api_version)
return self._result(self._get(url), json=True)
def unpause(self, container): def unpause(self, container):
if isinstance(container, dict): if isinstance(container, dict):
@ -934,3 +959,13 @@ class Client(requests.Session):
if 'StatusCode' in json_: if 'StatusCode' in json_:
return json_['StatusCode'] return json_['StatusCode']
return -1 return -1
class AutoVersionClient(Client):
def __init__(self, *args, **kwargs):
if 'version' in kwargs and kwargs['version']:
raise errors.DockerException(
'Can not specify version for AutoVersionClient'
)
kwargs['version'] = 'auto'
super(AutoVersionClient, self).__init__(*args, **kwargs)

View File

@ -12,7 +12,8 @@ c = Client(base_url='unix://var/run/docker.sock')
* base_url (str): Refers to the protocol+hostname+port where the Docker server * base_url (str): Refers to the protocol+hostname+port where the Docker server
is hosted. is hosted.
* version (str): The version of the API the client will use * version (str): The version of the API the client will use. Specify `'auto'`
to use the API version provided by the server.
* timeout (int): The HTTP request timeout, in seconds. * timeout (int): The HTTP request timeout, in seconds.
* tls (bool or [TLSConfig](tls.md#TLSConfig)): Equivalent CLI options: `docker --tls ...` * tls (bool or [TLSConfig](tls.md#TLSConfig)): Equivalent CLI options: `docker --tls ...`

View File

@ -30,6 +30,17 @@ FAKE_PATH = '/path'
# for clarity and readability # for clarity and readability
def get_fake_raw_version():
status_code = 200
response = {
"ApiVersion": "1.17",
"GitCommit": "fake-commit",
"GoVersion": "go1.3.3",
"Version": "1.5.0"
}
return status_code, response
def get_fake_version(): def get_fake_version():
status_code = 200 status_code = 200
response = {'GoVersion': '1', 'Version': '1.1.1', response = {'GoVersion': '1', 'Version': '1.1.1',
@ -347,6 +358,8 @@ def get_fake_stats():
# Maps real api url to fake response callback # Maps real api url to fake response callback
prefix = 'http+unix://var/run/docker.sock' prefix = 'http+unix://var/run/docker.sock'
fake_responses = { fake_responses = {
'{0}/version'.format(prefix):
get_fake_raw_version,
'{1}/{0}/version'.format(CURRENT_VERSION, prefix): '{1}/{0}/version'.format(CURRENT_VERSION, prefix):
get_fake_version, get_fake_version,
'{1}/{0}/info'.format(CURRENT_VERSION, prefix): '{1}/{0}/info'.format(CURRENT_VERSION, prefix):

View File

@ -1415,6 +1415,28 @@ class TestLoadJSONConfig(BaseTestCase):
self.assertEqual(cfg.get('Auth'), None) 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()
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(version='1.11')
class TestConnectionTimeout(unittest.TestCase): class TestConnectionTimeout(unittest.TestCase):
def setUp(self): def setUp(self):
self.timeout = 0.5 self.timeout = 0.5

View File

@ -130,7 +130,7 @@ class DockerClientTest(Cleanup, unittest.TestCase):
if not six.PY3: if not six.PY3:
self.assertEqual( self.assertEqual(
str(e), str(e),
'version parameter must be a string. Found float' 'Version parameter must be a string or None. Found float'
) )
######################### #########################
@ -147,6 +147,19 @@ class DockerClientTest(Cleanup, unittest.TestCase):
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS timeout=docker.client.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")
def test_auto_retrieve_server_version(self):
try:
version = self.client.retrieve_server_version()
except Exception as e:
self.fail('Command should not raise exception: {0}'.format(e))
else:
self.assertTrue(isinstance(version, six.string_types))
def test_info(self): def test_info(self):
try: try:
self.client.info() self.client.info()