mirror of
https://github.com/docker/compose.git
synced 2025-10-29 02:03:58 +01:00
The ANSI escape codes \e[0A (cursor up 0 lines) and \e[0B (cursor down 0 lines) are not well defined and are treated differently by different terminals. In particular xterm treats 0 as a missing parameter and therefore defaults to 1, whereas rxvt-unicode treats these escapes as a request to move 0 lines. However the use of these codes is unnecessary and were really just hiding the fact that we were not correctly computing diff when adding a new line. Having added the new line to the ids map and output the corresponding \n the correct diff would be 1 and not 0 (which xterm interprets as 1) as currently. Rather than changing the hardcoded 0 to a 1 pull the diff calculation out and always do it since it produces the correct answer in both cases. This fixes similar corruption when compose is pulling an image to that seen with `docker pull` and rxvt-unicode (and likely other terminals in that family) seen in docker/docker#28111. This is the same as the fix made to Docker's pkg/jsonmessage in https://github.com/docker/docker/pull/28238 (and I have shamelessly ripped off most of this commit message from there). Signed-off-by: Ian Campbell <ian.campbell@docker.com>
112 lines
2.9 KiB
Python
112 lines
2.9 KiB
Python
from __future__ import absolute_import
|
|
from __future__ import unicode_literals
|
|
|
|
from compose import utils
|
|
|
|
|
|
class StreamOutputError(Exception):
|
|
pass
|
|
|
|
|
|
def stream_output(output, stream):
|
|
is_terminal = hasattr(stream, 'isatty') and stream.isatty()
|
|
stream = utils.get_output_stream(stream)
|
|
all_events = []
|
|
lines = {}
|
|
diff = 0
|
|
|
|
for event in utils.json_stream(output):
|
|
all_events.append(event)
|
|
is_progress_event = 'progress' in event or 'progressDetail' in event
|
|
|
|
if not is_progress_event:
|
|
print_output_event(event, stream, is_terminal)
|
|
stream.flush()
|
|
continue
|
|
|
|
if not is_terminal:
|
|
continue
|
|
|
|
# if it's a progress event and we have a terminal, then display the progress bars
|
|
image_id = event.get('id')
|
|
if not image_id:
|
|
continue
|
|
|
|
if image_id not in lines:
|
|
lines[image_id] = len(lines)
|
|
stream.write("\n")
|
|
|
|
diff = len(lines) - lines[image_id]
|
|
|
|
# move cursor up `diff` rows
|
|
stream.write("%c[%dA" % (27, diff))
|
|
|
|
print_output_event(event, stream, is_terminal)
|
|
|
|
if 'id' in event:
|
|
# move cursor back down
|
|
stream.write("%c[%dB" % (27, diff))
|
|
|
|
stream.flush()
|
|
|
|
return all_events
|
|
|
|
|
|
def print_output_event(event, stream, is_terminal):
|
|
if 'errorDetail' in event:
|
|
raise StreamOutputError(event['errorDetail']['message'])
|
|
|
|
terminator = ''
|
|
|
|
if is_terminal and 'stream' not in event:
|
|
# erase current line
|
|
stream.write("%c[2K\r" % 27)
|
|
terminator = "\r"
|
|
elif 'progressDetail' in event:
|
|
return
|
|
|
|
if 'time' in event:
|
|
stream.write("[%s] " % event['time'])
|
|
|
|
if 'id' in event:
|
|
stream.write("%s: " % event['id'])
|
|
|
|
if 'from' in event:
|
|
stream.write("(from %s) " % event['from'])
|
|
|
|
status = event.get('status', '')
|
|
|
|
if 'progress' in event:
|
|
stream.write("%s %s%s" % (status, event['progress'], terminator))
|
|
elif 'progressDetail' in event:
|
|
detail = event['progressDetail']
|
|
total = detail.get('total')
|
|
if 'current' in detail and total:
|
|
percentage = float(detail['current']) / float(total) * 100
|
|
stream.write('%s (%.1f%%)%s' % (status, percentage, terminator))
|
|
else:
|
|
stream.write('%s%s' % (status, terminator))
|
|
elif 'stream' in event:
|
|
stream.write("%s%s" % (event['stream'], terminator))
|
|
else:
|
|
stream.write("%s%s\n" % (status, terminator))
|
|
|
|
|
|
def get_digest_from_pull(events):
|
|
for event in events:
|
|
status = event.get('status')
|
|
if not status or 'Digest' not in status:
|
|
continue
|
|
|
|
_, digest = status.split(':', 1)
|
|
return digest.strip()
|
|
return None
|
|
|
|
|
|
def get_digest_from_push(events):
|
|
for event in events:
|
|
digest = event.get('aux', {}).get('Digest')
|
|
if digest:
|
|
return digest
|
|
return None
|