awx/awxkit/awxkit/cli/format.py
Rigel Di Scala 579604d2c6 Allow YAML as a CLI import format
This changset allows the import of YAML formatted resources. The CLI
user can indicate which format to use with the `-f, --format` option.
The CLI help text has been amended to reflect the new feature.

The AWX CLI `export` subcommand offers the option of formatting the output
as YAML or JSON, so it makes sense that the `import` subcommand reflects
this.

A simple test is also provided. In order to ease the task of testing
commands that import resources by reading the stdin, the CLI has been
extended to allow specifying an alternative file descriptor for stdin,
similarly to stdout and stderr.
2020-08-10 23:43:53 +02:00

193 lines
5.2 KiB
Python

import locale
import json
from distutils.util import strtobool
import yaml
from awxkit.cli.utils import colored
def add_authentication_arguments(parser, env):
auth = parser.add_argument_group('authentication')
auth.add_argument(
'--conf.host',
default=env.get('TOWER_HOST', 'https://127.0.0.1:443'),
metavar='https://example.awx.org',
)
auth.add_argument(
'--conf.token',
default=env.get('TOWER_OAUTH_TOKEN', env.get('TOWER_TOKEN', '')),
help='an OAuth2.0 token (get one by using `awx login`)',
metavar='TEXT',
)
auth.add_argument(
'--conf.username',
default=env.get('TOWER_USERNAME', 'admin'),
metavar='TEXT',
)
auth.add_argument(
'--conf.password',
default=env.get('TOWER_PASSWORD', 'password'),
metavar='TEXT',
)
auth.add_argument(
'-k',
'--conf.insecure',
help='Allow insecure server connections when using SSL',
default=not strtobool(env.get('TOWER_VERIFY_SSL', 'True')),
action='store_true',
)
def add_output_formatting_arguments(parser, env):
formatting = parser.add_argument_group('input/output formatting')
formatting.add_argument(
'-f',
'--conf.format',
dest='conf.format',
choices=FORMATTERS.keys(),
default=env.get('TOWER_FORMAT', 'json'),
help=(
'specify a format for the input and output'
),
)
formatting.add_argument(
'--filter',
dest='conf.filter',
default='.',
metavar='TEXT',
help=(
'specify an output filter (only valid with jq or human format)'
),
)
formatting.add_argument(
'--conf.color',
metavar='BOOLEAN',
help='Display colorized output. Defaults to True',
default=env.get('TOWER_COLOR', 't'), type=strtobool,
)
formatting.add_argument(
'-v',
'--verbose',
dest='conf.verbose',
help='print debug-level logs, including requests made',
default=strtobool(env.get('TOWER_VERBOSE', 'f')),
action="store_true"
)
def format_response(response, fmt='json', filter='.', changed=False):
if response is None:
return # HTTP 204
if isinstance(response, str):
return response
if 'results' in response.__dict__:
results = getattr(response, 'results')
else:
results = [response]
for result in results:
if 'related' in result.json:
result.json.pop('related')
formatted = FORMATTERS[fmt](response.json, filter)
if changed:
formatted = colored(formatted, 'green')
return formatted
def format_jq(output, fmt):
try:
import jq
except ImportError:
if fmt == '.':
return output
raise ImportError(
'To use `-f jq`, you must install the optional jq dependency.\n'
'`pip install jq`\n',
'Note that some platforms may require additional programs to '
'build jq from source (like `libtool`).\n'
'See https://pypi.org/project/jq/ for instructions.'
)
results = []
for x in jq.jq(fmt).transform(output, multiple_output=True):
if x not in (None, ''):
if isinstance(x, str):
results.append(x)
else:
results.append(json.dumps(x))
return '\n'.join(results)
def format_json(output, fmt):
return json.dumps(output, indent=5)
def format_yaml(output, fmt):
output = json.loads(json.dumps(output))
return yaml.safe_dump(
output,
default_flow_style=False,
allow_unicode=True
)
def format_human(output, fmt):
lines = []
if fmt == '.':
fmt = 'id,name'
column_names = [col.strip() for col in fmt.split(',')]
if 'count' in output:
output = output['results']
else:
output = [output]
if fmt == '*' and len(output):
column_names = list(output[0].keys())
for k in ('summary_fields', 'related'):
if k in column_names:
column_names.remove(k)
table = [column_names]
table.extend([
[record.get(col, '') for col in column_names]
for record in output
])
col_paddings = []
def format_num(v):
try:
return locale.format("%.*f", (0, int(v)), True)
except (ValueError, TypeError):
if isinstance(v, (list, dict)):
return json.dumps(v)
if v is None:
return ''
return v
# calculate the max width of each column
for i, _ in enumerate(column_names):
max_width = max([len(format_num(row[i])) for row in table])
col_paddings.append(max_width)
# insert a row of === header lines
table.insert(1, ['=' * i for i in col_paddings])
# print each row of the table data, justified based on col_paddings
for row in table:
line = ''
for i, value in enumerate(row):
line += format_num(value).ljust(col_paddings[i] + 1)
lines.append(line)
return '\n'.join(lines)
FORMATTERS = {
'json': format_json,
'yaml': format_yaml,
'jq': format_jq,
'human': format_human
}