Skip to content

Commit 7c4ed86

Browse files
committed
Merge branch 'TomasTomecek-autodetect-api-version'
2 parents 9bed480 + 6f18b4a commit 7c4ed86

File tree

6 files changed

+99
-15
lines changed

6 files changed

+99
-15
lines changed

docker/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
__version__ = version
1818
__title__ = 'docker-py'
1919

20-
from .client import Client # flake8: noqa
20+
from .client import Client, AutoVersionClient # flake8: noqa

docker/client.py

Lines changed: 47 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141

4242
class Client(requests.Session):
43-
def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
43+
def __init__(self, base_url=None, version=None,
4444
timeout=DEFAULT_TIMEOUT_SECONDS, tls=False):
4545
super(Client, self).__init__()
4646
base_url = utils.parse_host(base_url)
@@ -50,15 +50,8 @@ def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
5050
raise errors.TLSParameterError(
5151
'If using TLS, the base_url argument must begin with '
5252
'"https://".')
53-
if not isinstance(version, six.string_types):
54-
raise errors.DockerException(
55-
'version parameter must be a string. Found {0}'.format(
56-
type(version).__name__
57-
)
58-
)
5953
self.base_url = base_url
6054
self.timeout = timeout
61-
self._version = version
6255
self._auth_configs = auth.load_config()
6356

