Project: Salt Project (Provisioning / Automation & Configuration)
File: sls-alternative-syntax.md

Feature Name: Batch mode SLS syntax Start Date: November 17, 2018. Enhance SLS syntax with simpler way of writing "things". Writing SLS can be not always easy and friendly. Especially when you want to do various operations on the same object. Main complain is heard that in order to do something again or run the same function e.g. on the same file, one would need to create another ID for it. The following example will get content of `/etc/<something>-release`, based on `os_family` grain, if it is e.g. `RedHat`: ```yaml rhelrelease: cmd.run: name: cat /etc/redhat-release onlyif: test -f /etc/redhat-release centosrelease: cmd.run: name: cat /etc/centos-release onlyif: test -f /etc/centos-release ``` This will return a structure with an empty `rhelrelease` and content of the release file under `centosrelease`. From API point of view, one would need to check both keys in the structure. Of course, it is possible to write it a bit better then that, something like: ```jinja {% if grains['os_family'] == 'RedHat' %} {% set releasefile = 'centos' if grains['os'] == 'CentOS' else 'redhat' %} release: cmd.run: name: cat /etc/{{ releasefile }}-release onlyif: test -f /etc/{{ releasefile }}-release {%endif%} ``` It is still gets more complicated if older opensuse or SLES machines also needs to be looked up. And yet this SLS is not finished as even more careful logic needed to be added in order to handle cases where no OS is detected. At this point SLS is no longer simple and declarative, but looks more like programming. We can make it simpler. For example as follows: ```jinja release: cmd: {% for releasefile in ['redhat', 'centos', 'SuSE', 'sles'] %} run: name: cat /etc/{{releasefile}}-release onlyif: test -f /etc/{{releasefile}}-release {% endfor %} ``` In the example above, the for-loop will just generate four times same statement to call `cmd.run` function, reusing already built-in check on `onlyif`. That said, no more need to figure out what OS name belongs to what family etc. The example above only made for show-case to let you get the idea. As simple as not specifying particular function. Typically, in Salt it is done this way: ```yaml /etc/some.conf: somemodule.some_function: # < here! params... ``` The simple syntax will be invoked as so: ```yaml /etc/some.conf: somemodule: # < just like that, no function some_function: params

``` Of course, in single call this dosn't make much sense and is actually one line more to write. However, if there is more one operation that needed to be done, this makes a big difference. Since it is impossible to have more than one same ID, you will be required to write different IDs for the same group of tasks, if we grouping them by the targeted object. This results into something like this: ```yaml dosomethingwithmyconfig: somemodule.some_function: name: /etc/some.conf params .... dosomethingwithmyconfig_again: somemodule.someotherfunction: name: /etc/some.conf params .... dosomethingwithmyconfigandover_again: somemodule.somethirdfunction: name: /etc/some.conf params .... ``` With the batch-mode, the same above can be re-written in much more efficient way as follows: ```yaml /etc/some.conf: somemodule: some_function params .... someotherfunction params .... somethirdfunction params .... ``` It works in three steps: During state compile, compiler finds that there is no end function defined, then it injects `call` function name into the state. Essentially, referring to the example above, state will be simply altered to `somemodule.call`, but transparently for the user. LazyLoader does not allow any functions that starts with `_` as they are considered private/hidden. The `call` is an exception but it should be still impossible to invoke it from the module directly. When LazyLoader loads a module, it will inject a generic function named `call` at the module level. If such function is already defined inside the module, LazyLoader will not inject that function therefore. The `call` function takes a list of sets structure, as shown above, assuming that the key of the set is the function name of the parent element `somemodule`, which is a module name, and essentially performs the following: ```python ret = [] for function_name in functions: args, kwargs = functions, functions ret.append(getattr(thismoduleinstance, function_name)(args, *kwargs)) ``` Nothing. Also no changes to any future modules, yet to be written. If module has non-traditional way of exposing methods, e.g. implemented as class or functions made dynamically, in this case `call` should be made separately for that. However, since this is advanced way of making modules, developer already knows what he is doing in this case. Or "lazy" implementation, if one wants to just turn this feature off: ```python call = lambda (a, *k): raise SaltNotSupportedError() ``` No impact. Performance and default syntax should stay unchanged.