The SaltStack developer docs are missing information about exceptions that can be thrown and how the state system and the CLI behaves when they are thrown.
Thankfully this is easy to test and is actually a pretty good development exercise. So, let’s write an execution module, a state module, and an sls file, then run them to determine the behavior.
A simple example execution module
modules/modules/example.py:
from salt.exceptions import CommandExecutionError
def example(name):
if name == 'succeed':
return True
elif name == 'fail':
return False
else:
raise CommandExecutionError('Example function failed due to unexpected input.')
A simple example state module
modules/states/example.py:
def present(name):
ret = {'name': name, 'result': True, 'comment': '', 'changes': {}}
if not __salt__['example.example'](name):
ret['result'] = False
return ret
In the above we’re calling the execution module via the salt dunder dictionary, which is a special convenience method for calling execution modules without needing to know their inclusion path.
Also in the above we’re using a special return format that Salt expects to receive when calling state modules.
A simple example sls file
example.sls:
Test fail behavior:
example.present:
- name: fail
Test error behavior:
example.present:
- name: error
Test succeed behavior:
example.present:
- name: succeed
Testing the behavior
State run behavior
# salt-call --retcode-passthrough --file-root . -m modules state.sls example
local:
----------
ID: Test fail behavior
Function: example.present
Name: fail
Result: False
Comment:
Started: 05:22:16.220802
Duration: 0 ms
Changes:
----------
ID: Test error behavior
Function: example.present
Name: error
Result: False
Comment: An exception occurred in this state: Traceback (most recent call last):
File "/srv/salt/venv/src/salt/salt/state.py", line 1518, in call
**cdata['kwargs'])
File "/root/modules/states/example.py", line 3, in present
if not __salt__['example.example'](name):
File "/root/modules/modules/example.py", line 9, in example
raise CommandExecutionError('Example function failed due to unexpected input.')
CommandExecutionError: Example function failed due to unexpected input.
Started: 05:22:16.221730
Duration: 1 ms
Changes:
----------
ID: Test succeed behavior
Function: example.present
Name: succeed
Result: True
Comment:
Started: 05:22:16.223336
Duration: 0 ms
Changes:
Summary
------------
Succeeded: 1
Failed: 2
------------
Total states run: 3
# echo $?
2
In the above I’m executing salt-call with a couple options. I’m including the modules and sls files explicitly from my relative path (‘-m modules’ and ‘–file-root .’). I do this for convenience and to be completely positive that my code is being loaded from exactly where I expect.
The state run behavior isn’t surprising. The exception is passed through to the output when there’s a legitimate error, otherwise the False value indicates a normal state failure. The return code is non-zero when using –retcode-passthrough as well.
CLI behavior
# salt-call -m modules example.example 'succeed'
local:
True
# echo $?
0
# salt-call -m modules testme.testme 'fail'
local:
False
# echo $?
0
# salt-call -m modules example.example 'error'
Error running 'example.example': Example function failed due to unexpected input.
# echo $?
1
When the execution module’s function successfully returns (with either True or False), Salt prints the result through stdout and returns a zero return code. When the function throws an exception, Salt prints an error to stderr and returns a non-zero return code.