Merge pull request #44533 from cloudflare/nested-colors

Nested outputter colors depending on the function retcode
This commit is contained in:
Nicole Thomas 2017-12-15 12:28:02 -05:00 committed by GitHub
commit 81ded2aaba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 166 additions and 15 deletions

View file

@ -145,8 +145,10 @@ class BaseCaller(object):
print_ret = ret.get('return', {})
salt.output.display_output(
{'local': print_ret},
out,
self.opts)
out=out,
opts=self.opts,
_retcode=ret.get('retcode', 0))
# _retcode will be available in the kwargs of the outputter function
if self.opts.get('retcode_passthrough', False):
sys.exit(ret['retcode'])
except SaltInvocationError as err:
@ -372,8 +374,10 @@ class RAETCaller(BaseCaller):
self.process.terminate()
salt.output.display_output(
{'local': print_ret},
ret.get('out', 'nested'),
self.opts)
out=ret.get('out', 'nested'),
opts=self.opts,
_retcode=ret.get('retcode', 0))
# _retcode will be available in the kwargs of the outputter function
if self.opts.get('retcode_passthrough', False):
sys.exit(ret['retcode'])

View file

@ -181,7 +181,7 @@ class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
for full_ret in self.local_client.cmd_cli(**kwargs):
ret_, out, retcode = self._format_ret(full_ret)
ret.update(ret_)
self._output_ret(ret, out)
self._output_ret(ret, out, retcode=retcode)
else:
if self.options.verbose:
kwargs['verbose'] = True
@ -190,7 +190,7 @@ class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
try:
ret_, out, retcode = self._format_ret(full_ret)
retcodes.append(retcode)
self._output_ret(ret_, out)
self._output_ret(ret_, out, retcode=retcode)
ret.update(full_ret)
except KeyError:
errors.append(full_ret)
@ -212,7 +212,7 @@ class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
except (SaltInvocationError, EauthAuthenticationError, SaltClientError) as exc:
ret = str(exc)
self._output_ret(ret, '')
self._output_ret(ret, '', retcode=1)
def _preview_target(self):
'''
@ -352,7 +352,7 @@ class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
'Requested job was still run but output cannot be displayed.\n')
salt.output.update_progress(self.config, progress, self.progress_bar, out)
def _output_ret(self, ret, out):
def _output_ret(self, ret, out, retcode=0):
'''
Print the output from a single return to the terminal
'''
@ -362,7 +362,10 @@ class SaltCMD(salt.utils.parsers.SaltCMDOptionParser):
self._print_docs(ret)
else:
# Determine the proper output method and run it
salt.output.display_output(ret, out, self.config)
salt.output.display_output(ret,
out=out,
opts=self.config,
_retcode=retcode)
if not ret:
sys.stderr.write('ERROR: No return received\n')
sys.exit(2)

View file

@ -39,7 +39,7 @@ class NestDisplay(object):
'''
Manage the nested display contents
'''
def __init__(self):
def __init__(self, retcode=0):
self.__dict__.update(
salt.utils.color.get_colors(
__opts__.get('color'),
@ -47,6 +47,7 @@ class NestDisplay(object):
)
)
self.strip_colors = __opts__.get('strip_colors', True)
self.retcode = retcode
def ustring(self,
indent,
@ -109,12 +110,15 @@ class NestDisplay(object):
)
first_line = False
elif isinstance(ret, (list, tuple)):
color = self.GREEN
if self.retcode != 0:
color = self.RED
for ind in ret:
if isinstance(ind, (list, tuple, dict)):
out.append(
self.ustring(
indent,
self.GREEN,
color,
'|_'
)
)
@ -124,10 +128,13 @@ class NestDisplay(object):
self.display(ind, indent, '- ', out)
elif isinstance(ret, dict):
if indent:
color = self.CYAN
if self.retcode != 0:
color = self.RED
out.append(
self.ustring(
indent,
self.CYAN,
color,
'----------'
)
)
@ -137,13 +144,15 @@ class NestDisplay(object):
keys = ret.keys()
else:
keys = sorted(ret)
color = self.CYAN
if self.retcode != 0:
color = self.RED
for key in keys:
val = ret[key]
out.append(
self.ustring(
indent,
self.CYAN,
color,
key,
suffix=':',
prefix=prefix
@ -158,7 +167,8 @@ def output(ret, **kwargs):
Display ret data
'''
# Prefer kwargs before opts
retcode = kwargs.get('_retcode', 0)
base_indent = kwargs.get('nested_indent', 0) \
or __opts__.get('nested_indent', 0)
nest = NestDisplay()
nest = NestDisplay(retcode=retcode)
return '\n'.join(nest.display(ret, base_indent, '', []))

View file

@ -0,0 +1,134 @@
# -*- coding: utf-8 -*-
'''
Unit tests for the Nested outputter
'''
# Import Python Libs
from __future__ import absolute_import
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase
# Import Salt Libs
import salt.output.nested as nested
class NestedOutputterTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.output.nested
'''
def setup_loader_modules(self):
return {
nested: {
'__opts__': {
'extension_modules': '',
'color': True
}
}
}
def setUp(self):
# The example from the documentation for the test.arg execution function
# Same function from the highstate outputter
self.data = {
'local': {
'args': (1, 'two', 3.1),
'kwargs': {
u'__pub_pid': 25938,
'wow': {
'a': 1,
'b': 'hello'
},
u'__pub_fun': 'test.arg',
u'__pub_jid': '20171207105927331329',
u'__pub_tgt': 'salt-call',
'txt': 'hello'
}
}
}
self.addCleanup(delattr, self, 'data')
def test_output_with_colors(self):
# Should look exacly like that, with the default color scheme:
#
# local:
# ----------
# args:
# - 1
# - two
# - 3.1
# kwargs:
# ----------
# __pub_fun:
# test.arg
# __pub_jid:
# 20171207105927331329
# __pub_pid:
# 25938
# __pub_tgt:
# salt-call
# txt:
# hello
# wow:
# ----------
# a:
# 1
# b:
# hello
expected_output_str = (
'\x1b[0;36mlocal\x1b[0;0m:\n \x1b[0;36m----------\x1b[0;0m\n \x1b[0;36margs\x1b[0;0m:\n'
' \x1b[0;1;33m- 1\x1b[0;0m\n \x1b[0;32m- two\x1b[0;0m\n \x1b[0;1;33m- 3.1\x1b[0;0m\n'
' \x1b[0;36mkwargs\x1b[0;0m:\n \x1b[0;36m----------\x1b[0;0m\n'
' \x1b[0;36m__pub_fun\x1b[0;0m:\n \x1b[0;32mtest.arg\x1b[0;0m\n'
' \x1b[0;36m__pub_jid\x1b[0;0m:\n \x1b[0;32m20171207105927331329\x1b[0;0m\n'
' \x1b[0;36m__pub_pid\x1b[0;0m:\n \x1b[0;1;33m25938\x1b[0;0m\n'
' \x1b[0;36m__pub_tgt\x1b[0;0m:\n \x1b[0;32msalt-call\x1b[0;0m\n'
' \x1b[0;36mtxt\x1b[0;0m:\n \x1b[0;32mhello\x1b[0;0m\n \x1b[0;36mwow\x1b[0;0m:\n'
' \x1b[0;36m----------\x1b[0;0m\n \x1b[0;36ma\x1b[0;0m:\n'
' \x1b[0;1;33m1\x1b[0;0m\n \x1b[0;36mb\x1b[0;0m:\n'
' \x1b[0;32mhello\x1b[0;0m'
)
ret = nested.output(self.data)
self.assertEqual(ret, expected_output_str)
def test_output_with_retcode(self):
# Non-zero retcode should change the colors
# Same output format as above, just different colors
expected_output_str = (
'\x1b[0;31mlocal\x1b[0;0m:\n \x1b[0;31m----------\x1b[0;0m\n \x1b[0;31margs\x1b[0;0m:\n'
' \x1b[0;1;33m- 1\x1b[0;0m\n \x1b[0;32m- two\x1b[0;0m\n \x1b[0;1;33m- 3.1\x1b[0;0m\n'
' \x1b[0;31mkwargs\x1b[0;0m:\n \x1b[0;31m----------\x1b[0;0m\n'
' \x1b[0;31m__pub_fun\x1b[0;0m:\n \x1b[0;32mtest.arg\x1b[0;0m\n'
' \x1b[0;31m__pub_jid\x1b[0;0m:\n \x1b[0;32m20171207105927331329\x1b[0;0m\n'
' \x1b[0;31m__pub_pid\x1b[0;0m:\n \x1b[0;1;33m25938\x1b[0;0m\n'
' \x1b[0;31m__pub_tgt\x1b[0;0m:\n \x1b[0;32msalt-call\x1b[0;0m\n'
' \x1b[0;31mtxt\x1b[0;0m:\n \x1b[0;32mhello\x1b[0;0m\n \x1b[0;31mwow\x1b[0;0m:\n'
' \x1b[0;31m----------\x1b[0;0m\n \x1b[0;31ma\x1b[0;0m:\n'
' \x1b[0;1;33m1\x1b[0;0m\n \x1b[0;31mb\x1b[0;0m:\n'
' \x1b[0;32mhello\x1b[0;0m'
)
# You can notice that in test_output_with_colors the color code is \x1b[0;36m, i.e., GREEN,
# while here the color code is \x1b[0;31m, i.e., RED (failure)
ret = nested.output(self.data, _retcode=1)
self.assertEqual(ret, expected_output_str)
def test_output_with_indent(self):
# Everything must be indented by exactly two spaces
# (using nested_indent=2 sent to nested.output as kwarg)
expected_output_str = (
' \x1b[0;36m----------\x1b[0;0m\n \x1b[0;36mlocal\x1b[0;0m:\n \x1b[0;36m----------\x1b[0;0m\n'
' \x1b[0;36margs\x1b[0;0m:\n \x1b[0;1;33m- 1\x1b[0;0m\n \x1b[0;32m- two\x1b[0;0m\n'
' \x1b[0;1;33m- 3.1\x1b[0;0m\n \x1b[0;36mkwargs\x1b[0;0m:\n'
' \x1b[0;36m----------\x1b[0;0m\n \x1b[0;36m__pub_fun\x1b[0;0m:\n'
' \x1b[0;32mtest.arg\x1b[0;0m\n \x1b[0;36m__pub_jid\x1b[0;0m:\n'
' \x1b[0;32m20171207105927331329\x1b[0;0m\n \x1b[0;36m__pub_pid\x1b[0;0m:\n'
' \x1b[0;1;33m25938\x1b[0;0m\n \x1b[0;36m__pub_tgt\x1b[0;0m:\n'
' \x1b[0;32msalt-call\x1b[0;0m\n \x1b[0;36mtxt\x1b[0;0m:\n'
' \x1b[0;32mhello\x1b[0;0m\n \x1b[0;36mwow\x1b[0;0m:\n'
' \x1b[0;36m----------\x1b[0;0m\n \x1b[0;36ma\x1b[0;0m:\n'
' \x1b[0;1;33m1\x1b[0;0m\n \x1b[0;36mb\x1b[0;0m:\n'
' \x1b[0;32mhello\x1b[0;0m'
)
ret = nested.output(self.data, nested_indent=2)
self.assertEqual(ret, expected_output_str)