Skip to content

Commit 3e0496c

Browse files
committed
detect server version when version="auto"
heavily inspired by PR#281 Signed-off-by: Tomas Tomecek <[email protected]>
1 parent 9bed480 commit 3e0496c

File tree

4 files changed

+66
-14
lines changed

4 files changed

+66
-14
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: 38 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,29 @@ 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+
response = self.version(api_version=False)
82+
try:
83+
return response["ApiVersion"]
84+
except KeyError:
85+
raise ValueError("Invalid response from docker daemon: "
86+
"key \"ApiVersion\" is missing.")
87+
7288
def _set_request_timeout(self, kwargs):
7389
"""Prepare the kwargs for an HTTP request by inserting the timeout
7490
parameter, if not already present."""
@@ -84,8 +100,11 @@ def _get(self, url, **kwargs):
84100
def _delete(self, url, **kwargs):
85101
return self.delete(url, **self._set_request_timeout(kwargs))
86102

87-
def _url(self, path):
88-
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
103+
def _url(self, path, versioned_api=True):
104+
if versioned_api:
105+
return '{0}/v{1}{2}'.format(self.base_url, self._version, path)
106+
else:
107+
return '{0}{1}'.format(self.base_url, path)
89108

90109
def _raise_for_status(self, response, explanation=None):
91110
"""Raises stored :class:`APIError`, if one occurred."""
@@ -914,8 +933,9 @@ def top(self, container):
914933
u = self._url("/containers/{0}/top".format(container))
915934
return self._result(self._get(u), True)
916935

917-
def version(self):
918-
return self._result(self._get(self._url("/version")), True)
936+
def version(self, api_version=True):
937+
url = self._url("/version", versioned_api=api_version)
938+
return self._result(self._get(url), json=True)
919939

920940
def unpause(self, container):
921941
if isinstance(container, dict):
@@ -934,3 +954,9 @@ def wait(self, container, timeout=None):
934954
if 'StatusCode' in json_:
935955
return json_['StatusCode']
936956
return -1
957+
958+
959+
class AutoVersionClient(Client):
960+
def __init__(self, *args, **kwargs):
961+
kwargs['version'] = 'auto'
962+
super(AutoVersionClient, self).__init__(*args, **kwargs)

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/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)