mirror of https://github.com/docker/docker-py.git
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: Joffrey F <joffrey@docker.com>
This commit is contained in:
parent
deb8222d69
commit
631cc3c121
|
|
@ -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):
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import itertools
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
@ -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):
|
||||||
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):
|
||||||
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):
|
||||||
|
|
||||||
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):
|
||||||
|
|
||||||
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')
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue