mirror of https://github.com/docker/docker-py.git
Fix handling output from tty-enabled containers.
Treat output from TTY-enabled containers as raw streams, rather than as multiplexed streams. The docker API docs specify that tty-enabled containers don't multiplex. Also update tests to pass with these changes, and changed the code used to read raw streams to not read line-by-line, and to not skip empty lines. Addresses issue #630 Signed-off-by: Dan O'Reilly <oreilldf@gmail.com>
This commit is contained in:
parent
7b18543999
commit
70b921f8a3
|
|
@ -40,28 +40,7 @@ class Client(clientbase.ClientBase):
|
|||
u = self._url("/containers/{0}/attach".format(container))
|
||||
response = self._post(u, params=params, stream=stream)
|
||||
|
||||
# Stream multi-plexing was only introduced in API v1.6. Anything before
|
||||
# that needs old-style streaming.
|
||||
if utils.compare_version('1.6', self._version) < 0:
|
||||
def stream_result():
|
||||
self._raise_for_status(response)
|
||||
for line in response.iter_lines(chunk_size=1,
|
||||
decode_unicode=True):
|
||||
# filter out keep-alive new lines
|
||||
if line:
|
||||
yield line
|
||||
|
||||
return stream_result() if stream else \
|
||||
self._result(response, binary=True)
|
||||
|
||||
sep = bytes() if six.PY3 else str()
|
||||
|
||||
if stream:
|
||||
return self._multiplexed_response_stream_helper(response)
|
||||
else:
|
||||
return sep.join(
|
||||
[x for x in self._multiplexed_buffer_helper(response)]
|
||||
)
|
||||
return self._get_result(container, stream, response)
|
||||
|
||||
@check_resource
|
||||
def attach_socket(self, container, params=None, ws=False):
|
||||
|
|
@ -363,17 +342,7 @@ class Client(clientbase.ClientBase):
|
|||
|
||||
res = self._post_json(self._url('/exec/{0}/start'.format(exec_id)),
|
||||
data=data, stream=stream)
|
||||
self._raise_for_status(res)
|
||||
if stream:
|
||||
return self._multiplexed_response_stream_helper(res)
|
||||
elif six.PY3:
|
||||
return bytes().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
else:
|
||||
return str().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
return self._get_result_tty(stream, res, tty)
|
||||
|
||||
@check_resource
|
||||
def export(self, container):
|
||||
|
|
@ -588,16 +557,7 @@ class Client(clientbase.ClientBase):
|
|||
params['tail'] = tail
|
||||
url = self._url("/containers/{0}/logs".format(container))
|
||||
res = self._get(url, params=params, stream=stream)
|
||||
if stream:
|
||||
return self._multiplexed_response_stream_helper(res)
|
||||
elif six.PY3:
|
||||
return bytes().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
else:
|
||||
return str().join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
return self._get_result(container, stream, res)
|
||||
return self.attach(
|
||||
container,
|
||||
stdout=stdout,
|
||||
|
|
|
|||
|
|
@ -221,6 +221,46 @@ class ClientBase(requests.Session):
|
|||
break
|
||||
yield data
|
||||
|
||||
def _stream_raw_result_old(self, response):
|
||||
''' Stream raw output for API versions below 1.6 '''
|
||||
self._raise_for_status(response)
|
||||
for line in response.iter_lines(chunk_size=1,
|
||||
decode_unicode=True):
|
||||
# filter out keep-alive new lines
|
||||
if line:
|
||||
yield line
|
||||
|
||||
def _stream_raw_result(self, response):
|
||||
''' Stream result for TTY-enabled container above API 1.6 '''
|
||||
self._raise_for_status(response)
|
||||
for out in response.iter_content(chunk_size=1, decode_unicode=True):
|
||||
yield out
|
||||
|
||||
def _get_result(self, container, stream, res):
|
||||
cont = self.inspect_container(container)
|
||||
return self._get_result_tty(stream, res, cont['Config']['Tty'])
|
||||
|
||||
def _get_result_tty(self, stream, res, is_tty):
|
||||
# Stream multi-plexing was only introduced in API v1.6. Anything
|
||||
# before that needs old-style streaming.
|
||||
if utils.compare_version('1.6', self._version) < 0:
|
||||
return self._stream_raw_result_old(res)
|
||||
|
||||
# We should also use raw streaming (without keep-alives)
|
||||
# if we're dealing with a tty-enabled container.
|
||||
if is_tty:
|
||||
return self._stream_raw_result(res) if stream else \
|
||||
self._result(res, binary=True)
|
||||
|
||||
self._raise_for_status(res)
|
||||
sep = six.binary_type()
|
||||
if stream:
|
||||
return self._multiplexed_response_stream_helper(res)
|
||||
else:
|
||||
return sep.join(
|
||||
[x for x in self._multiplexed_buffer_helper(res)]
|
||||
)
|
||||
|
||||
def get_adapter(self, url):
|
||||
try:
|
||||
return super(ClientBase, self).get_adapter(url)
|
||||
|
|
|
|||
|
|
@ -129,11 +129,11 @@ def post_fake_create_container():
|
|||
return status_code, response
|
||||
|
||||
|
||||
def get_fake_inspect_container():
|
||||
def get_fake_inspect_container(tty=False):
|
||||
status_code = 200
|
||||
response = {
|
||||
'Id': FAKE_CONTAINER_ID,
|
||||
'Config': {'Privileged': True},
|
||||
'Config': {'Privileged': True, 'Tty': tty},
|
||||
'ID': FAKE_CONTAINER_ID,
|
||||
'Image': 'busybox:latest',
|
||||
"State": {
|
||||
|
|
|
|||
|
|
@ -69,6 +69,14 @@ def fake_resolve_authconfig(authconfig, registry=None):
|
|||
return None
|
||||
|
||||
|
||||
def fake_inspect_container(self, container, tty=False):
|
||||
return fake_api.get_fake_inspect_container(tty=tty)[1]
|
||||
|
||||
|
||||
def fake_inspect_container_tty(self, container):
|
||||
return fake_inspect_container(self, container, tty=True)
|
||||
|
||||
|
||||
def fake_resp(url, data=None, **kwargs):
|
||||
status_code, content = fake_api.fake_responses[url]()
|
||||
return response(status_code=status_code, content=content)
|
||||
|
|
@ -1546,7 +1554,9 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
|
||||
def test_logs(self):
|
||||
try:
|
||||
logs = self.client.logs(fake_api.FAKE_CONTAINER_ID)
|
||||
with mock.patch('docker.Client.inspect_container',
|
||||
fake_inspect_container):
|
||||
logs = self.client.logs(fake_api.FAKE_CONTAINER_ID)
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
|
|
@ -1565,7 +1575,9 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
|
||||
def test_logs_with_dict_instead_of_id(self):
|
||||
try:
|
||||
logs = self.client.logs({'Id': fake_api.FAKE_CONTAINER_ID})
|
||||
with mock.patch('docker.Client.inspect_container',
|
||||
fake_inspect_container):
|
||||
logs = self.client.logs({'Id': fake_api.FAKE_CONTAINER_ID})
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
|
|
@ -1584,7 +1596,9 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
|
||||
def test_log_streaming(self):
|
||||
try:
|
||||
self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True)
|
||||
with mock.patch('docker.Client.inspect_container',
|
||||
fake_inspect_container):
|
||||
self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=True)
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
|
|
@ -1598,7 +1612,10 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
|
||||
def test_log_tail(self):
|
||||
try:
|
||||
self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False, tail=10)
|
||||
with mock.patch('docker.Client.inspect_container',
|
||||
fake_inspect_container):
|
||||
self.client.logs(fake_api.FAKE_CONTAINER_ID, stream=False,
|
||||
tail=10)
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
|
|
@ -1610,6 +1627,27 @@ class DockerClientTest(Cleanup, base.BaseTestCase):
|
|||
stream=False
|
||||
)
|
||||
|
||||
def test_log_tty(self):
|
||||
try:
|
||||
m = mock.Mock()
|
||||
with mock.patch('docker.Client.inspect_container',
|
||||
fake_inspect_container_tty):
|
||||
with mock.patch('docker.Client._stream_raw_result',
|
||||
m):
|
||||
self.client.logs(fake_api.FAKE_CONTAINER_ID,
|
||||
stream=True)
|
||||
except Exception as e:
|
||||
self.fail('Command should not raise exception: {0}'.format(e))
|
||||
|
||||
self.assertTrue(m.called)
|
||||
fake_request.assert_called_with(
|
||||
url_prefix + 'containers/3cc2351ab11b/logs',
|
||||
params={'timestamps': 0, 'follow': 1, 'stderr': 1, 'stdout': 1,
|
||||
'tail': 'all'},
|
||||
timeout=DEFAULT_TIMEOUT_SECONDS,
|
||||
stream=True
|
||||
)
|
||||
|
||||
def test_diff(self):
|
||||
try:
|
||||
self.client.diff(fake_api.FAKE_CONTAINER_ID)
|
||||
|
|
|
|||
Loading…
Reference in New Issue