Skip to content

APIClient.push() with no auth config results in EOF with Docker 28.3.3 #3348

@felixfontein

Description

@felixfontein

I reported this to moby/moby (moby/moby#50614), but since it also affects Docker SDK for Python, I'd like to report it here as well.

Reproducer from moby/moby#50614 (comment):

  1. Run on shell:

    docker run --name registry --publish 5000:5000 --detach registry:2.8.3
    docker pull hello-world:latest
    docker tag hello-world:latest localhost:5000/hello-world:latest
  2. Run in Python, with Docker SDK for Python available:

    import docker
    list(docker.APIClient().push("localhost:5000/hello-world:latest", stream=True, decode=True))

This results in:

Traceback (most recent call last):
  File "/usr/lib/python3.13/http/client.py", line 579, in _get_chunk_left
    chunk_left = self._read_next_chunk_size()
  File "/usr/lib/python3.13/http/client.py", line 546, in _read_next_chunk_size
    return int(line, 16)
ValueError: invalid literal for int() with base 16: b''

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.13/http/client.py", line 597, in _read_chunked
    while (chunk_left := self._get_chunk_left()) is not None:
                         ~~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3.13/http/client.py", line 581, in _get_chunk_left
    raise IncompleteRead(b'')
http.client.IncompleteRead: IncompleteRead(0 bytes read)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 779, in _error_catcher
    yield
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 904, in _raw_read
    data = self._fp_read(amt, read1=read1) if not fp_closed else b""
           ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 887, in _fp_read
    return self._fp.read(amt) if amt is not None else self._fp.read()
           ~~~~~~~~~~~~~^^^^^
  File "/usr/lib/python3.13/http/client.py", line 473, in read
    return self._read_chunked(amt)
           ~~~~~~~~~~~~~~~~~~^^^^^
  File "/usr/lib/python3.13/http/client.py", line 609, in _read_chunked
    raise IncompleteRead(b''.join(value)) from exc
http.client.IncompleteRead: IncompleteRead(0 bytes read)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "<python-input-1>", line 1, in <module>
    list(docker.APIClient().push("localhost:5000/hello-world:latest", stream=True, decode=True))
    ~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/path/to/docker-py/docker/api/client.py", line 360, in _stream_helper
    yield from json_stream(self._stream_helper(response, False))
  File "/path/to/docker-py/docker/utils/json_stream.py", line 60, in split_buffer
    for data in stream_as_text(stream):
                ~~~~~~~~~~~~~~^^^^^^^^
  File "/path/to/docker-py/docker/utils/json_stream.py", line 16, in stream_as_text
    for data in stream:
                ^^^^^^
  File "/path/to/docker-py/docker/api/client.py", line 365, in _stream_helper
    data = reader.read(1)
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 980, in read
    data = self._raw_read(amt)
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 903, in _raw_read
    with self._error_catcher():
         ~~~~~~~~~~~~~~~~~~~^^
  File "/usr/lib/python3.13/contextlib.py", line 162, in __exit__
    self.gen.throw(value)
    ~~~~~~~~~~~~~~^^^^^^^
  File "/usr/lib/python3.13/site-packages/urllib3/response.py", line 806, in _error_catcher
    raise ProtocolError(f"Connection broken: {e!r}", e) from e
urllib3.exceptions.ProtocolError: ('Connection broken: IncompleteRead(0 bytes read)', IncompleteRead(0 bytes read))

A simple workaround is to pass auth_config={}:

list(docker.APIClient().push("localhost:5000/hello-world:latest", stream=True, decode=True, auth_config={}))

This bug can be fixed on Docker SDK for Python's side with the following patch:

diff --git a/docker/api/image.py b/docker/api/image.py
index 85109473..6e28894e 100644
--- a/docker/api/image.py
+++ b/docker/api/image.py
@@ -484,6 +484,8 @@ class ImageApiMixin:
             header = auth.get_config_header(self, registry)
             if header:
                 headers['X-Registry-Auth'] = header
+            else:
+                headers['X-Registry-Auth'] = auth.encode_header({})
         else:
             log.debug('Sending supplied auth config')
             headers['X-Registry-Auth'] = auth.encode_header(auth_config)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions