mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge pull request #11337 from techdragon/fix-hightstate-failure-retcode
Fix for broken salt cmd return codes - issue #7013
This commit is contained in:
commit
4af873dcaf
@ -24,7 +24,8 @@ from salt.utils.verify import check_user, verify_env, verify_files
|
||||
from salt.exceptions import (
|
||||
SaltInvocationError,
|
||||
SaltClientError,
|
||||
EauthAuthenticationError
|
||||
EauthAuthenticationError,
|
||||
SaltSystemExit
|
||||
)
|
||||
|
||||
|
||||
@ -130,6 +131,7 @@ class SaltCMD(parsers.SaltCMDOptionParser):
|
||||
jid = local.cmd_async(**kwargs)
|
||||
print('Executed command with job ID: {0}'.format(jid))
|
||||
return
|
||||
retcodes = []
|
||||
try:
|
||||
# local will be None when there was an error
|
||||
if local:
|
||||
@ -143,21 +145,33 @@ class SaltCMD(parsers.SaltCMDOptionParser):
|
||||
if self.options.verbose:
|
||||
kwargs['verbose'] = True
|
||||
full_ret = local.cmd_full_return(**kwargs)
|
||||
ret, out = self._format_ret(full_ret)
|
||||
ret, out, retcode = self._format_ret(full_ret)
|
||||
self._output_ret(ret, out)
|
||||
elif self.config['fun'] == 'sys.doc':
|
||||
ret = {}
|
||||
out = ''
|
||||
for full_ret in local.cmd_cli(**kwargs):
|
||||
ret_, out = self._format_ret(full_ret)
|
||||
ret_, out, retcode = self._format_ret(full_ret)
|
||||
ret.update(ret_)
|
||||
self._output_ret(ret, out)
|
||||
else:
|
||||
if self.options.verbose:
|
||||
kwargs['verbose'] = True
|
||||
for full_ret in cmd_func(**kwargs):
|
||||
ret, out = self._format_ret(full_ret)
|
||||
ret, out, retcode = self._format_ret(full_ret)
|
||||
retcodes.append(retcode)
|
||||
self._output_ret(ret, out)
|
||||
|
||||
# NOTE: Return code is set here based on if all minions
|
||||
# returned 'ok' with a retcode of 0.
|
||||
# This is the final point before the 'salt' cmd returns,
|
||||
# which is why we set the retcode here.
|
||||
if retcodes.count(0) < len(retcodes):
|
||||
err = 'All Minions did not return a retcode of 0. One or more minions had a problem'
|
||||
# NOTE: This could probably be made more informative.
|
||||
# I chose 11 since its not in use.
|
||||
raise SaltSystemExit(code=11, msg=err)
|
||||
|
||||
except (SaltInvocationError, EauthAuthenticationError) as exc:
|
||||
ret = str(exc)
|
||||
out = ''
|
||||
@ -182,11 +196,14 @@ class SaltCMD(parsers.SaltCMDOptionParser):
|
||||
'''
|
||||
ret = {}
|
||||
out = ''
|
||||
retcode = 0
|
||||
for key, data in full_ret.items():
|
||||
ret[key] = data['ret']
|
||||
if 'out' in data:
|
||||
out = data['out']
|
||||
return ret, out
|
||||
if 'retcode' in data:
|
||||
retcode = data['retcode']
|
||||
return ret, out, retcode
|
||||
|
||||
def _print_docs(self, ret):
|
||||
'''
|
||||
|
@ -1089,6 +1089,7 @@ class LocalClient(object):
|
||||
'''
|
||||
Get the returns for the command line interface via the event system
|
||||
'''
|
||||
log.debug("entered - function get_cli_static_event_returns()")
|
||||
minions = set(minions)
|
||||
if verbose:
|
||||
msg = 'Executing job with jid {0}'.format(jid)
|
||||
@ -1163,6 +1164,7 @@ class LocalClient(object):
|
||||
'''
|
||||
Get the returns for the command line interface via the event system
|
||||
'''
|
||||
log.debug("func get_cli_event_returns()")
|
||||
if not isinstance(minions, set):
|
||||
if isinstance(minions, string_types):
|
||||
minions = set([minions])
|
||||
@ -1194,6 +1196,11 @@ class LocalClient(object):
|
||||
# Wait 0 == forever, use a minimum of 1s
|
||||
wait = max(1, time_left)
|
||||
raw = self.event.get_event(wait, jid)
|
||||
log.debug(
|
||||
"get_cli_event_returns()" +
|
||||
" called self.event.get_event()" +
|
||||
" and recieved : raw = " + str(raw)
|
||||
)
|
||||
if raw is not None:
|
||||
if 'minions' in raw.get('data', {}):
|
||||
minions.update(raw['data']['minions'])
|
||||
@ -1203,10 +1210,16 @@ class LocalClient(object):
|
||||
continue
|
||||
if 'return' not in raw:
|
||||
continue
|
||||
|
||||
found.add(raw.get('id'))
|
||||
ret = {raw['id']: {'ret': raw['return']}}
|
||||
if 'out' in raw:
|
||||
ret[raw['id']]['out'] = raw['out']
|
||||
if 'retcode' in raw:
|
||||
ret[raw['id']]['retcode'] = raw['retcode']
|
||||
log.trace("raw = " + str(raw))
|
||||
log.trace("ret = " + str(ret))
|
||||
log.trace("yeilding 'ret'")
|
||||
yield ret
|
||||
if len(found.intersection(minions)) >= len(minions):
|
||||
# All minions have returned, break out of the loop
|
||||
@ -1268,6 +1281,7 @@ class LocalClient(object):
|
||||
Gather the return data from the event system, break hard when timeout
|
||||
is reached.
|
||||
'''
|
||||
log.debug("entered - function get_event_iter_returns()")
|
||||
if timeout is None:
|
||||
timeout = self.opts['timeout']
|
||||
jid_dir = salt.utils.jid_dir(jid,
|
||||
|
@ -1017,6 +1017,7 @@ class Minion(MinionBase):
|
||||
if not os.path.isdir(jdir):
|
||||
os.makedirs(jdir)
|
||||
salt.utils.fopen(fn_, 'w+b').write(self.serial.dumps(ret))
|
||||
log.debug('ret_val = ' + str(ret_val))
|
||||
return ret_val
|
||||
|
||||
def _state_run(self):
|
||||
|
@ -39,6 +39,7 @@ def display_output(data, out, opts=None):
|
||||
display_data = get_printout('nested', opts)(data).rstrip()
|
||||
|
||||
output_filename = opts.get('output_file', None)
|
||||
log.debug("data = " + str(data))
|
||||
try:
|
||||
if output_filename is not None:
|
||||
with salt.utils.fopen(output_filename, 'a') as ofh:
|
||||
|
235
salt/states/test.py
Normal file
235
salt/states/test.py
Normal file
@ -0,0 +1,235 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Test States
|
||||
==================
|
||||
|
||||
Provide test case states that enable easy testing of things to do with
|
||||
state calls, e.g. running, calling, logging, output filtering etc.
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
always-passes:
|
||||
test.succeed_without_changes:
|
||||
- name: foo
|
||||
|
||||
always-fails:
|
||||
test.fail_without_changes:
|
||||
- name: foo
|
||||
|
||||
always-changes-and-succeeds:
|
||||
test.succeed_with_changes:
|
||||
- name: foo
|
||||
|
||||
always-changes-and-fails:
|
||||
test.fail_with_changes:
|
||||
- name: foo
|
||||
|
||||
my-custom-combo:
|
||||
test.configurable_test_state:
|
||||
- name: foo
|
||||
- changes: True
|
||||
- result: False
|
||||
- comment: bar.baz
|
||||
|
||||
'''
|
||||
|
||||
# Import Python libs
|
||||
import logging
|
||||
import random
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def succeed_without_changes(name):
|
||||
'''
|
||||
Returns successful.
|
||||
|
||||
name
|
||||
A unique string.
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': 'This is just a test, nothing actually happened'
|
||||
}
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = (
|
||||
'Yo dawg I heard you like tests,'
|
||||
' so I put tests in your tests,'
|
||||
' so you can test while you test.'
|
||||
)
|
||||
return ret
|
||||
|
||||
|
||||
def fail_without_changes(name):
|
||||
'''
|
||||
Returns failure.
|
||||
|
||||
name:
|
||||
A unique string.
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': 'This is just a test, nothing actually happened'
|
||||
}
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = (
|
||||
'Yo dawg I heard you like tests,'
|
||||
' so I put tests in your tests,'
|
||||
' so you can test while you test.'
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def succeed_with_changes(name):
|
||||
'''
|
||||
Returns successful and changes is not empty
|
||||
|
||||
name:
|
||||
A unique string.
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': True,
|
||||
'comment': 'This is just a test, nothing actually happened'
|
||||
}
|
||||
|
||||
# Following the docs as written here
|
||||
# http://docs.saltstack.com/ref/states/writing.html#return-data
|
||||
ret['changes'] = {
|
||||
'testing': {
|
||||
'old': 'Nothing has changed yet',
|
||||
'new': 'Were pretending really hard that we changed something'
|
||||
}
|
||||
}
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = (
|
||||
'Yo dawg I heard you like tests,'
|
||||
' so I put tests in your tests,'
|
||||
' so you can test while you test.'
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def fail_with_changes(name):
|
||||
'''
|
||||
Returns failure and changes is not empty.
|
||||
|
||||
name:
|
||||
A unique string.
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': 'This is just a test, nothing actually happened'
|
||||
}
|
||||
|
||||
# Following the docs as written here
|
||||
# http://docs.saltstack.com/ref/states/writing.html#return-data
|
||||
ret['changes'] = {
|
||||
'testing': {
|
||||
'old': 'Nothing has changed yet',
|
||||
'new': 'Were pretending really hard that we changed something'
|
||||
}
|
||||
}
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = (
|
||||
'Yo dawg I heard you like tests,'
|
||||
' so I put tests in your tests,'
|
||||
' so you can test while you test.'
|
||||
)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def configurable_test_state(name, changes=True, result=True, comment=''):
|
||||
'''
|
||||
A configurable test state which determines its output based on the inputs.
|
||||
|
||||
name:
|
||||
A unique string.
|
||||
changes:
|
||||
Do we return anything in the changes field?
|
||||
Accepts True, False, and 'Random'
|
||||
Default is True
|
||||
result:
|
||||
Do we return sucessfuly or not?
|
||||
Accepts True, False, and 'Random'
|
||||
Default is True
|
||||
comment:
|
||||
String to fill the comment field with.
|
||||
Default is ''
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': comment
|
||||
}
|
||||
|
||||
# E8712 is disabled because this code is a LOT cleaner if we allow it.
|
||||
if changes == "Random":
|
||||
if random.choice([True, False]):
|
||||
# Following the docs as written here
|
||||
# http://docs.saltstack.com/ref/states/writing.html#return-data
|
||||
ret['changes'] = {
|
||||
'testing': {
|
||||
'old': 'Nothing has changed yet',
|
||||
'new': 'Were pretending really hard that we changed something'
|
||||
}
|
||||
}
|
||||
elif changes == True: # pylint: disable=E8712
|
||||
# If changes is True we place our dummy change dictionary into it.
|
||||
# Following the docs as written here
|
||||
# http://docs.saltstack.com/ref/states/writing.html#return-data
|
||||
ret['changes'] = {
|
||||
'testing': {
|
||||
'old': 'Nothing has changed yet',
|
||||
'new': 'Were pretending really hard that we changed something'
|
||||
}
|
||||
}
|
||||
elif changes == False: # pylint: disable=E8712
|
||||
ret['changes'] = {}
|
||||
else:
|
||||
err = ('You have specified the state option \'Changes\' with'
|
||||
' invalid arguments. It must be either '
|
||||
' \'True\', \'False\', or \'Random\'')
|
||||
raise SaltInvocationError(err)
|
||||
|
||||
if result == 'Random':
|
||||
# since result is a boolean, if its random we just set it here,
|
||||
ret['result'] = random.choice([True, False])
|
||||
elif result == True: # pylint: disable=E8712
|
||||
ret['result'] = True
|
||||
elif result == False: # pylint: disable=E8712
|
||||
ret['result'] = False
|
||||
else:
|
||||
err = ('You have specified the state option \'Result\' with'
|
||||
' invalid arguments. It must be either '
|
||||
' \'True\', \'False\', or \'Random\'')
|
||||
raise SaltInvocationError(err)
|
||||
|
||||
if __opts__['test']:
|
||||
ret['result'] = None
|
||||
ret['comment'] = (
|
||||
'Yo dawg I heard you like tests,'
|
||||
' so I put tests in your tests,'
|
||||
' so you can test while you test.'
|
||||
)
|
||||
|
||||
return ret
|
1
salt/templates/__init__.py
Normal file
1
salt/templates/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
@ -1360,28 +1360,14 @@ def check_state_result(running):
|
||||
return False
|
||||
if not running:
|
||||
return False
|
||||
for host in running:
|
||||
if not isinstance(running[host], dict):
|
||||
return False
|
||||
|
||||
if host.find('_|-') >= 3:
|
||||
# This is a single ret, no host associated
|
||||
rets = running[host]
|
||||
for state in running.keys():
|
||||
if type(running[str(state)]) is not list:
|
||||
if not running[str(state)]['result']:
|
||||
return False
|
||||
else:
|
||||
rets = running[host].values()
|
||||
|
||||
if isinstance(rets, dict) and 'result' in rets:
|
||||
if rets['result'] is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
for ret in rets:
|
||||
if not isinstance(ret, dict):
|
||||
return False
|
||||
if 'result' not in ret:
|
||||
return False
|
||||
if ret['result'] is False:
|
||||
return False
|
||||
# return false when hosts return a list instead of a dict
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
|
@ -282,6 +282,7 @@ class SaltEvent(object):
|
||||
wait = timeout_at - time.time()
|
||||
continue
|
||||
|
||||
log.debug("get_event() recieved = " + str(ret))
|
||||
if full:
|
||||
return ret
|
||||
return ret['data']
|
||||
@ -331,6 +332,7 @@ class SaltEvent(object):
|
||||
else: # new style longer than 20 chars
|
||||
tagend = TAGEND
|
||||
|
||||
log.debug("Sending event - data = " + str(data))
|
||||
event = '{0}{1}{2}'.format(tag, tagend, self.serial.dumps(data))
|
||||
try:
|
||||
self.push.send(event)
|
||||
|
@ -224,15 +224,11 @@ class UtilsTestCase(TestCase):
|
||||
self.assertDictEqual(utils.clean_kwargs(__foo_bar='gwar'), {'__foo_bar': 'gwar'})
|
||||
|
||||
def test_check_state_result(self):
|
||||
self.assertFalse(utils.check_state_result([]), "Failed to handle an invalid data type.")
|
||||
self.assertFalse(utils.check_state_result(None), "Failed to handle None as an invalid data type.")
|
||||
self.assertFalse(utils.check_state_result({'host1': []}),
|
||||
"Failed to handle an invalid data structure for a host")
|
||||
self.assertFalse(utils.check_state_result([]), "Failed to handle an invalid data type.")
|
||||
self.assertFalse(utils.check_state_result({}), "Failed to handle an empty dictionary.")
|
||||
self.assertFalse(utils.check_state_result({'host1': []}), "Failed to handle an invalid host data structure.")
|
||||
|
||||
self.assertTrue(utils.check_state_result({' _|-': {}}))
|
||||
|
||||
test_valid_state = {'host1': {'test_state': {'result': 'We have liftoff!'}}}
|
||||
self.assertTrue(utils.check_state_result(test_valid_state))
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user