6457
# Use SSLAdapter for the ability to specify SSL version
@@ -69,6 +62,34 @@ def __init__(self, base_url=None, version=DEFAULT_DOCKER_API_VERSION,
6962
else:
7063
self.mount('http+unix://', unixconn.UnixAdapter(base_url, timeout))
7164

65+
# version detection needs to be after unix adapter mounting
66+
if version is None:
67+
self._version = DEFAULT_DOCKER_API_VERSION
68+
elif isinstance(version, six.string_types):
69+
if version.lower() == 'auto':
70+
self._version = self._retrieve_server_version()
71+
else:
72+
self._version = version
73+
else:
74+
raise errors.DockerException(
75+
'Version parameter must be a string or None. Found {0}'.format(
76+
type(version).__name__
77+
)
78+
)
79+
80+
def _retrieve_server_version(self):
81+
try:
82+
return self.version(api_version=False)["ApiVersion"]
83+
except KeyError:
84+
raise errors.DockerException(
85+
'Invalid response from docker daemon: key "ApiVersion"'
86+
' is missing.'
87+
)
88+
except Exception as e:
89+
raise errors.DockerException(
90+
'Error while fetching server API version: {0}'.format(e)
91+
)
92+
7293
def _set_request_timeout(self, kwargs):
7394
"""Prepare the kwargs for an HTTP request by inserting the timeout
7495
parameter, if not already present."""
@@ -84,8 +105,11 @@ def _get(self, url, **kwargs):
84105
def _delete(self, url, **kwargs):
85106
return self.delete(url, **self._set_request_timeout(kwargs))
86107

87-
def _url(self, path):
88-
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
108+
def _url(self, path, versioned_api=True):
109+
if versioned_api:
110+
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
111+
else:
112+
return '{0}{1}'.format(self.base_url, path)
89113

90114
def _raise_for_status(self, response, explanation=None):
91115
"""Raises stored :class:`APIError`, if one occurred."""
@@ -914,8 +938,9 @@ def top(self, container):
914938
u = self._url("/containers/{0}/top".format(container))
915939
return self._result(self._get(u), True)
916940

917-
def version(self):
918-
return self._result(self._get(self._url("/version")), True)
941+
def version(self, api_version=True):
942+
url = self._url("/version", versioned_api=api_version)
943+
return self._result(self._get(url), json=True)
919944

920945
def unpause(self, container):
921946
if isinstance(container, dict):
@@ -934,3 +959,13 @@ def wait(self, container, timeout=None):
934959
if 'StatusCode' in json_:
935960
return json_['StatusCode']
936961
return -1
962+
963+
964+
class AutoVersionClient(Client):
965+
def __init__(self, *args, **kwargs):
966+
if 'version' in kwargs and kwargs['version']:
967+
raise errors.DockerException(
968+
'Can not specify version for AutoVersionClient'
969+
)
970+
kwargs['version'] = 'auto'
971+
super(AutoVersionClient, self).__init__(*args, **kwargs)

docs/api.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ c = Client(base_url='unix://var/run/docker.sock')
1212

1313
* base_url (str): Refers to the protocol+hostname+port where the Docker server
1414
is hosted.
15-
* version (str): The version of the API the client will use
15+
* version (str): The version of the API the client will use. Specify `'auto'`
16+
to use the API version provided by the server.
1617
* timeout (int): The HTTP request timeout, in seconds.
1718
* tls (bool or [TLSConfig](tls.md#TLSConfig)): Equivalent CLI options: `docker --tls ...`
1819

tests/fake_api.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,17 @@
3030
# for clarity and readability
3131

3232

33+
def get_fake_raw_version():
34+
status_code = 200
35+
response = {
36+
"ApiVersion": "1.17",
37+
"GitCommit": "fake-commit",
38+
"GoVersion": "go1.3.3",
39+
"Version": "1.5.0"
40+
}
41+
return status_code, response
42+
43+
3344
def get_fake_version():
3445
status_code = 200
3546
response = {'GoVersion': '1', 'Version': '1.1.1',
@@ -347,6 +358,8 @@ def get_fake_stats():
347358
# Maps real api url to fake response callback
348359
prefix = 'http+unix://var/run/docker.sock'
349360
fake_responses = {
361+
'{0}/version'.format(prefix):
362+
get_fake_raw_version,
350363
'{1}/{0}/version'.format(CURRENT_VERSION, prefix):
351364
get_fake_version,
352365
'{1}/{0}/info'.format(CURRENT_VERSION, prefix):

tests/integration_test.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1415,6 +1415,28 @@ def runTest(self):
14151415
self.assertEqual(cfg.get('Auth'), None)
14161416

14171417

1418+
class TestAutoDetectVersion(unittest.TestCase):
1419+
def test_client_init(self):
1420+
client = docker.Client(version='auto')
1421+
client_version = client._version
1422+
api_version = client.version(api_version=False)['ApiVersion']
1423+
self.assertEqual(client_version, api_version)
1424+
api_version_2 = client.version()['ApiVersion']
1425+
self.assertEqual(client_version, api_version_2)
1426+
client.close()
1427+
1428+
def test_auto_client(self):
1429+
client = docker.AutoVersionClient()
1430+
client_version = client._version
1431+
api_version = client.version(api_version=False)['ApiVersion']
1432+
self.assertEqual(client_version, api_version)
1433+
api_version_2 = client.version()['ApiVersion']
1434+
self.assertEqual(client_version, api_version_2)
1435+
client.close()
1436+
with self.assertRaises(docker.errors.DockerException):
1437+
docker.AutoVersionClient(version='1.11')
1438+
1439+
14181440
class TestConnectionTimeout(unittest.TestCase):
14191441
def setUp(self):
14201442
self.timeout = 0.5

tests/test.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ def test_ctor(self):
130130
if not six.PY3:
131131
self.assertEqual(
132132
str(e),
133-
'version parameter must be a string. Found float'
133+
'Version parameter must be a string or None. Found float'
134134
)
135135

136136
#########################
@@ -147,6 +147,19 @@ def test_version(self):
147147
timeout=docker.client.DEFAULT_TIMEOUT_SECONDS
148148
)
149149

150+
def test_retrieve_server_version(self):
151+
client = docker.Client(version="auto")
152+
self.assertTrue(isinstance(client._version, six.string_types))
153+
self.assertFalse(client._version == "auto")
154+
155+
def test_auto_retrieve_server_version(self):
156+
try:
157+
version = self.client.retrieve_server_version()
158+
except Exception as e:
159+
self.fail('Command should not raise exception: {0}'.format(e))
160+
else:
161+
self.assertTrue(isinstance(version, six.string_types))
162+
150163
def test_info(self):
151164
try:
152165
self.client.info()

0 commit comments

Comments
 (0)