From a74d546864b64ba03dce1e3407d3b0cd5badee9f Mon Sep 17 00:00:00 2001 From: adw1n Date: Mon, 3 Sep 2018 05:54:12 +0200 Subject: [PATCH] Fix pulling images with `stream=True` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pulling an image with option `stream=True` like this: ``` client.api.pull('docker.io/user/repo_name', tag='latest', stream=True) ``` without consuming the generator oftentimes results in premature drop of the connection. Docker daemon tries to send progress of pulling the image to the client, but it encounters an error (broken pipe) and therefore cancells the pull action: ``` Thread 1 "dockerd-dev" received signal SIGPIPE, Broken pipe. ERRO[2018-09-03T05:12:35.746497638+02:00] Not continuing with pull after error: context canceled ``` As described in issue #2116, even though client receives response with status code 200, image is not pulled. Closes #2116 Signed-off-by: Przemysław Adamek --- docker/api/image.py | 3 ++- docker/models/images.py | 1 + tests/unit/models_containers_test.py | 3 ++- tests/unit/models_images_test.py | 6 ++++-- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docker/api/image.py b/docker/api/image.py index a9f801e9..d3fed5c0 100644 --- a/docker/api/image.py +++ b/docker/api/image.py @@ -334,7 +334,8 @@ class ImageApiMixin(object): Args: repository (str): The repository to pull tag (str): The tag to pull - stream (bool): Stream the output as a generator + stream (bool): Stream the output as a generator. Make sure to + consume the generator, otherwise pull might get cancelled. auth_config (dict): Override the credentials that :py:meth:`~docker.api.daemon.DaemonApiMixin.login` has set for this request. ``auth_config`` should contain the ``username`` diff --git a/docker/models/images.py b/docker/models/images.py index 4578c0bd..f8b842a9 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -425,6 +425,7 @@ class ImageCollection(Collection): if not tag: repository, tag = parse_repository_tag(repository) + kwargs['stream'] = False self.client.api.pull(repository, tag=tag, **kwargs) if tag: return self.get('{0}{2}{1}'.format( diff --git a/tests/unit/models_containers_test.py b/tests/unit/models_containers_test.py index 22dd2410..957035af 100644 --- a/tests/unit/models_containers_test.py +++ b/tests/unit/models_containers_test.py @@ -232,7 +232,8 @@ class ContainerCollectionTest(unittest.TestCase): container = client.containers.run('alpine', 'sleep 300', detach=True) assert container.id == FAKE_CONTAINER_ID - client.api.pull.assert_called_with('alpine', platform=None, tag=None) + client.api.pull.assert_called_with('alpine', platform=None, tag=None, + stream=False) def test_run_with_error(self): client = make_fake_client() diff --git a/tests/unit/models_images_test.py b/tests/unit/models_images_test.py index 67832795..ef81a159 100644 --- a/tests/unit/models_images_test.py +++ b/tests/unit/models_images_test.py @@ -43,7 +43,8 @@ class ImageCollectionTest(unittest.TestCase): def test_pull(self): client = make_fake_client() image = client.images.pull('test_image:latest') - client.api.pull.assert_called_with('test_image', tag='latest') + client.api.pull.assert_called_with('test_image', tag='latest', + stream=False) client.api.inspect_image.assert_called_with('test_image:latest') assert isinstance(image, Image) assert image.id == FAKE_IMAGE_ID @@ -51,7 +52,8 @@ class ImageCollectionTest(unittest.TestCase): def test_pull_multiple(self): client = make_fake_client() images = client.images.pull('test_image') - client.api.pull.assert_called_with('test_image', tag=None) + client.api.pull.assert_called_with('test_image', tag=None, + stream=False) client.api.images.assert_called_with( all=False, name='test_image', filters=None )