diff --git a/openhands/runtime/builder/docker.py b/openhands/runtime/builder/docker.py index e429d06079..75fb567bd5 100644 --- a/openhands/runtime/builder/docker.py +++ b/openhands/runtime/builder/docker.py @@ -26,11 +26,16 @@ class DockerRuntimeBuilder(RuntimeBuilder): logger.error(f'Sandbox image build failed: {e}') raise RuntimeError(f'Sandbox image build failed: {e}') + layers: dict[str, dict[str, str]] = {} + previous_layer_count = 0 for log in build_logs: if 'stream' in log: logger.info(log['stream'].strip()) elif 'error' in log: logger.error(log['error'].strip()) + elif 'status' in log: + self._output_build_progress(log, layers, previous_layer_count) + previous_layer_count = len(layers) else: logger.info(str(log)) @@ -85,39 +90,13 @@ class DockerRuntimeBuilder(RuntimeBuilder): 'Image not found locally. Trying to pull it, please wait...' ) - layers = {} + layers: dict[str, dict[str, str]] = {} previous_layer_count = 0 for line in self.docker_client.api.pull( image_name, stream=True, decode=True ): - if 'id' in line and 'progressDetail' in line: - layer_id = line['id'] - if layer_id not in layers: - layers[layer_id] = {'last_logged': 0} - - if ( - 'total' in line['progressDetail'] - and 'current' in line['progressDetail'] - ): - total = line['progressDetail']['total'] - current = line['progressDetail']['current'] - percentage = (current / total) * 100 - - # refresh process bar in console if stdout is a tty - if sys.stdout.isatty(): - layers[layer_id]['last_logged'] = percentage - self._output_pull_progress(layers, previous_layer_count) - previous_layer_count = len(layers) - # otherwise Log only if percentage is at least 10% higher than last logged - elif percentage - layers[layer_id]['last_logged'] >= 10: - logger.info( - f'Layer {layer_id}: {percentage:.0f}% downloaded' - ) - layers[layer_id]['last_logged'] = percentage - - elif 'status' in line: - logger.info(line['status']) - + self._output_build_progress(line, layers, previous_layer_count) + previous_layer_count = len(layers) logger.info('Image pulled') return True except docker.errors.ImageNotFound: @@ -133,9 +112,45 @@ class DockerRuntimeBuilder(RuntimeBuilder): logger.warning(msg) return False - def _output_pull_progress(self, layers: dict, previous_layer_count: int) -> None: - sys.stdout.write('\033[F' * previous_layer_count) - for lid, layer_data in sorted(layers.items()): - sys.stdout.write('\033[K') - print(f'Layer {lid}: {layer_data["last_logged"]:.0f}% downloaded') - sys.stdout.flush() + def _output_build_progress( + self, current_line: dict, layers: dict, previous_layer_count: int + ) -> None: + if 'id' in current_line and 'progressDetail' in current_line: + layer_id = current_line['id'] + if layer_id not in layers: + layers[layer_id] = {'status': '', 'progress': '', 'last_logged': 0} + + if 'status' in current_line: + layers[layer_id]['status'] = current_line['status'] + + if 'progress' in current_line: + layers[layer_id]['progress'] = current_line['progress'] + + if ( + 'total' in current_line['progressDetail'] + and 'current' in current_line['progressDetail'] + ): + total = current_line['progressDetail']['total'] + current = current_line['progressDetail']['current'] + percentage = (current / total) * 100 + else: + percentage = 0 + + # refresh process bar in console if stdout is a tty + if sys.stdout.isatty(): + sys.stdout.write('\033[F' * previous_layer_count) + for lid, layer_data in sorted(layers.items()): + sys.stdout.write('\033[K') + print( + f'Layer {lid}: {layer_data["progress"]} {layer_data["status"]}' + ) + sys.stdout.flush() + # otherwise Log only if percentage is at least 10% higher than last logged + elif percentage != 0 and percentage - layers[layer_id]['last_logged'] >= 10: + logger.info( + f'Layer {layer_id}: {layers[layer_id]["progress"]} {layers[layer_id]["status"]}' + ) + + layers[layer_id]['last_logged'] = percentage + elif 'status' in current_line: + logger.info(current_line['status'])