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.