import json import socket import sys import os from typing import List, Tuple, Dict import click import requests from ..operation_strings import descriptions as op_desc class Abort(click.ClickException): """Abort silently.""" def __init__(self, exit_code=1): super().__init__('') self.exit_code = exit_code def show(self): pass def print_colon_kv(pairs: List[Tuple[str, str]]): """ Pretty-print a list of key-value pairs such that the key and value columns align. Example: key1: value1 key1000: value2 """ maxlen = max(len(key) for key, val in pairs) for key, val in pairs: if key != '': click.echo(key + ': ', nl=False) else: # assume this is a continuation from the previous line click.echo(' ', nl=False) extra_space = ' ' * (maxlen - len(key)) click.echo(extra_space, nl=False) click.echo(val) def handle_stream_response(resp: requests.Response, operations: List[str]) -> List[Dict]: """ Print output to the console while operations are being streamed from the server over HTTP. Returns the parsed JSON data streamed from the server. """ if resp.status_code != 200: click.echo('An error occurred:') click.echo(resp.text.rstrip()) raise Abort() click.echo(op_desc[operations[0]] + '... ', nl=False) idx = 0 data = [] for line in resp.iter_lines(decode_unicode=True, chunk_size=8): d = json.loads(line) data.append(d) if d['status'] == 'aborted': click.echo(click.style('ABORTED', fg='red')) click.echo('The transaction was rolled back.') click.echo('The error was: ' + d['error']) click.echo('Please check the ceod logs.') sys.exit(1) elif d['status'] == 'completed': if idx < len(operations): click.echo('Skipped') click.echo('Transaction successfully completed.') return data operation = d['operation'] oper_failed = False err_msg = None prefix = 'failed_to_' if operation.startswith(prefix): operation = operation[len(prefix):] oper_failed = True # sometimes the operation looks like # "failed_to_do_something: error message" if ':' in operation: operation, err_msg = operation.split(': ', 1) while idx < len(operations) and operations[idx] != operation: click.echo('Skipped') idx += 1 if idx == len(operations): break click.echo(op_desc[operations[idx]] + '... ', nl=False) if idx == len(operations): click.echo('Unrecognized operation: ' + operation) continue if oper_failed: click.echo(click.style('Failed', fg='red')) if err_msg is not None: click.echo(' Error message: ' + err_msg) else: click.echo(click.style('Done', fg='green')) idx += 1 if idx < len(operations): click.echo(op_desc[operations[idx]] + '... ', nl=False) raise Exception('server response ended abruptly') def handle_sync_response(resp: requests.Response): """ Exit the program if the request was not successful. Returns the parsed JSON response. """ if resp.status_code != 200: click.echo('An error occurred:') click.echo(resp.text.rstrip()) raise Abort() return resp.json() def check_file_path(file): if os.path.isfile(file): click.echo(f"{file} will be overwritten") click.confirm('Do you want to continue?', abort=True) elif os.path.isdir(file): click.echo(f"Error: there exists a directory at {file}") raise Abort() def check_if_in_development() -> bool: """Aborts if we are not currently in the dev environment.""" if not socket.getfqdn().endswith('.csclub.internal'): click.echo('This command may only be called during development.') raise Abort()