Skip to content

Commit 9c0332e

Browse files
authored
Merge pull request #1875 from docker/1702-build-logs-dockerclient
ImageCollection.build now also returns build logs
2 parents deb8222 + 631cc3c commit 9c0332e

File tree

3 files changed

+24
-14
lines changed

3 files changed

+24
-14
lines changed

docker/errors.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -140,8 +140,11 @@ def __init__(self, reason):
140140
self.msg = reason
141141

142142

143-
class BuildError(Exception):
144-
pass
143+
class BuildError(DockerException):
144+
def __init__(self, reason, build_log):
145+
super(BuildError, self).__init__(reason)
146+
self.msg = reason
147+
self.build_log = build_log
145148

146149

147150
class ImageLoadError(DockerException):

docker/models/images.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import itertools
12
import re
23

34
import six
@@ -160,7 +161,9 @@ def build(self, **kwargs):
160161
platform (str): Platform in the format ``os[/arch[/variant]]``.
161162
162163
Returns:
163-
(:py:class:`Image`): The built image.
164+
(tuple): The first item is the :py:class:`Image` object for the
165+
image that was build. The second item is a generator of the
166+
build logs as JSON-decoded objects.
164167
165168
Raises:
166169
:py:class:`docker.errors.BuildError`
@@ -175,9 +178,10 @@ def build(self, **kwargs):
175178
return self.get(resp)
176179
last_event = None
177180
image_id = None
178-
for chunk in json_stream(resp):
181+
result_stream, internal_stream = itertools.tee(json_stream(resp))
182+
for chunk in internal_stream:
179183
if 'error' in chunk:
180-
raise BuildError(chunk['error'])
184+
raise BuildError(chunk['error'], result_stream)
181185
if 'stream' in chunk:
182186
match = re.search(
183187
r'(^Successfully built |sha256:)([0-9a-f]+)$',
@@ -187,8 +191,8 @@ def build(self, **kwargs):
187191
image_id = match.group(2)
188192
last_event = chunk
189193
if image_id:
190-
return self.get(image_id)
191-
raise BuildError(last_event or 'Unknown')
194+
return (self.get(image_id), result_stream)
195+
raise BuildError(last_event or 'Unknown', result_stream)
192196

193197
def get(self, name):
194198
"""

tests/integration/models_images_test.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,30 @@ class ImageCollectionTest(BaseIntegrationTest):
1010

1111
def test_build(self):
1212
client = docker.from_env(version=TEST_API_VERSION)
13-
image = client.images.build(fileobj=io.BytesIO(
13+
image, _ = client.images.build(fileobj=io.BytesIO(
1414
"FROM alpine\n"
1515
"CMD echo hello world".encode('ascii')
1616
))
1717
self.tmp_imgs.append(image.id)
1818
assert client.containers.run(image) == b"hello world\n"
1919

20-
@pytest.mark.xfail(reason='Engine 1.13 responds with status 500')
20+
# @pytest.mark.xfail(reason='Engine 1.13 responds with status 500')
2121
def test_build_with_error(self):
2222
client = docker.from_env(version=TEST_API_VERSION)
2323
with self.assertRaises(docker.errors.BuildError) as cm:
2424
client.images.build(fileobj=io.BytesIO(
2525
"FROM alpine\n"
26-
"NOTADOCKERFILECOMMAND".encode('ascii')
26+
"RUN exit 1".encode('ascii')
2727
))
28-
assert str(cm.exception) == ("Unknown instruction: "
29-
"NOTADOCKERFILECOMMAND")
28+
print(cm.exception)
29+
assert str(cm.exception) == (
30+
"The command '/bin/sh -c exit 1' returned a non-zero code: 1"
31+
)
32+
assert cm.exception.build_log
3033

3134
def test_build_with_multiple_success(self):
3235
client = docker.from_env(version=TEST_API_VERSION)
33-
image = client.images.build(
36+
image, _ = client.images.build(
3437
tag='some-tag', fileobj=io.BytesIO(
3538
"FROM alpine\n"
3639
"CMD echo hello world".encode('ascii')
@@ -41,7 +44,7 @@ def test_build_with_multiple_success(self):
4144

4245
def test_build_with_success_build_output(self):
4346
client = docker.from_env(version=TEST_API_VERSION)
44-
image = client.images.build(
47+
image, _ = client.images.build(
4548
tag='dup-txt-tag', fileobj=io.BytesIO(
4649
"FROM alpine\n"
4750
"CMD echo Successfully built abcd1234".encode('ascii')

0 commit comments

Comments
 (0)