Skip to content

Commit d9df2a8

Browse files
committed
Fix handling of non-multiplexed (TTY) streams over upgraded sockets
Signed-off-by: Joffrey F <[email protected]>
1 parent a6065df commit d9df2a8

File tree

6 files changed

+42
-11
lines changed

6 files changed

+42
-11
lines changed

docker/api/client.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
from ..tls import TLSConfig
3333
from ..transport import SSLAdapter, UnixAdapter
3434
from ..utils import utils, check_resource, update_headers
35-
from ..utils.socket import frames_iter
35+
from ..utils.socket import frames_iter, socket_raw_iter
3636
from ..utils.json_stream import json_stream
3737
try:
3838
from ..transport import NpipeAdapter
@@ -362,13 +362,19 @@ def _stream_raw_result(self, response):
362362
for out in response.iter_content(chunk_size=1, decode_unicode=True):
363363
yield out
364364

365-
def _read_from_socket(self, response, stream):
365+
def _read_from_socket(self, response, stream, tty=False):
366366
socket = self._get_raw_response_socket(response)
367367

368+
gen = None
369+
if tty is False:
370+
gen = frames_iter(socket)
371+
else:
372+
gen = socket_raw_iter(socket)
373+
368374
if stream:
369-
return frames_iter(socket)
375+
return gen
370376
else:
371-
return six.binary_type().join(frames_iter(socket))
377+
return six.binary_type().join(gen)
372378

373379
def _disable_socket_timeout(self, socket):
374380
""" Depending on the combination of python version and whether we're
@@ -398,9 +404,13 @@ def _disable_socket_timeout(self, socket):
398404

399405
s.settimeout(None)
400406

401-
def _get_result(self, container, stream, res):
407+
@check_resource('container')
408+
def _check_is_tty(self, container):
402409
cont = self.inspect_container(container)
403-
return self._get_result_tty(stream, res, cont['Config']['Tty'])
410+
return cont['Config']['Tty']
411+
412+
def _get_result(self, container, stream, res):
413+
return self._get_result_tty(stream, res, self._check_is_tty(container))
404414

405415
def _get_result_tty(self, stream, res, is_tty):
406416
# Stream multi-plexing was only introduced in API v1.6. Anything

docker/api/container.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,9 @@ def attach(self, container, stdout=True, stderr=True,
5252
u = self._url("/containers/{0}/attach", container)
5353
response = self._post(u, headers=headers, params=params, stream=stream)
5454

55-
return self._read_from_socket(response, stream)
55+
return self._read_from_socket(
56+
response, stream, self._check_is_tty(container)
57+
)
5658

5759
@utils.check_resource('container')
5860
def attach_socket(self, container, params=None, ws=False):

docker/api/exec_api.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,4 @@ def exec_start(self, exec_id, detach=False, tty=False, stream=False,
153153
return self._result(res)
154154
if socket:
155155
return self._get_raw_response_socket(res)
156-
return self._read_from_socket(res, stream)
156+
return self._read_from_socket(res, stream, tty)

docker/utils/socket.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,5 +75,24 @@ def frames_iter(socket):
7575
break
7676
while n > 0:
7777
result = read(socket, n)
78-
n -= len(result)
78+
if result is None:
79+
continue
80+
data_length = len(result)
81+
if data_length == 0:
82+
# We have reached EOF
83+
return
84+
n -= data_length
7985
yield result
86+
87+
88+
def socket_raw_iter(socket):
89+
"""
90+
Returns a generator of data read from the socket.
91+
This is used for non-multiplexed streams.
92+
"""
93+
while True:
94+
result = read(socket)
95+
if len(result) == 0:
96+
# We have reached EOF
97+
return
98+
yield result

tests/integration/api_build_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,8 +244,8 @@ def test_build_with_network_mode(self):
244244
with pytest.raises(errors.NotFound):
245245
self.client.inspect_image('dockerpytest_nonebuild')
246246

247+
@requires_experimental(until=None)
247248
@requires_api_version('1.25')
248-
@requires_experimental
249249
def test_build_squash(self):
250250
script = io.BytesIO('\n'.join([
251251
'FROM busybox',

tests/unit/api_test.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def fake_delete(self, url, *args, **kwargs):
8383
return fake_request('DELETE', url, *args, **kwargs)
8484

8585

86-
def fake_read_from_socket(self, response, stream):
86+
def fake_read_from_socket(self, response, stream, tty=False):
8787
return six.binary_type()
8888

8989

0 commit comments

Comments
 (0)