Skip to content

Commit bf699c7

Browse files
authored
Merge pull request #1836 from docker/1813-run-autoremove
Retrieve container logs before container exits / is removed
2 parents 1fd5958 + 6b8dfe4 commit bf699c7

File tree

4 files changed

+43
-18
lines changed

4 files changed

+43
-18
lines changed

docker/models/containers.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,9 @@ def run(self, image, command=None, stdout=True, stderr=False,
629629
(e.g. ``SIGINT``).
630630
storage_opt (dict): Storage driver options per container as a
631631
key-value mapping.
632+
stream (bool): If true and ``detach`` is false, return a log
633+
generator instead of a string. Ignored if ``detach`` is true.
634+
Default: ``False``.
632635
sysctls (dict): Kernel parameters to set in the container.
633636
tmpfs (dict): Temporary filesystems to mount, as a dictionary
634637
mapping a path inside the container to options for that path.
@@ -696,6 +699,7 @@ def run(self, image, command=None, stdout=True, stderr=False,
696699
"""
697700
if isinstance(image, Image):
698701
image = image.id
702+
stream = kwargs.pop('stream', False)
699703
detach = kwargs.pop("detach", False)
700704
if detach and remove:
701705
if version_gte(self.client.api._version, '1.25'):
@@ -723,23 +727,28 @@ def run(self, image, command=None, stdout=True, stderr=False,
723727
if detach:
724728
return container
725729

726-
exit_status = container.wait()
727-
if exit_status != 0:
728-
stdout = False
729-
stderr = True
730-
731730
logging_driver = container.attrs['HostConfig']['LogConfig']['Type']
732731

732+
out = None
733733
if logging_driver == 'json-file' or logging_driver == 'journald':
734-
out = container.logs(stdout=stdout, stderr=stderr)
735-
else:
736-
out = None
734+
out = container.logs(
735+
stdout=stdout, stderr=stderr, stream=True, follow=True
736+
)
737+
738+
exit_status = container.wait()
739+
if exit_status != 0:
740+
out = container.logs(stdout=False, stderr=True)
737741

738742
if remove:
739743
container.remove()
740744
if exit_status != 0:
741-
raise ContainerError(container, exit_status, command, image, out)
742-
return out
745+
raise ContainerError(
746+
container, exit_status, command, image, out
747+
)
748+
749+
return out if stream or out is None else b''.join(
750+
[line for line in out]
751+
)
743752

744753
def create(self, image, command=None, **kwargs):
745754
"""

tests/integration/models_containers_test.py

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import docker
22
import tempfile
33
from .base import BaseIntegrationTest, TEST_API_VERSION
4-
from ..helpers import random_name
4+
from ..helpers import random_name, requires_api_version
55

66

77
class ContainerCollectionTest(BaseIntegrationTest):
@@ -95,7 +95,7 @@ def test_run_with_none_driver(self):
9595
"alpine", "echo hello",
9696
log_config=dict(type='none')
9797
)
98-
self.assertEqual(out, None)
98+
assert out is None
9999

100100
def test_run_with_json_file_driver(self):
101101
client = docker.from_env(version=TEST_API_VERSION)
@@ -104,7 +104,24 @@ def test_run_with_json_file_driver(self):
104104
"alpine", "echo hello",
105105
log_config=dict(type='json-file')
106106
)
107-
self.assertEqual(out, b'hello\n')
107+
assert out == b'hello\n'
108+
109+
@requires_api_version('1.25')
110+
def test_run_with_auto_remove(self):
111+
client = docker.from_env(version=TEST_API_VERSION)
112+
out = client.containers.run(
113+
'alpine', 'echo hello', auto_remove=True
114+
)
115+
assert out == b'hello\n'
116+
117+
def test_run_with_streamed_logs(self):
118+
client = docker.from_env(version=TEST_API_VERSION)
119+
out = client.containers.run(
120+
'alpine', 'sh -c "echo hello && echo world"', stream=True
121+
)
122+
logs = [line for line in out]
123+
assert logs[0] == b'hello\n'
124+
assert logs[1] == b'world\n'
108125

109126
def test_get(self):
110127
client = docker.from_env(version=TEST_API_VERSION)

tests/unit/fake_api_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def make_fake_api_client():
4343
fake_api.get_fake_inspect_container()[1],
4444
'inspect_image.return_value': fake_api.get_fake_inspect_image()[1],
4545
'inspect_network.return_value': fake_api.get_fake_network()[1],
46-
'logs.return_value': 'hello world\n',
46+
'logs.return_value': [b'hello world\n'],
4747
'networks.return_value': fake_api.get_fake_network_list()[1],
4848
'start.return_value': None,
4949
'wait.return_value': 0,

tests/unit/models_containers_test.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def test_run(self):
1212
client = make_fake_client()
1313
out = client.containers.run("alpine", "echo hello world")
1414

15-
assert out == 'hello world\n'
15+
assert out == b'hello world\n'
1616

1717
client.api.create_container.assert_called_with(
1818
image="alpine",
@@ -24,9 +24,8 @@ def test_run(self):
2424
client.api.start.assert_called_with(FAKE_CONTAINER_ID)
2525
client.api.wait.assert_called_with(FAKE_CONTAINER_ID)
2626
client.api.logs.assert_called_with(
27-
FAKE_CONTAINER_ID,
28-
stderr=False,
29-
stdout=True
27+
FAKE_CONTAINER_ID, stderr=False, stdout=True, stream=True,
28+
follow=True
3029
)
3130

3231
def test_create_container_args(self):

0 commit comments

Comments
 (0)