1from __future__ import absolute_import 2from __future__ import unicode_literals 3 4from compose import utils 5 6 7class StreamOutputError(Exception): 8 pass 9 10 11def write_to_stream(s, stream): 12 try: 13 stream.write(s) 14 except UnicodeEncodeError: 15 encoding = getattr(stream, 'encoding', 'ascii') 16 stream.write(s.encode(encoding, errors='replace').decode(encoding)) 17 18 19def stream_output(output, stream): 20 is_terminal = hasattr(stream, 'isatty') and stream.isatty() 21 stream = utils.get_output_stream(stream) 22 lines = {} 23 diff = 0 24 25 for event in utils.json_stream(output): 26 yield event 27 is_progress_event = 'progress' in event or 'progressDetail' in event 28 29 if not is_progress_event: 30 print_output_event(event, stream, is_terminal) 31 stream.flush() 32 continue 33 34 if not is_terminal: 35 continue 36 37 # if it's a progress event and we have a terminal, then display the progress bars 38 image_id = event.get('id') 39 if not image_id: 40 continue 41 42 if image_id not in lines: 43 lines[image_id] = len(lines) 44 write_to_stream("\n", stream) 45 46 diff = len(lines) - lines[image_id] 47 48 # move cursor up `diff` rows 49 write_to_stream("%c[%dA" % (27, diff), stream) 50 51 print_output_event(event, stream, is_terminal) 52 53 if 'id' in event: 54 # move cursor back down 55 write_to_stream("%c[%dB" % (27, diff), stream) 56 57 stream.flush() 58 59 60def print_output_event(event, stream, is_terminal): 61 if 'errorDetail' in event: 62 raise StreamOutputError(event['errorDetail']['message']) 63 64 terminator = '' 65 66 if is_terminal and 'stream' not in event: 67 # erase current line 68 write_to_stream("%c[2K\r" % 27, stream) 69 terminator = "\r" 70 elif 'progressDetail' in event: 71 return 72 73 if 'time' in event: 74 write_to_stream("[%s] " % event['time'], stream) 75 76 if 'id' in event: 77 write_to_stream("%s: " % event['id'], stream) 78 79 if 'from' in event: 80 write_to_stream("(from %s) " % event['from'], stream) 81 82 status = event.get('status', '') 83 84 if 'progress' in event: 85 write_to_stream("%s %s%s" % (status, event['progress'], terminator), stream) 86 elif 'progressDetail' in event: 87 detail = event['progressDetail'] 88 total = detail.get('total') 89 if 'current' in detail and total: 90 percentage = float(detail['current']) / float(total) * 100 91 write_to_stream('%s (%.1f%%)%s' % (status, percentage, terminator), stream) 92 else: 93 write_to_stream('%s%s' % (status, terminator), stream) 94 elif 'stream' in event: 95 write_to_stream("%s%s" % (event['stream'], terminator), stream) 96 else: 97 write_to_stream("%s%s\n" % (status, terminator), stream) 98 99 100def get_digest_from_pull(events): 101 digest = None 102 for event in events: 103 status = event.get('status') 104 if not status or 'Digest' not in status: 105 continue 106 else: 107 digest = status.split(':', 1)[1].strip() 108 return digest 109 110 111def get_digest_from_push(events): 112 for event in events: 113 digest = event.get('aux', {}).get('Digest') 114 if digest: 115 return digest 116 return None 117