Merge pull request #46022 from bloomberg/nested-orch-highstate

salt.outputter.highstate: recursion for orch
This commit is contained in:
Mike Place 2018-04-11 12:22:14 -06:00 committed by GitHub
commit 4e44fee506
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 107 additions and 9 deletions

View File

@ -105,6 +105,7 @@ Example output with no special settings in configuration files:
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import pprint
import re
import textwrap
# Import salt libs
@ -141,8 +142,9 @@ def output(data, **kwargs): # pylint: disable=unused-argument
if 'data' in data:
data = data.pop('data')
indent_level = kwargs.get('indent_level', 1)
ret = [
_format_host(host, hostdata)[0]
_format_host(host, hostdata, indent_level=indent_level)[0]
for host, hostdata in six.iteritems(data)
]
if ret:
@ -155,7 +157,11 @@ def output(data, **kwargs): # pylint: disable=unused-argument
return ''
def _format_host(host, data):
def _format_host(host, data, indent_level=1):
'''
Main highstate formatter. can be called recursively if a nested highstate
contains other highstates (ie in an orchestration)
'''
host = salt.utils.data.decode(host)
colors = salt.utils.color.get_colors(
@ -217,15 +223,17 @@ def _format_host(host, data):
try:
rdurations.append(float(rduration))
except ValueError:
log.error('Cannot parse a float from duration {0}'
.format(ret.get('duration', 0)))
log.error('Cannot parse a float from duration %s', ret.get('duration', 0))
tcolor = colors['GREEN']
orchestration = ret.get('__orchestration__', False)
schanged, ctext = _format_changes(ret['changes'], orchestration)
if not ctext and 'pchanges' in ret:
schanged, ctext = _format_changes(ret['pchanges'], orchestration)
nchanges += 1 if schanged else 0
if ret.get('name') in ['state.orch', 'state.orchestrate', 'state.sls']:
nested = output(ret['changes']['return'], indent_level=indent_level+1)
ctext = re.sub('^', ' ' * 14 * indent_level, '\n'+nested, flags=re.MULTILINE)
schanged = True
nchanges += 1
else:
schanged, ctext = _format_changes(ret['changes'])
nchanges += 1 if schanged else 0
# Skip this state if it was successful & diff output was requested
if __opts__.get('state_output_diff', False) and \

View File

@ -107,3 +107,93 @@ class JsonTestCase(TestCase, LoaderModuleMockMixin):
self.assertIn('Succeeded: 1 (changed=1)', ret)
self.assertIn('Failed: 0', ret)
self.assertIn('Total states run: 1', ret)
# this should all pass the above tests
class JsonNestedTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for nested salt.output.highstate (ie orchestrations calling other orchs)
'''
def setup_loader_modules(self):
return {
highstate: {
'__opts__': {
'extension_modules': '',
'color': False,
}
}
}
def setUp(self):
self.data = {
'outputter': 'highstate',
'data': {
'local_master': {
'salt_|-nested_|-state.orchestrate_|-runner': {
'comment': 'Runner function \'state.orchestrate\' executed.',
'name': 'state.orchestrate',
'__orchestration__': True,
'start_time': '09:22:53.158742',
'result': True,
'duration': 980.694,
'__run_num__': 0,
'__jid__': '20180326092253538853',
'__sls__': 'orch.test.nested',
'changes': {
'return': {
'outputter': 'highstate',
'data': {
'local_master': {
'test_|-always-passes-with-changes_|-oinaosf_|-succeed_with_changes': {
'comment': 'Success!',
'name': 'oinaosf',
'start_time': '09:22:54.128415',
'result': True,
'duration': 0.437,
'__run_num__': 0,
'__sls__': 'orch.test.changes',
'changes': {
'testing': {
'new': 'Something pretended to change',
'old': 'Unchanged'
}
},
'__id__': 'always-passes-with-changes'
},
'test_|-always-passes_|-fasdfasddfasdfoo_|-succeed_without_changes': {
'comment': 'Success!',
'name': 'fasdfasddfasdfoo',
'start_time': '09:22:54.128986',
'result': True,
'duration': 0.25,
'__run_num__': 1,
'__sls__': 'orch.test.changes',
'changes': {},
'__id__': 'always-passes'
}
}
},
'retcode': 0
}
},
'__id__': 'nested'
}
}
},
'retcode': 0
}
self.addCleanup(delattr, self, 'data')
def test_nested_output(self):
ret = highstate.output(self.data)
self.assertIn('Succeeded: 1 (changed=1)', ret)
self.assertIn('Failed: 0', ret)
self.assertIn('Total states run: 1', ret)
# the whitespace is relevant in this case, it is testing that it is nested
self.assertIn(' ID: always-passes-with-changes', ret)
self.assertIn(' Started: 09:22:54.128415', ret)
self.assertIn(' Succeeded: 2 (changed=1)', ret)
self.assertIn(' Failed: 0', ret)
self.assertIn(' Total states run: 2', ret)