From 39e7280d02fdcae32ff7b6659d145b5c779f4fa9 Mon Sep 17 00:00:00 2001 From: Peter Hyatt Date: Fri, 5 Apr 2024 08:50:44 -0400 Subject: [PATCH 1/5] Add stream and timestamp args to client.build Signed-off-by: Peter Hyatt --- docker/models/images.py | 17 ++++++++++++++++- tests/unit/api_build_test.py | 15 +++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/docker/models/images.py b/docker/models/images.py index 4f058d24d9..f435da76c7 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -217,7 +217,7 @@ def reload(self): class ImageCollection(Collection): model = Image - def build(self, **kwargs): + def build(self, stream=False, timestamp=False, **kwargs): """ Build an image and return it. Similar to the ``docker build`` command. Either ``path`` or ``fileobj`` must be set. @@ -245,6 +245,10 @@ def build(self, **kwargs): custom_context (bool): Optional if using ``fileobj`` encoding (str): The encoding for a stream. Set to ``gzip`` for compressing + stream (bool): Print the build output to stdout and stderr while + the build runs + timestamp (bool): Prefix build output stream with a timestamp + "%Y-%m-%d %H:%M:%S" pull (bool): Downloads any updates to the FROM image in Dockerfiles forcerm (bool): Always remove intermediate containers, even after unsuccessful builds @@ -300,9 +304,20 @@ def build(self, **kwargs): image_id = None result_stream, internal_stream = itertools.tee(json_stream(resp)) for chunk in internal_stream: + if timestamp: + timestamp_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") if 'error' in chunk: + if stream: + if timestamp: + print(timestamp_str, "- ", end='', file=sys.stderr) + print("Error:", chunk['error'].strip(), flush=True, file=sys.stderr)) raise BuildError(chunk['error'], result_stream) if 'stream' in chunk: + if stream: + for line in chunk["stream"].splitlines(): + if timestamp: + print(timestamp_str, "- ", end='') + print(line.strip(), flush=True) match = re.search( r'(^Successfully built |sha256:)([0-9a-f]+)$', chunk['stream'] diff --git a/tests/unit/api_build_test.py b/tests/unit/api_build_test.py index 01958c3e1f..245fdaf243 100644 --- a/tests/unit/api_build_test.py +++ b/tests/unit/api_build_test.py @@ -28,6 +28,21 @@ def test_build_container(self): self.client.build(fileobj=script) + def test_build_container_with_stream_with_timestamp(self): + script = io.BytesIO( + "\n".join( + [ + "FROM busybox", + "RUN mkdir -p /tmp/test", + "EXPOSE 8080", + "ADD https://dl.dropboxusercontent.com/u/20637798/silence.tar.gz" + " /tmp/silence.tar.gz", + ] + ).encode("ascii") + ) + + self.client.build(fileobj=script, stream=True, timestamp=True) + def test_build_container_pull(self): script = io.BytesIO( "\n".join( From 777504af82a47e6bce858c796ec979fef03e1a8e Mon Sep 17 00:00:00 2001 From: Peter Hyatt Date: Fri, 5 Apr 2024 11:29:57 -0400 Subject: [PATCH 2/5] Fix parenths typo Signed-off-by: Peter Hyatt --- docker/models/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/models/images.py b/docker/models/images.py index f435da76c7..fdf60a4018 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -310,7 +310,7 @@ def build(self, stream=False, timestamp=False, **kwargs): if stream: if timestamp: print(timestamp_str, "- ", end='', file=sys.stderr) - print("Error:", chunk['error'].strip(), flush=True, file=sys.stderr)) + print("Error:", chunk['error'].strip(), flush=True, file=sys.stderr) raise BuildError(chunk['error'], result_stream) if 'stream' in chunk: if stream: From adad7f8b22c6cece6ea24d4b956c0d61b321937e Mon Sep 17 00:00:00 2001 From: Peter Hyatt Date: Fri, 5 Apr 2024 12:41:39 -0400 Subject: [PATCH 3/5] Fix missing import Signed-off-by: Peter Hyatt --- docker/models/images.py | 1 + 1 file changed, 1 insertion(+) diff --git a/docker/models/images.py b/docker/models/images.py index fdf60a4018..839253ca0b 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -1,4 +1,5 @@ import itertools +import datetime import re import warnings From fc5438cbeea9fb1d8d3d8756ee365b6a5aabc371 Mon Sep 17 00:00:00 2001 From: Peter Hyatt Date: Fri, 5 Apr 2024 12:55:31 -0400 Subject: [PATCH 4/5] Remove excess whitespace Signed-off-by: Peter Hyatt --- docker/models/images.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/docker/models/images.py b/docker/models/images.py index 839253ca0b..fb664e68a7 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -246,9 +246,9 @@ def build(self, stream=False, timestamp=False, **kwargs): custom_context (bool): Optional if using ``fileobj`` encoding (str): The encoding for a stream. Set to ``gzip`` for compressing - stream (bool): Print the build output to stdout and stderr while + stream (bool): Print the build output to stdout and stderr while the build runs - timestamp (bool): Prefix build output stream with a timestamp + timestamp (bool): Prefix build output stream with a timestamp "%Y-%m-%d %H:%M:%S" pull (bool): Downloads any updates to the FROM image in Dockerfiles forcerm (bool): Always remove intermediate containers, even after @@ -309,16 +309,18 @@ def build(self, stream=False, timestamp=False, **kwargs): timestamp_str = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") if 'error' in chunk: if stream: - if timestamp: - print(timestamp_str, "- ", end='', file=sys.stderr) - print("Error:", chunk['error'].strip(), flush=True, file=sys.stderr) + if len(chunk['error'].strip()) > 0: + if timestamp: + print(timestamp_str, "- ", end='', file=sys.stderr) + print("Error:", chunk['error'].strip(), flush=True, file=sys.stderr) raise BuildError(chunk['error'], result_stream) if 'stream' in chunk: if stream: for line in chunk["stream"].splitlines(): - if timestamp: - print(timestamp_str, "- ", end='') - print(line.strip(), flush=True) + if len(line.strip()) > 0: + if timestamp: + print(timestamp_str, "- ", end='') + print(line.strip(), flush=True) match = re.search( r'(^Successfully built |sha256:)([0-9a-f]+)$', chunk['stream'] From d88f413e10111748c4721848e803edd61fbc83ba Mon Sep 17 00:00:00 2001 From: Peter Hyatt Date: Fri, 5 Apr 2024 16:17:05 -0400 Subject: [PATCH 5/5] Strip extra escape codes on empty lines Signed-off-by: Peter Hyatt --- docker/models/images.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/models/images.py b/docker/models/images.py index fb664e68a7..7d890a5e99 100644 --- a/docker/models/images.py +++ b/docker/models/images.py @@ -317,7 +317,7 @@ def build(self, stream=False, timestamp=False, **kwargs): if 'stream' in chunk: if stream: for line in chunk["stream"].splitlines(): - if len(line.strip()) > 0: + if len(line.strip()) > 0 and not bool(re.match(r'^(\s*\x1b\[[0-9;]*m)*\s*(\x1b\[0m)?\s*$', line.strip())): if timestamp: print(timestamp_str, "- ", end='') print(line.strip(), flush=True)