Kaydet (Commit) c485bd8d authored tarafından Joffrey F's avatar Joffrey F

Merge pull request #752 from docker/697-stricter-url-construction

Stricter url construction
...@@ -41,7 +41,7 @@ class Client(clientbase.ClientBase): ...@@ -41,7 +41,7 @@ class Client(clientbase.ClientBase):
'stderr': stderr and 1 or 0, 'stderr': stderr and 1 or 0,
'stream': stream and 1 or 0, 'stream': stream and 1 or 0,
} }
u = self._url("/containers/{0}/attach".format(container)) u = self._url("/containers/{0}/attach", container)
response = self._post(u, params=params, stream=stream) response = self._post(u, params=params, stream=stream)
return self._get_result(container, stream, response) return self._get_result(container, stream, response)
...@@ -58,7 +58,7 @@ class Client(clientbase.ClientBase): ...@@ -58,7 +58,7 @@ class Client(clientbase.ClientBase):
if ws: if ws:
return self._attach_websocket(container, params) return self._attach_websocket(container, params)
u = self._url("/containers/{0}/attach".format(container)) u = self._url("/containers/{0}/attach", container)
return self._get_raw_response_socket(self.post( return self._get_raw_response_socket(self.post(
u, None, params=self._attach_params(params), stream=True)) u, None, params=self._attach_params(params), stream=True))
...@@ -275,8 +275,9 @@ class Client(clientbase.ClientBase): ...@@ -275,8 +275,9 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def diff(self, container): def diff(self, container):
return self._result(self._get(self._url("/containers/{0}/changes". return self._result(
format(container))), True) self._get(self._url("/containers/{0}/changes", container)), True
)
def events(self, since=None, until=None, filters=None, decode=None): def events(self, since=None, until=None, filters=None, decode=None):
if isinstance(since, datetime): if isinstance(since, datetime):
...@@ -326,7 +327,7 @@ class Client(clientbase.ClientBase): ...@@ -326,7 +327,7 @@ class Client(clientbase.ClientBase):
'Cmd': cmd 'Cmd': cmd
} }
url = self._url('/containers/{0}/exec'.format(container)) url = self._url('/containers/{0}/exec', container)
res = self._post_json(url, data=data) res = self._post_json(url, data=data)
return self._result(res, True) return self._result(res, True)
...@@ -337,7 +338,7 @@ class Client(clientbase.ClientBase): ...@@ -337,7 +338,7 @@ class Client(clientbase.ClientBase):
) )
if isinstance(exec_id, dict): if isinstance(exec_id, dict):
exec_id = exec_id.get('Id') exec_id = exec_id.get('Id')
res = self._get(self._url("/exec/{0}/json".format(exec_id))) res = self._get(self._url("/exec/{0}/json", exec_id))
return self._result(res, True) return self._result(res, True)
def exec_resize(self, exec_id, height=None, width=None): def exec_resize(self, exec_id, height=None, width=None):
...@@ -347,7 +348,7 @@ class Client(clientbase.ClientBase): ...@@ -347,7 +348,7 @@ class Client(clientbase.ClientBase):
exec_id = exec_id.get('Id') exec_id = exec_id.get('Id')
params = {'h': height, 'w': width} params = {'h': height, 'w': width}
url = self._url("/exec/{0}/resize".format(exec_id)) url = self._url("/exec/{0}/resize", exec_id)
res = self._post(url, params=params) res = self._post(url, params=params)
self._raise_for_status(res) self._raise_for_status(res)
...@@ -362,27 +363,28 @@ class Client(clientbase.ClientBase): ...@@ -362,27 +363,28 @@ class Client(clientbase.ClientBase):
'Detach': detach 'Detach': detach
} }
res = self._post_json(self._url('/exec/{0}/start'.format(exec_id)), res = self._post_json(
data=data, stream=stream) self._url('/exec/{0}/start', exec_id), data=data, stream=stream
)
return self._get_result_tty(stream, res, tty) return self._get_result_tty(stream, res, tty)
@check_resource @check_resource
def export(self, container): def export(self, container):
res = self._get(self._url("/containers/{0}/export".format(container)), res = self._get(
stream=True) self._url("/containers/{0}/export", container), stream=True
)
self._raise_for_status(res) self._raise_for_status(res)
return res.raw return res.raw
@check_resource @check_resource
def get_image(self, image): def get_image(self, image):
res = self._get(self._url("/images/{0}/get".format(image)), res = self._get(self._url("/images/{0}/get", image), stream=True)
stream=True)
self._raise_for_status(res) self._raise_for_status(res)
return res.raw return res.raw
@check_resource @check_resource
def history(self, image): def history(self, image):
res = self._get(self._url("/images/{0}/history".format(image))) res = self._get(self._url("/images/{0}/history", image))
return self._result(res, True) return self._result(res, True)
def images(self, name=None, quiet=False, all=False, viz=False, def images(self, name=None, quiet=False, all=False, viz=False,
...@@ -496,7 +498,7 @@ class Client(clientbase.ClientBase): ...@@ -496,7 +498,7 @@ class Client(clientbase.ClientBase):
raise errors.DeprecatedMethod( raise errors.DeprecatedMethod(
'insert is not available for API version >=1.12' 'insert is not available for API version >=1.12'
) )
api_url = self._url("/images/{0}/insert".format(image)) api_url = self._url("/images/{0}/insert", image)
params = { params = {
'url': url, 'url': url,
'path': path 'path': path
...@@ -506,21 +508,18 @@ class Client(clientbase.ClientBase): ...@@ -506,21 +508,18 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def inspect_container(self, container): def inspect_container(self, container):
return self._result( return self._result(
self._get(self._url("/containers/{0}/json".format(container))), self._get(self._url("/containers/{0}/json", container)), True
True) )
@check_resource @check_resource
def inspect_image(self, image): def inspect_image(self, image):
return self._result( return self._result(
self._get( self._get(self._url("/images/{0}/json", image)), True
self._url("/images/{0}/json".format(image.replace('/', '%2F')))
),
True
) )
@check_resource @check_resource
def kill(self, container, signal=None): def kill(self, container, signal=None):
url = self._url("/containers/{0}/kill".format(container)) url = self._url("/containers/{0}/kill", container)
params = {} params = {}
if signal is not None: if signal is not None:
params['signal'] = signal params['signal'] = signal
...@@ -583,7 +582,7 @@ class Client(clientbase.ClientBase): ...@@ -583,7 +582,7 @@ class Client(clientbase.ClientBase):
if tail != 'all' and (not isinstance(tail, int) or tail <= 0): if tail != 'all' and (not isinstance(tail, int) or tail <= 0):
tail = 'all' tail = 'all'
params['tail'] = tail params['tail'] = tail
url = self._url("/containers/{0}/logs".format(container)) url = self._url("/containers/{0}/logs", container)
res = self._get(url, params=params, stream=stream) res = self._get(url, params=params, stream=stream)
return self._get_result(container, stream, res) return self._get_result(container, stream, res)
return self.attach( return self.attach(
...@@ -596,7 +595,7 @@ class Client(clientbase.ClientBase): ...@@ -596,7 +595,7 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def pause(self, container): def pause(self, container):
url = self._url('/containers/{0}/pause'.format(container)) url = self._url('/containers/{0}/pause', container)
res = self._post(url) res = self._post(url)
self._raise_for_status(res) self._raise_for_status(res)
...@@ -605,7 +604,7 @@ class Client(clientbase.ClientBase): ...@@ -605,7 +604,7 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def port(self, container, private_port): def port(self, container, private_port):
res = self._get(self._url("/containers/{0}/json".format(container))) res = self._get(self._url("/containers/{0}/json", container))
self._raise_for_status(res) self._raise_for_status(res)
json_ = res.json() json_ = res.json()
s_port = str(private_port) s_port = str(private_port)
...@@ -692,7 +691,7 @@ class Client(clientbase.ClientBase): ...@@ -692,7 +691,7 @@ class Client(clientbase.ClientBase):
if not tag: if not tag:
repository, tag = utils.parse_repository_tag(repository) repository, tag = utils.parse_repository_tag(repository)
registry, repo_name = auth.resolve_repository_name(repository) registry, repo_name = auth.resolve_repository_name(repository)
u = self._url("/images/{0}/push".format(repository)) u = self._url("/images/{0}/push", repository)
params = { params = {
'tag': tag 'tag': tag
} }
...@@ -725,14 +724,15 @@ class Client(clientbase.ClientBase): ...@@ -725,14 +724,15 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def remove_container(self, container, v=False, link=False, force=False): def remove_container(self, container, v=False, link=False, force=False):
params = {'v': v, 'link': link, 'force': force} params = {'v': v, 'link': link, 'force': force}
res = self._delete(self._url("/containers/" + container), res = self._delete(
params=params) self._url("/containers/{0}", container), params=params
)
self._raise_for_status(res) self._raise_for_status(res)
@check_resource @check_resource
def remove_image(self, image, force=False, noprune=False): def remove_image(self, image, force=False, noprune=False):
params = {'force': force, 'noprune': noprune} params = {'force': force, 'noprune': noprune}
res = self._delete(self._url("/images/" + image), params=params) res = self._delete(self._url("/images/{0}", image), params=params)
self._raise_for_status(res) self._raise_for_status(res)
@check_resource @check_resource
...@@ -741,7 +741,7 @@ class Client(clientbase.ClientBase): ...@@ -741,7 +741,7 @@ class Client(clientbase.ClientBase):
raise errors.InvalidVersion( raise errors.InvalidVersion(
'rename was only introduced in API version 1.17' 'rename was only introduced in API version 1.17'
) )
url = self._url("/containers/{0}/rename".format(container)) url = self._url("/containers/{0}/rename", container)
params = {'name': name} params = {'name': name}
res = self._post(url, params=params) res = self._post(url, params=params)
self._raise_for_status(res) self._raise_for_status(res)
...@@ -749,21 +749,22 @@ class Client(clientbase.ClientBase): ...@@ -749,21 +749,22 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def resize(self, container, height, width): def resize(self, container, height, width):
params = {'h': height, 'w': width} params = {'h': height, 'w': width}
url = self._url("/containers/{0}/resize".format(container)) url = self._url("/containers/{0}/resize", container)
res = self._post(url, params=params) res = self._post(url, params=params)
self._raise_for_status(res) self._raise_for_status(res)
@check_resource @check_resource
def restart(self, container, timeout=10): def restart(self, container, timeout=10):
params = {'t': timeout} params = {'t': timeout}
url = self._url("/containers/{0}/restart".format(container)) url = self._url("/containers/{0}/restart", container)
res = self._post(url, params=params) res = self._post(url, params=params)
self._raise_for_status(res) self._raise_for_status(res)
def search(self, term): def search(self, term):
return self._result(self._get(self._url("/images/search"), return self._result(
params={'term': term}), self._get(self._url("/images/search"), params={'term': term}),
True) True
)
@check_resource @check_resource
def start(self, container, binds=None, port_bindings=None, lxc_conf=None, def start(self, container, binds=None, port_bindings=None, lxc_conf=None,
...@@ -829,7 +830,7 @@ class Client(clientbase.ClientBase): ...@@ -829,7 +830,7 @@ class Client(clientbase.ClientBase):
) )
start_config = self.create_host_config(**start_config_kwargs) start_config = self.create_host_config(**start_config_kwargs)
url = self._url("/containers/{0}/start".format(container)) url = self._url("/containers/{0}/start", container)
res = self._post_json(url, data=start_config) res = self._post_json(url, data=start_config)
self._raise_for_status(res) self._raise_for_status(res)
...@@ -839,13 +840,13 @@ class Client(clientbase.ClientBase): ...@@ -839,13 +840,13 @@ class Client(clientbase.ClientBase):
raise errors.InvalidVersion( raise errors.InvalidVersion(
'Stats retrieval is not supported in API < 1.17!') 'Stats retrieval is not supported in API < 1.17!')
url = self._url("/containers/{0}/stats".format(container)) url = self._url("/containers/{0}/stats", container)
return self._stream_helper(self._get(url, stream=True), decode=decode) return self._stream_helper(self._get(url, stream=True), decode=decode)
@check_resource @check_resource
def stop(self, container, timeout=10): def stop(self, container, timeout=10):
params = {'t': timeout} params = {'t': timeout}
url = self._url("/containers/{0}/stop".format(container)) url = self._url("/containers/{0}/stop", container)
res = self._post(url, params=params, res = self._post(url, params=params,
timeout=(timeout + (self.timeout or 0))) timeout=(timeout + (self.timeout or 0)))
...@@ -858,14 +859,14 @@ class Client(clientbase.ClientBase): ...@@ -858,14 +859,14 @@ class Client(clientbase.ClientBase):
'repo': repository, 'repo': repository,
'force': 1 if force else 0 'force': 1 if force else 0
} }
url = self._url("/images/{0}/tag".format(image)) url = self._url("/images/{0}/tag", image)
res = self._post(url, params=params) res = self._post(url, params=params)
self._raise_for_status(res) self._raise_for_status(res)
return res.status_code == 201 return res.status_code == 201
@check_resource @check_resource
def top(self, container): def top(self, container):
u = self._url("/containers/{0}/top".format(container)) u = self._url("/containers/{0}/top", container)
return self._result(self._get(u), True) return self._result(self._get(u), True)
def version(self, api_version=True): def version(self, api_version=True):
...@@ -874,13 +875,13 @@ class Client(clientbase.ClientBase): ...@@ -874,13 +875,13 @@ class Client(clientbase.ClientBase):
@check_resource @check_resource
def unpause(self, container): def unpause(self, container):
url = self._url('/containers/{0}/unpause'.format(container)) url = self._url('/containers/{0}/unpause', container)
res = self._post(url) res = self._post(url)
self._raise_for_status(res) self._raise_for_status(res)
@check_resource @check_resource
def wait(self, container, timeout=None): def wait(self, container, timeout=None):
url = self._url("/containers/{0}/wait".format(container)) url = self._url("/containers/{0}/wait", container)
res = self._post(url, timeout=timeout) res = self._post(url, timeout=timeout)
self._raise_for_status(res) self._raise_for_status(res)
json_ = res.json() json_ = res.json()
......
...@@ -88,11 +88,21 @@ class ClientBase(requests.Session): ...@@ -88,11 +88,21 @@ class ClientBase(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, versioned_api=True): def _url(self, pathfmt, resource_id=None, versioned_api=True):
if resource_id and not isinstance(resource_id, six.string_types):
raise ValueError(
'Expected a resource ID string but found {0} ({1}) '
'instead'.format(resource_id, type(resource_id))
)
elif resource_id:
resource_id = six.moves.urllib.parse.quote_plus(resource_id)
if versioned_api: if versioned_api:
return '{0}/v{1}{2}'.format(self.base_url, self._version, path) return '{0}/v{1}{2}'.format(
self.base_url, self._version, pathfmt.format(resource_id)
)
else: else:
return '{0}{1}'.format(self.base_url, path) return '{0}{1}'.format(self.base_url, pathfmt.format(resource_id))
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."""
...@@ -136,7 +146,7 @@ class ClientBase(requests.Session): ...@@ -136,7 +146,7 @@ class ClientBase(requests.Session):
@check_resource @check_resource
def _attach_websocket(self, container, params=None): def _attach_websocket(self, container, params=None):
url = self._url("/containers/{0}/attach/ws".format(container)) url = self._url("/containers/{0}/attach/ws", container)
req = requests.Request("POST", url, params=self._attach_params(params)) req = requests.Request("POST", url, params=self._attach_params(params))
full_url = req.prepare().url full_url = req.prepare().url
full_url = full_url.replace("http://", "ws://", 1) full_url = full_url.replace("http://", "ws://", 1)
......
...@@ -144,6 +144,28 @@ class DockerClientTest(Cleanup, base.BaseTestCase): ...@@ -144,6 +144,28 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
'Version parameter must be a string or None. Found float' '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', '/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'))
url = self.client._url('/simple', None)
self.assertEqual(url, '{0}{1}'.format(url_prefix, 'simple'))
######################### #########################
# INFORMATION TESTS # # INFORMATION TESTS #
######################### #########################
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment