Description
Container.put_archive(path, data)
raises docker.errors.NotFound
if path
argument not found inside container. But if data
argument is a sufficiently big file (in my tests 780K is enough), it is fails with requests.exceptions.ConnectionError
.
Environment details:
$ pip freeze | grep docker && python --version && docker version
docker==2.6.1
docker-pycreds==0.2.1
Python 3.6.2
Client:
Version: 17.07.0-ce
API version: 1.31
Go version: go1.9
Git commit: 8784753
Built: unknown-buildtime
OS/Arch: darwin/amd64
Server:
Version: 17.09.0-ce
API version: 1.32 (minimum version 1.12)
Go version: go1.8.3
Git commit: afdb6d4
Built: Tue Sep 26 22:45:38 2017
OS/Arch: linux/amd64
Experimental: false
OS details
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.10.5
BuildVersion: 14F2511
Steps to reproduce
Run container:
$ docker run -d --name cp_test ubuntu:16.04 /bin/sh -c "while true; do echo hello world; sleep 1; done"
$ docker ps
CONTAINER ID NAMES IMAGE PORTS STATUS
574e03b4aca8 cp_test ubuntu:16.04 Up About an hour
Consider dkr_cp_bug.py script that puts file into cp_test container:
# -*- coding: utf-8 -*-
import os
import tarfile
import docker
FNAME = "testdata.txt"
TAR_NAME = "testdata.tar"
CONTAINER_NAME = "cp_test"
LINES_QTY = 10 ** 4
# Destination to put file inside container
# (non-existent)
DST_PATH = "/foo"
def compose_data(qty=LINES_QTY):
line = "-" * 79
line += "\n"
return line * qty
def write_data(FNAME, data):
with open(FNAME, "w") as fd:
fd.write(data)
def make_tar(TAR_NAME, path):
with tarfile.open(TAR_NAME, 'w') as tar:
tar.add(path)
def main():
try:
data = compose_data()
write_data(FNAME, data)
make_tar(TAR_NAME, "./" + FNAME)
client = docker.from_env(version="auto")
container = client.containers.get(CONTAINER_NAME)
with open(TAR_NAME, 'rb') as fd:
ok = container.put_archive(path=DST_PATH, data=fd)
# Cleanup
finally:
os.remove(FNAME)
os.remove(TAR_NAME)
if ok:
print("Success")
else:
print("Fail")
if __name__ == '__main__':
main()
Traceback (same happens under Python 2.7.14 and 3.5.2):
$ python dkr_cp_bug.py
Traceback (most recent call last):
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/connectionpool.py", line 601, in urlopen
chunked=chunked)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/connectionpool.py", line 357, in _make_request
conn.request(method, url, **httplib_request_kw)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1239, in request
self._send_request(method, url, body, headers, encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1285, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1234, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1065, in _send_output
self.send(chunk)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 986, in send
self.sock.sendall(data)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 965, in sendall
v = self.send(data[count:])
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 935, in send
return self._sslobj.write(data)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 636, in write
return self._sslobj.write(data)
BrokenPipeError: [Errno 32] Broken pipe
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
timeout=timeout
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/connectionpool.py", line 639, in urlopen
_stacktrace=sys.exc_info()[2])
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/util/retry.py", line 357, in increment
raise six.reraise(type(error), error, _stacktrace)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/packages/six.py", line 685, in reraise
raise value.with_traceback(tb)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/connectionpool.py", line 601, in urlopen
chunked=chunked)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/urllib3/connectionpool.py", line 357, in _make_request
conn.request(method, url, **httplib_request_kw)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1239, in request
self._send_request(method, url, body, headers, encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1285, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1234, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 1065, in _send_output
self.send(chunk)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/http/client.py", line 986, in send
self.sock.sendall(data)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 965, in sendall
v = self.send(data[count:])
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 935, in send
return self._sslobj.write(data)
File "/usr/local/var/pyenv/versions/3.6.2/lib/python3.6/ssl.py", line 636, in write
return self._sslobj.write(data)
urllib3.exceptions.ProtocolError: ('Connection aborted.', BrokenPipeError(32, 'Broken pipe'))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "dkr_cp_bug.py", line 59, in <module>
main()
File "dkr_cp_bug.py", line 45, in main
ok = container.put_archive(path=DST_PATH, data=fd)
File "/Users/me/git_cloned/docker-py/docker/models/containers.py", line 267, in put_archive
return self.client.api.put_archive(self.id, path, data)
File "/Users/me/git_cloned/docker-py/docker/utils/decorators.py", line 19, in wrapped
return f(self, resource_id, *args, **kwargs)
File "/Users/me/git_cloned/docker-py/docker/utils/decorators.py", line 34, in wrapper
return f(self, *args, **kwargs)
File "/Users/me/git_cloned/docker-py/docker/api/container.py", line 941, in put_archive
res = self._put(url, params=params, data=data)
File "/Users/me/git_cloned/docker-py/docker/utils/decorators.py", line 46, in inner
return f(self, *args, **kwargs)
File "/Users/me/git_cloned/docker-py/docker/api/client.py", line 195, in _put
return self.put(url, **self._set_request_timeout(kwargs))
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/sessions.py", line 566, in put
return self.request('PUT', url, data=data, **kwargs)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/sessions.py", line 508, in request
resp = self.send(prep, **send_kwargs)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/sessions.py", line 618, in send
r = adapter.send(request, **kwargs)
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/adapters.py", line 490, in send
raise ConnectionError(err, request=request)
requests.exceptions.ConnectionError: ('Connection aborted.', BrokenPipeError(32, 'Broken pipe'))
However, if file to copy is small, e.g. LINES_QTY = 10
, it raises docker.errors.NotFound
as expected:
# LINES_QTY = 10
$ python dkr_cp_bug.py
Traceback (most recent call last):
File "/Users/me/git_cloned/docker-py/docker/api/client.py", line 222, in _raise_for_status
response.raise_for_status()
File "/usr/local/var/pyenv/versions/dkr-py36/lib/python3.6/site-packages/requests/models.py", line 935, in raise_for_status
raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 404 Client Error: Not Found for url: https://192.168.99.100:2376/v1.32/containers/574e03b4aca807939fed04298d877b927fd8b604e476e703cede5a73c4b84b11/archive?path=%2Ffoo
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "dkr_cp_bug.py", line 60, in <module>
main()
File "dkr_cp_bug.py", line 46, in main
ok = container.put_archive(path=DST_PATH, data=fd)
File "/Users/me/git_cloned/docker-py/docker/models/containers.py", line 267, in put_archive
return self.client.api.put_archive(self.id, path, data)
File "/Users/me/git_cloned/docker-py/docker/utils/decorators.py", line 19, in wrapped
return f(self, resource_id, *args, **kwargs)
File "/Users/me/git_cloned/docker-py/docker/utils/decorators.py", line 34, in wrapper
return f(self, *args, **kwargs)
File "/Users/me/git_cloned/docker-py/docker/api/container.py", line 942, in put_archive
self._raise_for_status(res)
File "/Users/me/git_cloned/docker-py/docker/api/client.py", line 224, in _raise_for_status
raise create_api_error_from_http_exception(e)
File "/Users/me/git_cloned/docker-py/docker/errors.py", line 31, in create_api_error_from_http_exception
raise cls(e, response=response, explanation=explanation)
docker.errors.NotFound: 404 Client Error: Not Found ("Could not find the file /foo in container 574e03b4aca807939fed04298d877b927fd8b604e476e703cede5a73c4b84b11")
Some thoughts
Digging into this I've found that it's actually a requests known issue, raised several times already. From this discussion it seems that it will not get fixed any time soon.
Meantime as a workaround it's possible to catch requests.exceptions.ConnnectionError
in res = self._put(url, params=params, data=data)
call here:
@utils.check_resource('container')
@utils.minimum_version('1.20')
def put_archive(self, container, path, data):
"""
Insert a file or folder in an existing container using a tar archive as
source.
Args:
container (str): The container where the file(s) will be extracted
path (str): Path inside the container where the file(s) will be
extracted. Must exist.
data (bytes): tar data to be extracted
Returns:
(bool): True if the call succeeds.
Raises:
:py:class:`docker.errors.APIError`
If the server returns an error.
"""
params = {'path': path}
url = self._url('/containers/{0}/archive', container)
res = self._put(url, params=params, data=data)
self._raise_for_status(res)
return res.status_code == 200
and try to compose docker.errors.NotFound
similarly as it is done in docker.errors.create_api_error_from_http_exception
.
I can try to compose a PR for this.