Kaydet (Commit) 631cc3c1 authored tarafından Joffrey F's avatar Joffrey F

ImageCollection.build now also returns build logs along with the built image reference

BuildError.build_logs has a copy of the logs generator
Signed-off-by: 's avatarJoffrey F <joffrey@docker.com>
üst deb8222d
...@@ -140,8 +140,11 @@ class StreamParseError(RuntimeError): ...@@ -140,8 +140,11 @@ class StreamParseError(RuntimeError):
self.msg = reason self.msg = reason
class BuildError(Exception): class BuildError(DockerException):
pass def __init__(self, reason, build_log):
super(BuildError, self).__init__(reason)
self.msg = reason
self.build_log = build_log
class ImageLoadError(DockerException): class ImageLoadError(DockerException):
......
import itertools
import re import re
import six import six
...@@ -160,7 +161,9 @@ class ImageCollection(Collection): ...@@ -160,7 +161,9 @@ class ImageCollection(Collection):
platform (str): Platform in the format ``os[/arch[/variant]]``. platform (str): Platform in the format ``os[/arch[/variant]]``.
Returns: Returns:
(:py:class:`Image`): The built image. (tuple): The first item is the :py:class:`Image` object for the
image that was build. The second item is a generator of the
build logs as JSON-decoded objects.
Raises: Raises:
:py:class:`docker.errors.BuildError` :py:class:`docker.errors.BuildError`
...@@ -175,9 +178,10 @@ class ImageCollection(Collection): ...@@ -175,9 +178,10 @@ class ImageCollection(Collection):
return self.get(resp) return self.get(resp)
last_event = None last_event = None
image_id = None image_id = None
for chunk in json_stream(resp): result_stream, internal_stream = itertools.tee(json_stream(resp))
for chunk in internal_stream:
if 'error' in chunk: if 'error' in chunk:
raise BuildError(chunk['error']) raise BuildError(chunk['error'], result_stream)
if 'stream' in chunk: if 'stream' in chunk:
match = re.search( match = re.search(
r'(^Successfully built |sha256:)([0-9a-f]+)$', r'(^Successfully built |sha256:)([0-9a-f]+)$',
...@@ -187,8 +191,8 @@ class ImageCollection(Collection): ...@@ -187,8 +191,8 @@ class ImageCollection(Collection):
image_id = match.group(2) image_id = match.group(2)
last_event = chunk last_event = chunk
if image_id: if image_id:
return self.get(image_id) return (self.get(image_id), result_stream)
raise BuildError(last_event or 'Unknown') raise BuildError(last_event or 'Unknown', result_stream)
def get(self, name): def get(self, name):
""" """
......
...@@ -10,27 +10,30 @@ class ImageCollectionTest(BaseIntegrationTest): ...@@ -10,27 +10,30 @@ class ImageCollectionTest(BaseIntegrationTest):
def test_build(self): def test_build(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
image = client.images.build(fileobj=io.BytesIO( image, _ = client.images.build(fileobj=io.BytesIO(
"FROM alpine\n" "FROM alpine\n"
"CMD echo hello world".encode('ascii') "CMD echo hello world".encode('ascii')
)) ))
self.tmp_imgs.append(image.id) self.tmp_imgs.append(image.id)
assert client.containers.run(image) == b"hello world\n" assert client.containers.run(image) == b"hello world\n"
@pytest.mark.xfail(reason='Engine 1.13 responds with status 500') # @pytest.mark.xfail(reason='Engine 1.13 responds with status 500')
def test_build_with_error(self): def test_build_with_error(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
with self.assertRaises(docker.errors.BuildError) as cm: with self.assertRaises(docker.errors.BuildError) as cm:
client.images.build(fileobj=io.BytesIO( client.images.build(fileobj=io.BytesIO(
"FROM alpine\n" "FROM alpine\n"
"NOTADOCKERFILECOMMAND".encode('ascii') "RUN exit 1".encode('ascii')
)) ))
assert str(cm.exception) == ("Unknown instruction: " print(cm.exception)
"NOTADOCKERFILECOMMAND") assert str(cm.exception) == (
"The command '/bin/sh -c exit 1' returned a non-zero code: 1"
)
assert cm.exception.build_log
def test_build_with_multiple_success(self): def test_build_with_multiple_success(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
image = client.images.build( image, _ = client.images.build(
tag='some-tag', fileobj=io.BytesIO( tag='some-tag', fileobj=io.BytesIO(
"FROM alpine\n" "FROM alpine\n"
"CMD echo hello world".encode('ascii') "CMD echo hello world".encode('ascii')
...@@ -41,7 +44,7 @@ class ImageCollectionTest(BaseIntegrationTest): ...@@ -41,7 +44,7 @@ class ImageCollectionTest(BaseIntegrationTest):
def test_build_with_success_build_output(self): def test_build_with_success_build_output(self):
client = docker.from_env(version=TEST_API_VERSION) client = docker.from_env(version=TEST_API_VERSION)
image = client.images.build( image, _ = client.images.build(
tag='dup-txt-tag', fileobj=io.BytesIO( tag='dup-txt-tag', fileobj=io.BytesIO(
"FROM alpine\n" "FROM alpine\n"
"CMD echo Successfully built abcd1234".encode('ascii') "CMD echo Successfully built abcd1234".encode('ascii')
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment