def do_genesis(args, data_dir): """Given the command args, take an series of input files containing GenesisData, combine all the batches into one GenesisData, and output the result into a new file. """ genesis_batches = [] for input_file in args.input_file: print('Processing {}...'.format(input_file)) input_data = BatchList() try: with open(input_file, 'rb') as in_file: input_data.ParseFromString(in_file.read()) except: raise CliException('Unable to read {}'.format(input_file)) genesis_batches += input_data.batches _validate_depedencies(genesis_batches) if args.output: genesis_file = args.output else: genesis_file = os.path.join(data_dir, 'genesis.batch') print('Generating {}'.format(genesis_file)) output_data = GenesisData(batches=genesis_batches) with open(genesis_file, 'wb') as out_file: out_file.write(output_data.SerializeToString())
def _do_config_set(args): """Executes the 'set' subcommand. Given a key file, and a series of key/value pairs, it generates batches of sawtooth_config transactions in a BatchList instance, and stores it in a file. """ settings = [s.split('=', 1) for s in args.setting] with open(args.key, 'r') as key_file: wif_key = key_file.read().strip() signing_key = bitcoin.encode_privkey( bitcoin.decode_privkey(wif_key, 'wif'), 'hex') pubkey = bitcoin.encode_pubkey(bitcoin.privkey_to_pubkey(signing_key), 'hex') txns = [ _create_config_txn(pubkey, signing_key, setting) for setting in settings ] txn_ids = [txn.header_signature for txn in txns] batch_header = BatchHeader(signer_pubkey=pubkey, transaction_ids=txn_ids).SerializeToString() batch = Batch(header=batch_header, header_signature=bitcoin.ecdsa_sign(batch_header, signing_key), transactions=txns) batch_list = BatchList(batches=[batch]).SerializeToString() try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list) except: raise CliException('Unable to write to {}'.format(args.output))
def _do_identity_role_create(args): """Executes the 'role create' subcommand. Given a key file, a role name, and a policy name it generates a batch of sawtooth_identity transactions in a BatchList instance. The BatchList is either stored to a file or submitted to a validator, depending on the supplied CLI arguments. """ pubkey, signing_key = _read_signing_keys(args.key) txns = [_create_role_txn(pubkey, signing_key, args.name, args.policy)] batch = _create_batch(pubkey, signing_key, txns) batch_list = BatchList(batches=[batch]) if args.output is not None: try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException( 'Unable to write to batch file: {}'.format(str(e))) elif args.url is not None: rest_client = RestClient(args.url) rest_client.send_batches(batch_list) else: raise AssertionError('No target for create set.')
def do_block(args): """Runs the block list or block show command, printing output to the console Args: args: The parsed arguments sent to the command at runtime """ rest_client = RestClient(args.url, args.user) if args.subcommand == 'list': blocks = rest_client.list_blocks(limit=args.limit) keys = ('num', 'block_id', 'batches', 'txns', 'signer') headers = tuple(k.upper() if k != 'batches' else 'BATS' for k in keys) def parse_block_row(block): batches = block.get('batches', []) txns = [t for b in batches for t in b['transactions']] return (block['header'].get('block_num', 0), block['header_signature'], len(batches), len(txns), block['header']['signer_public_key']) if args.format == 'default': fmt.print_terminal_table(headers, blocks, parse_block_row) elif args.format == 'csv': fmt.print_csv(headers, blocks, parse_block_row) elif args.format == 'json' or args.format == 'yaml': data = [{k: d for k, d in zip(keys, parse_block_row(b))} for b in blocks] if args.format == 'yaml': fmt.print_yaml(data) elif args.format == 'json': fmt.print_json(data) else: raise AssertionError('Missing handler: {}'.format(args.format)) else: raise AssertionError('Missing handler: {}'.format(args.format)) if args.subcommand == 'show': output = rest_client.get_block(args.block_id) if args.key: if args.key in output: output = output[args.key] elif args.key in output['header']: output = output['header'][args.key] else: raise CliException( 'key "{}" not found in block or header'.format(args.key)) if args.format == 'yaml': fmt.print_yaml(output) elif args.format == 'json': fmt.print_json(output) else: raise AssertionError('Missing handler: {}'.format(args.format))
def _do_config_proposal_create(args): """Executes the 'proposal create' subcommand. Given a key file, and a series of key/value pairs, it generates batches of sawtooth_settings transactions in a BatchList instance. The BatchList is either stored to a file or submitted to a validator, depending on the supplied CLI arguments. """ settings = [s.split('=', 1) for s in args.setting] public_key, signing_key = _read_signing_keys(args.key) txns = [ _create_propose_txn(public_key, signing_key, setting) for setting in settings ] batch = _create_batch(public_key, signing_key, txns) batch_list = BatchList(batches=[batch]) if args.output is not None: try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException('Unable to write to batch file: {}'.format( str(e))) elif args.url is not None: rest_client = RestClient(args.url) rest_client.send_batches(batch_list) else: raise AssertionError('No target for create set.')
def do_peers(args): if args.peers_command == 'list': _do_peers_list(args) elif args.peers_command == 'graph': _do_peers_graph(args) else: raise CliException('Invalid command: {}'.format(args.subcommand))
def main(prog_name=os.path.basename(sys.argv[0]), args=None, with_loggers=True): parser = create_parser(prog_name) if args is None: args = sys.argv[1:] args = parser.parse_args(args) if with_loggers is True: if args.verbose is None: verbose_level = 0 else: verbose_level = args.verbose setup_loggers(verbose_level=verbose_level) if args.subcommand == 'proposal' and args.proposal_cmd == 'create': _do_config_proposal_create(args) elif args.subcommand == 'proposal' and args.proposal_cmd == 'list': _do_config_proposal_list(args) elif args.subcommand == 'proposal' and args.proposal_cmd == 'vote': _do_config_proposal_vote(args) elif args.subcommand == 'genesis': _do_config_genesis(args) else: raise CliException('"{}" is not a valid subcommand of "config"'.format( args.subcommand))
def do_cluster_logs(args): state = load_state() supported_types = 'docker', if state['Manage'] in supported_types: prefix = 'sawtooth-cluster-0' for node_name in args.node_names: try: node_num = node_name[len('validator-'):] processes = state['Processors'] + ['validator'] containers = [ '-'.join([prefix, proc, node_num]) for proc in processes ] for c in containers: print("Logs for container: " + c + "of node: " + node_name) cmd = ['docker', 'logs', c] handle = subprocess.Popen(cmd) while handle.returncode is None: handle.poll() except subprocess.CalledProcessError as cpe: raise CliException(str(cpe)) else: print("logs not implemented for {}".format(state['Manage']))
def do_cluster_status(args): state = load_state() node_controller = get_node_controller(state, args) node_command_generator = SimpleNodeCommandGenerator() vnm = ValidatorNetworkManager( node_controller=node_controller, node_command_generator=node_command_generator) if len(args.node_names) > 0: node_names = args.node_names node_superset = state['Nodes'] nodes = {} for node_name in args.node_names: try: nodes[node_name] = node_superset[node_name] except KeyError: raise CliException( "{} is not a known node name".format(node_name)) else: node_names = vnm.get_node_names() nodes = state['Nodes'] # Check expected status of nodes vs what is returned from vnm print("NodeName".ljust(15), "Status".ljust(10)) for node_name in nodes: if node_name not in node_names and \ (nodes[node_name]["Status"] == "Running" or nodes[node_name]["Status"] == "No Response"): print(node_name.ljust(15), "Not Running".ljust(10)) else: status = vnm.status(node_name) if status == "UNKNOWN": status = "Not Running" print(node_name.ljust(15), status.ljust(10))
def main(prog_name=os.path.basename(sys.argv[0]), args=None, with_loggers=True): parser = create_parser(prog_name) if args is None: args = sys.argv[1:] args = parser.parse_args(args) load_cli_config(args) if with_loggers is True: if args.verbose is None: verbose_level = 0 else: verbose_level = args.verbose setup_loggers(verbose_level=verbose_level) if args.command == 'keygen': do_keygen(args) elif args.command == 'block': do_block(args) elif args.command == 'batch': do_batch(args) elif args.command == 'transaction': do_transaction(args) elif args.command == 'state': do_state(args) elif args.command == 'identity': do_identity(args) elif args.command == 'settings': do_settings(args) elif args.command == 'peer': do_peer(args) else: raise CliException("invalid command: {}".format(args.command))
def _read_signing_keys(key_filename): """Reads the given file as a WIF formatted key. Args: key_filename: The filename where the key is stored. If None, defaults to the default key for the current user. Returns: tuple (str, str): the public and private key pair Raises: CliException: If unable to read the file. """ filename = key_filename if filename is None: filename = os.path.join(os.path.expanduser('~'), '.sawtooth', 'keys', getpass.getuser() + '.priv') try: with open(filename, 'r') as key_file: signing_key = key_file.read().strip() public_key = signing.generate_public_key(signing_key) return public_key, signing_key except IOError as e: raise CliException('Unable to read key file: {}'.format(str(e)))
def _do_identity_policy_list(args): rest_client = RestClient(args.url) state = rest_client.list_state(subtree=IDENTITY_NAMESPACE + _POLICY_PREFIX) head = state['head'] state_values = state['data'] printable_policies = [] for state_value in state_values: policies_list = PolicyList() decoded = b64decode(state_value['data']) policies_list.ParseFromString(decoded) for policy in policies_list.policies: printable_policies.append(policy) printable_policies.sort(key=lambda p: p.name) if args.format == 'default': tty_width = tty.width() for policy in printable_policies: # Set value width to the available terminal space, or the min width width = tty_width - len(policy.name) - 3 width = width if width > _MIN_PRINT_WIDTH else _MIN_PRINT_WIDTH value = "Entries:\n" for entry in policy.entries: entry_string = (" " * 4) + Policy.Type.Name(entry.type) + " " \ + entry.key value += (entry_string[:width] + '...' if len(entry_string) > width else entry_string) + "\n" print('{}: \n {}'.format(policy.name, value)) elif args.format == 'csv': try: writer = csv.writer(sys.stdout, quoting=csv.QUOTE_ALL) writer.writerow(['POLICY NAME', 'ENTRIES']) for policy in printable_policies: output = [policy.name] for entry in policy.entries: output.append( Policy.Type.Name(entry.type) + " " + entry.key) writer.writerow(output) except csv.Error: raise CliException('Error writing CSV') elif args.format == 'json' or args.format == 'yaml': output = {} for policy in printable_policies: value = "Entries: " for entry in policy.entries: entry_string = Policy.Type.Name(entry.type) + " " \ + entry.key value += entry_string + " " output[policy.name] = value policies_snapshot = {'head': head, 'policies': output} if args.format == 'json': print(json.dumps(policies_snapshot, indent=2, sort_keys=True)) else: print(yaml.dump(policies_snapshot, default_flow_style=False)[0:-1]) else: raise AssertionError('Unknown format {}'.format(args.format))
def do_batch(args): """Runs the batch list or batch show command, printing output to the console Args: args: The parsed arguments sent to the command at runtime """ rest_client = RestClient(args.url, args.user) if args.subcommand == 'list': batches = rest_client.list_batches() keys = ('batch_id', 'txns', 'signer') headers = tuple(k.upper() for k in keys) def parse_batch_row(batch): return (batch['header_signature'], len(batch.get('transactions', [])), batch['header']['signer_pubkey']) if args.format == 'default': fmt.print_terminal_table(headers, batches, parse_batch_row) elif args.format == 'csv': fmt.print_csv(headers, batches, parse_batch_row) elif args.format == 'json' or args.format == 'yaml': data = [{k: d for k, d in zip(keys, parse_batch_row(b))} for b in batches] if args.format == 'yaml': fmt.print_yaml(data) elif args.format == 'json': fmt.print_json(data) else: raise AssertionError('Missing handler: {}'.format(args.format)) else: raise AssertionError('Missing handler: {}'.format(args.format)) if args.subcommand == 'show': output = rest_client.get_batch(args.batch_id) if args.key: if args.key in output: output = output[args.key] elif args.key in output['header']: output = output['header'][args.key] else: raise CliException( 'key "{}" not found in batch or header'.format(args.key)) if args.format == 'yaml': fmt.print_yaml(output) elif args.format == 'json': fmt.print_json(output) else: raise AssertionError('Missing handler: {}'.format(args.format))
def _do_config_list(args): """Lists the current on-chain configuration values. """ rest_client = RestClient(args.url) state = rest_client.list_state(subtree=SETTINGS_NAMESPACE) prefix = args.filter head = state['head'] state_values = state['data'] printable_settings = [] proposals_address = _key_to_address('sawtooth.settings.vote.proposals') for state_value in state_values: if state_value['address'] == proposals_address: # This is completely internal setting and we won't list it here continue decoded = b64decode(state_value['data']) setting = Setting() setting.ParseFromString(decoded) for entry in setting.entries: if entry.key.startswith(prefix): printable_settings.append(entry) printable_settings.sort(key=lambda s: s.key) if args.format == 'default': tty_width = tty.width() for setting in printable_settings: # Set value width to the available terminal space, or the min width width = tty_width - len(setting.key) - 3 width = width if width > _MIN_PRINT_WIDTH else _MIN_PRINT_WIDTH value = (setting.value[:width] + '...' if len(setting.value) > width else setting.value) print('{}: {}'.format(setting.key, value)) elif args.format == 'csv': try: writer = csv.writer(sys.stdout, quoting=csv.QUOTE_ALL) writer.writerow(['KEY', 'VALUE']) for setting in printable_settings: writer.writerow([setting.key, setting.value]) except csv.Error: raise CliException('Error writing CSV') elif args.format == 'json' or args.format == 'yaml': settings_snapshot = { 'head': head, 'settings': {setting.key: setting.value for setting in printable_settings} } if args.format == 'json': print(json.dumps(settings_snapshot, indent=2, sort_keys=True)) else: print(yaml.dump(settings_snapshot, default_flow_style=False)[0:-1]) else: raise AssertionError('Unknown format {}'.format(args.format))
def _get(self, path, **queries): code, json_result = self._submit_request(self._base_url + path + self._format_queries(queries)) if code == 200: return json_result elif code == 404: return None else: raise CliException("({}): {}".format(code, json_result))
def do_admin(args): data_dir = ensure_directory('data', '/var/lib/sawtooth') if args.admin_cmd == 'genesis': do_genesis(args, data_dir) elif args.admin_cmd == 'keygen': do_keygen(args) else: raise CliException("invalid command: {}".format(args.admin_cmd))
def _submit_request(self, url, params=None, data=None, headers=None, method="GET"): """Submits the given request, and handles the errors appropriately. Args: url (str): the request to send. params (dict): params to be passed along to get/post data (bytes): the data to include in the request. headers (dict): the headers to include in the request. method (str): the method to use for the request, "POST" or "GET". Returns: tuple of (int, str): The response status code and the json parsed body, or the error message. Raises: `CliException`: If any issues occur with the URL. """ if headers is None: headers = {} if self._auth_header is not None: headers['Authorization'] = self._auth_header try: if method == 'POST': result = requests.post( url, params=params, data=data, headers=headers) elif method == 'GET': result = requests.get( url, params=params, data=data, headers=headers) result.raise_for_status() return (result.status_code, result.json()) except requests.exceptions.HTTPError as e: return (e.response.status_code, e.response.reason) except RemoteDisconnected as e: raise CliException(e) except (requests.exceptions.MissingSchema, requests.exceptions.InvalidURL) as e: raise CliException(e) except requests.exceptions.ConnectionError as e: raise CliException( ('Unable to connect to "{}": ' 'make sure URL is correct').format(self._base_url))
def do_state(args): """Runs the batch list or batch show command, printing output to the console Args: args: The parsed arguments sent to the command at runtime """ rest_client = RestClient(args.url, args.user) if args.subcommand == 'list': response = rest_client.list_state(args.subtree, args.head) leaves = response['data'] head = response['head'] keys = ('address', 'size', 'data') headers = tuple(k.upper() for k in keys) def parse_leaf_row(leaf, decode=True): decoded = b64decode(leaf['data']) return (leaf['address'], len(decoded), str(decoded) if decode else leaf['data']) if args.format == 'default': fmt.print_terminal_table(headers, leaves, parse_leaf_row) print('HEAD BLOCK: "{}"'.format(head)) elif args.format == 'csv': fmt.print_csv(headers, leaves, parse_leaf_row) print('(data for head block: "{}")'.format(head)) elif args.format == 'json' or args.format == 'yaml': state_data = { 'head': head, 'data': [ dict(zip(keys, parse_leaf_row(leaf, False))) for leaf in leaves ] } if args.format == 'yaml': fmt.print_yaml(state_data) elif args.format == 'json': fmt.print_json(state_data) else: raise AssertionError('Missing handler: {}'.format(args.format)) else: raise AssertionError('Missing handler: {}'.format(args.format)) if args.subcommand == 'show': output = rest_client.get_leaf(args.address, args.head) if output is not None: print('DATA: "{}"'.format(b64decode(output['data']))) print('HEAD: "{}"'.format(output['head'])) else: raise CliException('No data available at {}'.format(args.address))
def _get_data(self, path, **queries): url = self._base_url + path while url: code, json_result = self._submit_request( url, params=self._format_queries(queries), ) if code == 404: raise CliException( 'There is no resource with the identifier "{}"'. format(path.split('/')[-1])) elif code != 200: raise CliException("({}): {}".format(code, json_result)) for item in json_result.get('data', []): yield item url = json_result['paging'].get('next', None)
def _get_data(self, path, **queries): url = self._base_url + path params = self._format_queries(queries) while url: code, json_result = self._submit_request(url, params=params) if code == 404: raise CliException( '{}: There is no resource with the identifier "{}"'.format( self._base_url, path.split("/")[-1])) elif code != 200: raise CliException("{}: {} {}".format(self._base_url, code, json_result)) for item in json_result.get("data", []): yield item url = json_result["paging"].get("next", None)
def print_csv(headers, data_list, parse_row_fn): """Takes headers, data, and a row parsing function, and prints data to the console in a csv format. """ try: writer = csv.writer(sys.stdout) writer.writerow(headers) for data in data_list: writer.writerow(parse_row_fn(data)) except csv.Error as e: raise CliException('Error writing CSV: {}'.format(e))
def _get(self, path, queries=None): query_string = '?' + urlencode(queries) if queries else '' code, json_result = self._submit_request(self._base_url + path + query_string) if code == 200: return json_result elif code == 404: return None else: raise CliException("({}): {}".format(code, json_result))
def _get(self, path, **queries): code, json_result = self._submit_request( self._base_url + path, params=self._format_queries(queries), ) # concat any additional pages of data while code == 200 and 'next' in json_result.get('paging', {}): previous_data = json_result.get('data', []) code, json_result = self._submit_request( json_result['paging']['next']) json_result['data'] = previous_data + json_result.get('data', []) if code == 200: return json_result elif code == 404: raise CliException('There is no resource with the identifier "{}"'. format(path.split('/')[-1])) else: raise CliException("({}): {}".format(code, json_result))
def _get(self, path, **queries): code, json_result = self._submit_request( self._base_url + path, params=self._format_queries(queries)) # concat any additional pages of data while code == 200 and "next" in json_result.get("paging", {}): previous_data = json_result.get("data", []) code, json_result = self._submit_request( json_result["paging"]["next"]) json_result["data"] = previous_data + json_result.get("data", []) if code == 200: return json_result if code == 404: raise CliException( '{}: There is no resource with the identifier "{}"'.format( self._base_url, path.split("/")[-1])) raise CliException("{}: {} {}".format(self._base_url, code, json_result))
def _do_config_genesis(args): signer = _read_signer(args.key) public_key = signer.get_public_key().as_hex() authorized_keys = args.authorized_key if args.authorized_key else \ [public_key] if public_key not in authorized_keys: authorized_keys.append(public_key) txns = [] txns.append(_create_propose_txn( signer, ('sawtooth.settings.vote.authorized_keys', ','.join(authorized_keys)))) if args.approval_threshold is not None: if args.approval_threshold < 1: raise CliException('approval threshold must not be less than 1') if args.approval_threshold > len(authorized_keys): raise CliException( 'approval threshold must not be greater than the number of ' 'authorized keys') txns.append(_create_propose_txn( signer, ('sawtooth.settings.vote.approval_threshold', str(args.approval_threshold)))) batch = _create_batch(signer, txns) batch_list = BatchList(batches=[batch]) try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) print('Generated {}'.format(args.output)) except IOError as e: raise CliException( 'Unable to write to batch file: {}'.format(str(e)))
def _submit_request(self, url_or_request): """Submits the given request, and handles the errors appropriately. Args: url_or_request (str or `urlib.request.Request`): the request to send. Returns: `http.client.HTTPResponse`: The response from the request. Raises: `CliException`: If any issues occur when making the request or the URL is unavailable. """ try: return urllib.urlopen(url_or_request) except HTTPError as e: raise CliException('({}) {}'.format(e.code, e.msg)) except URLError as e: raise CliException( ('Unable to connect to "{}": ' 'make sure URL is correct').format(self._base_url))
def _do_identity_role_create(args): """Executes the 'role create' subcommand. Given a key file, a role name, and a policy name it generates a batch of sawtooth_identity transactions in a BatchList instance. The BatchList is either stored to a file or submitted to a validator, depending on the supplied CLI arguments. """ signer = _read_signer(args.key) txns = [_create_role_txn(signer, args.name, args.policy)] batch = _create_batch(signer, txns) batch_list = BatchList(batches=[batch]) if args.output is not None: try: with open(args.output, 'wb') as batch_file: batch_file.write(batch_list.SerializeToString()) except IOError as e: raise CliException( 'Unable to write to batch file: {}'.format(str(e))) elif args.url is not None: rest_client = RestClient(args.url) rest_client.send_batches(batch_list) if args.wait and args.wait > 0: batch_id = batch.header_signature wait_time = 0 start_time = time.time() while wait_time < args.wait: statuses = rest_client.get_statuses( [batch_id], args.wait - int(wait_time)) wait_time = time.time() - start_time if statuses[0]['status'] == 'COMMITTED': print( 'Role committed in {:.6} sec'.format(wait_time)) return # Wait a moment so as not to hammer the Rest Api time.sleep(0.2) print('Wait timed out! Role was not committed...') print('{:128.128} {}'.format( batch_id, statuses[0]['status'])) exit(1) else: raise AssertionError('No target for create set.')
def do_cluster(args): if args.cluster_command == 'start': do_cluster_start(args) elif args.cluster_command == 'status': do_cluster_status(args) elif args.cluster_command == 'stop': do_cluster_stop(args) elif args.cluster_command == 'extend': do_cluster_extend(args) elif args.cluster_command == 'logs': do_cluster_logs(args) else: raise CliException("invalid cluster command: {}".format( args.cluster_command))
def _read_signer(key_filename): """Reads the given file as a hex, or (as a fallback) a WIF formatted key. Args: key_filename: The filename where the key is stored. If None, defaults to the default key for the current user. Returns: Signer: the signer Raises: CliException: If unable to read the file. """ filename = key_filename if filename is None: filename = os.path.join(os.path.expanduser('~'), '.sawtooth', 'keys', getpass.getuser() + '.priv') try: with open(filename, 'r') as key_file: signing_key = key_file.read().strip() except IOError as e: raise CliException('Unable to read key file: {}'.format(str(e))) try: private_key = Secp256k1PrivateKey.from_hex(signing_key) except ParseError as e: try: private_key = Secp256k1PrivateKey.from_wif(signing_key) except ParseError: raise CliException('Unable to read key in file: {}'.format(str(e))) context = create_context('secp256k1') crypto_factory = CryptoFactory(context) return crypto_factory.new_signer(private_key)
def _do_identity_role_list(args): """Lists the current on-chain configuration values. """ rest_client = RestClient(args.url) state = rest_client.list_state(subtree=IDENTITY_NAMESPACE + _ROLE_PREFIX) head = state['head'] state_values = state['data'] printable_roles = [] for state_value in state_values: role_list = RoleList() decoded = b64decode(state_value['data']) role_list.ParseFromString(decoded) for role in role_list.roles: printable_roles.append(role) printable_roles.sort(key=lambda r: r.name) if args.format == 'default': tty_width = tty.width() for role in printable_roles: # Set value width to the available terminal space, or the min width width = tty_width - len(role.name) - 3 width = width if width > _MIN_PRINT_WIDTH else _MIN_PRINT_WIDTH value = (role.policy_name[:width] + '...' if len(role.policy_name) > width else role.policy_name) print('{}: {}'.format(role.name, value)) elif args.format == 'csv': try: writer = csv.writer(sys.stdout, quoting=csv.QUOTE_ALL) writer.writerow(['KEY', 'VALUE']) for role in printable_roles: writer.writerow([role.name, role.policy_name]) except csv.Error: raise CliException('Error writing CSV') elif args.format == 'json' or args.format == 'yaml': roles_snapshot = { 'head': head, 'roles': {role.name: role.policy_name for role in printable_roles} } if args.format == 'json': print(json.dumps(roles_snapshot, indent=2, sort_keys=True)) else: print(yaml.dump(roles_snapshot, default_flow_style=False)[0:-1]) else: raise AssertionError('Unknown format {}'.format(args.format))