node: Add --field option (#922)

This lets the user add fields to be printed in the table and
allows printing arbitrary data that wasn't possible to show before,
except when using `--json`.
This commit is contained in:
Marc Abramowitz
2017-03-16 16:49:53 -07:00
committed by tamarrow
parent 965c888dc4
commit faa645e79f
4 changed files with 75 additions and 7 deletions

View File

@@ -4,7 +4,7 @@ Description:
Usage:
dcos node --help
dcos node --info
dcos node [--json]
dcos node [--json | --field=<field>...]
dcos node --version
dcos node diagnostics (--list | --status | --cancel) [--json]
dcos node diagnostics create (<nodes>)...
@@ -52,6 +52,9 @@ Options:
Show DC/OS component logs.
--config-file=<path>
Path to SSH configuration file.
--field=<field>
Name of extra field to include in the output of `dcos node`.
Can be repeated multiple times to add several fields.
--filter=<filter>
Filter logs by field and value. Filter must be a string separated by colon.
For example: --filter _PID:0 --filter _UID:1.

View File

@@ -115,7 +115,7 @@ def _cmds():
cmds.Command(
hierarchy=['node'],
arg_keys=['--json'],
arg_keys=['--json', '--field'],
function=_list)
]
@@ -458,12 +458,15 @@ def _info():
return 0
def _list(json_):
def _list(json_, extra_field_names):
"""List DC/OS nodes
:param json_: If true, output json.
Otherwise, output a human readable table.
:type json_: bool
:param extra_field_names: List of additional field names to include in
table output
:type extra_field_names: [str]
:returns: process return code
:rtype: int
"""
@@ -473,7 +476,16 @@ def _list(json_):
if json_:
emitter.publish(slaves)
else:
table = tables.slave_table(slaves)
for extra_field_name in extra_field_names:
field_name = extra_field_name.split(':')[-1]
if len(slaves) > 0:
try:
tables._dotted_itemgetter(field_name)(slaves[0])
except KeyError:
emitter.publish(errors.DefaultError(
'Field "%s" is invalid.' % field_name))
return
table = tables.slave_table(slaves, extra_field_names)
output = six.text_type(table)
if output:
emitter.publish(output)

View File

@@ -1,5 +1,6 @@
import copy
import datetime
import operator
import posixpath
import textwrap
@@ -839,11 +840,13 @@ def auth_provider_table(providers):
return tb
def slave_table(slaves):
def slave_table(slaves, field_names=()):
"""Returns a PrettyTable representation of the provided DC/OS slaves
:param slaves: slaves to render. dicts from /mesos/state-summary
:type slaves: [dict]
:param field_names: Extra fields to add to the table
:type slaves: [str]
:rtype: PrettyTable
"""
@@ -853,10 +856,46 @@ def slave_table(slaves):
('ID', lambda s: s['id'])
])
tb = table(fields, slaves, sortby="HOSTNAME")
for field_name in field_names:
if field_name.upper() in fields:
continue
if ':' in field_name:
heading, field_name = field_name.split(':', 1)
else:
heading = field_name
fields[heading.upper()] = _dotted_itemgetter(field_name.lower())
sortby = list(fields.keys())[0]
tb = table(fields, slaves, sortby=sortby)
return tb
def _dotted_itemgetter(field_name):
"""Returns a func that gets the value in a nested dict where the
`field_name` is a dotted path to the key.
Example:
>>> from dcoscli.tables import _dotted_itemgetter
>>> d1 = {'a': {'b': {'c': 21}}}
>>> d2 = {'a': {'b': {'c': 22}}}
>>> func = _dotted_itemgetter('a.b.c')
>>> func(d1)
21
>>> func(d2)
22
:param field_name: dotted path to key in nested dict
:type field_name: str
:rtype: callable
"""
if '.' not in field_name:
return operator.itemgetter(field_name)
head, tail = field_name.split('.', 1)
return lambda d: _dotted_itemgetter(tail)(d[head])
def _format_unix_timestamp(ts):
""" Formats a unix timestamp in a `dcos task ls --long` format.
@@ -976,7 +1015,10 @@ def truncate_table(fields, objs, limits, **kwargs):
:type function: function
:rtype: PrettyTable
"""
result = str(function(obj))
try:
result = str(function(obj))
except KeyError:
result = 'N/A'
if (limits is not None and limits.get(key) is not None):
result = textwrap.\
shorten(result, width=limits.get(key), placeholder='...')

View File

@@ -45,6 +45,17 @@ def test_node_table():
assert len(stdout.decode('utf-8').split('\n')) > 2
def test_node_table_field_option():
returncode, stdout, stderr = exec_command(
['dcos', 'node', '--field=disk_used:used_resources.disk'])
assert returncode == 0
assert stderr == b''
lines = stdout.decode('utf-8').splitlines()
assert len(lines) > 2
assert lines[0].split() == ['HOSTNAME', 'IP', 'ID', 'DISK_USED']
def test_node_log_empty():
stderr = b"You must choose one of --leader or --mesos-id.\n"
assert_command(['dcos', 'node', 'log'], returncode=1, stderr=stderr)