From da6cbd4535f7e458e382dc070008f0aa41928e6b Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Wed, 15 Jul 2015 17:44:46 +0100 Subject: [PATCH 1/3] Improve output for parallel command This approach takes the style of replacing the output message, in place, when the command has finished executing. Bringing it a bit more inline with what `docker pull` does. Signed-off-by: Mazz Mosley --- compose/utils.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/compose/utils.py b/compose/utils.py index 6dc751c636..3efde05214 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -1,7 +1,9 @@ +import codecs import hashlib import json import logging import os +import sys import concurrent.futures @@ -16,9 +18,11 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): Execute a given command upon a list of containers in parallel. """ max_workers = os.environ.get('COMPOSE_MAX_WORKERS', DEFAULT_MAX_WORKERS) + stream = codecs.getwriter('utf-8')(sys.stdout) + lines = [] def container_command_execute(container, command, **options): - log.info("{} {}...".format(doing_msg, container.name)) + write_out_msg(stream, lines, container.name, doing_msg) return getattr(container, command)(**options) with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: @@ -33,7 +37,24 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): for future in concurrent.futures.as_completed(future_container): container = future_container[future] - log.info("{} {}".format(done_msg, container.name)) + write_out_msg(stream, lines, container.name, done_msg) + + +def write_out_msg(stream, lines, container_name, msg): + if container_name in lines: + position = lines.index(container_name) + diff = len(lines) - position + # move up + stream.write("%c[%dA" % (27, diff)) + # erase + stream.write("%c[2K\r" % 27) + stream.write("{}: {} \n".format(container_name, msg)) + # move back down + stream.write("%c[%dB" % (27, diff)) + else: + diff = 0 + lines.append(container_name) + stream.write("{}: {}... \r\n".format(container_name, msg)) def json_hash(obj): From 61787fecea4e0058dc65d5dcb29afe3399621ce0 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Thu, 16 Jul 2015 14:32:39 +0100 Subject: [PATCH 2/3] Resolve race condition Sometimes, some messages were being executed at the same time, meaning that the status wasn't being overwritten, it was displaying on a separate line for both doing and done messages. Rather than trying to have both sets of statuses being written out concurrently, we write out all of the doing messages first. Then the done messages are written out/updated, as they are completed. Signed-off-by: Mazz Mosley --- compose/utils.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/compose/utils.py b/compose/utils.py index 3efde05214..5ffe7b707d 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -21,8 +21,10 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): stream = codecs.getwriter('utf-8')(sys.stdout) lines = [] - def container_command_execute(container, command, **options): + for container in containers: write_out_msg(stream, lines, container.name, doing_msg) + + def container_command_execute(container, command, **options): return getattr(container, command)(**options) with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: @@ -41,6 +43,10 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): def write_out_msg(stream, lines, container_name, msg): + """ + Using special ANSI code characters we can write out the msg over the top of + a previous status message, if it exists. + """ if container_name in lines: position = lines.index(container_name) diff = len(lines) - position @@ -56,6 +62,8 @@ def write_out_msg(stream, lines, container_name, msg): lines.append(container_name) stream.write("{}: {}... \r\n".format(container_name, msg)) + stream.flush() + def json_hash(obj): dump = json.dumps(obj, sort_keys=True, separators=(',', ':')) From 9d9b8657966a574ff4ec30390985606227ea6e14 Mon Sep 17 00:00:00 2001 From: Mazz Mosley Date: Thu, 16 Jul 2015 16:07:30 +0100 Subject: [PATCH 3/3] Add in error handling Signed-off-by: Mazz Mosley --- compose/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compose/utils.py b/compose/utils.py index 5ffe7b707d..c3316ccda8 100644 --- a/compose/utils.py +++ b/compose/utils.py @@ -5,6 +5,7 @@ import logging import os import sys +from docker.errors import APIError import concurrent.futures from .const import DEFAULT_MAX_WORKERS @@ -20,12 +21,16 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): max_workers = os.environ.get('COMPOSE_MAX_WORKERS', DEFAULT_MAX_WORKERS) stream = codecs.getwriter('utf-8')(sys.stdout) lines = [] + errors = {} for container in containers: write_out_msg(stream, lines, container.name, doing_msg) def container_command_execute(container, command, **options): - return getattr(container, command)(**options) + try: + getattr(container, command)(**options) + except APIError as e: + errors[container.name] = e.explanation with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor: future_container = { @@ -41,6 +46,10 @@ def parallel_execute(command, containers, doing_msg, done_msg, **options): container = future_container[future] write_out_msg(stream, lines, container.name, done_msg) + if errors: + for container in errors: + stream.write("ERROR: for {} {} \n".format(container, errors[container])) + def write_out_msg(stream, lines, container_name, msg): """