neulab/tldr huggingface dataset:
run_script: cmd.run: - name: Powershell -NonInteractive -ExecutionPolicy Bypass -File C:\\Temp\\script.ps1 - runas: frank - runas_password: supersecret
In the above state, the Powershell script run by cmd.run will be run by the frank user with the password supersecret.
check_cmd New in version 2014.7.0.
Check Command is used for determining that a state did or did not run as expected.
NOTE: Under the hood check_cmd calls cmd.retcode with python_shell=True. This means the commands referenced by unless will be parsed by a shell, so beware of side-effects as this shell will be run with the same privileges as the salt-minion.
comment-repo: file.replace: - name: /etc/yum.repos.d/fedora.repo - pattern: '^enabled=0' - repl: enabled=1 - check_cmd: - "! grep 'enabled=0' /etc/yum.repos.d/fedora.repo"
This will attempt to do a replace on all enabled=0 in the .repo file, and replace them with enabled=1. The check_cmd is just a bash command. It will do a grep for enabled=0 in the file, and if it finds any, it will return a 0, which will be inverted by the leading !, causing check_cmd to set the state as failed. If it returns a 1, meaning it didn't find any enabled=0, it will be inverted by the leading !, returning a 0, and declaring the function succeeded.
NOTE: This requisite check_cmd functions differently than the check_cmd of the file.managed state.
Overriding Checks There are two commands used for the above checks.
mod_run_check is used to check for onlyif and unless. If the goal is to override the global check for these to variables, include a mod_run_check in the salt/states/ file.
mod_run_check_cmd is used to check for the check_cmd options. To override this one, include a mod_run_check_cmd in the states file for the state.
Fire Event Notifications New in version 2015.8.0.
The fire_event option in a state will cause the minion to send an event to the Salt Master upon completion of that individual state.
The following example will cause the minion to send an event to the Salt Master with a tag of salt/state_result/20150505121517276431/dasalt/nano and the result of the state will be the data field of the event. Notice that the name of the state gets added to the tag.
nano_stuff: pkg.installed: - name: nano - fire_event: True
In the following example instead of setting fire_event to True, fire_event is set to an arbitrary string, which will cause the event to be sent with this tag: salt/state_result/20150505121725642845/dasalt/custom/tag/nano/finished
nano_stuff: pkg.installed: - name: nano - fire_event: custom/tag/nano/finished
Retrying States New in version 2017.7.0.
The retry option in a state allows it to be executed multiple times until a desired result is obtained or the maximum number of attempts have been made.
The retry option can be configured by the attempts, until, interval, and splay parameters.
The attempts parameter controls the maximum number of times the state will be run.  If not specified or if an invalid value is specified, attempts will default to 2.
The until parameter defines the result that is required to stop retrying the state.  If not specified or if an invalid value is specified, until will default to True
The interval parameter defines the amount of time, in seconds, that the system will wait between attempts.  If not specified or if an invalid value is specified, interval will default to 30.
The splay parameter allows the interval to be additionally spread out. If not specified or if an invalid value is specified, splay defaults to 0 (i.e. no splaying will occur).
The following example will run the pkg.installed state until it returns True or it has been run 5 times.  Each attempt will be 60 seconds apart and the interval will be splayed up to an additional 10 seconds:
my_retried_state: pkg.installed: - name: nano - retry: attempts: 5 until: True interval: 60 splay: 10
The following example will run the pkg.installed state with all the defaults for retry.  The state will run up to 2 times, each attempt being 30 seconds apart, or until it returns True.
install_nano: pkg.installed: - name: nano - retry: True
The following example will run the file.exists state every 30 seconds up to 15 times or until the file exists (i.e. the state returns True).
wait_for_file: file.exists: - name: /path/to/file - retry: attempts: 15 interval: 30
Return data from a retried state When a state is retried, the returned output is as follows:
The result return value is the result from the final run.  For example, imagine a state set to retry up to three times or until True.  If the state returns False on the first run and then True on the second, the result of the state will be True.
The started return value is the started from the first run.
The duration return value is the total duration of all attempts plus the retry intervals.
The comment return value will include the result and comment from all previous attempts.
wait_for_file: file.exists: - name: /path/to/file - retry: attempts: 10 interval: 2 splay: 5
Would return similar to the following.  The state result in this case is False (file.exist was run 10 times with a 2 second interval, but the file specified did not exist on any run).
ID: wait_for_file Function: file.exists Result: False Comment: Attempt 1: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 2: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 3: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 4: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 5: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 6: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 7: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 8: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Attempt 9: Returned a result of "False", with the following comment: "Specified path /path/to/file does not exist" Specified path /path/to/file does not exist Started: 09:08:12.903000 Duration: 47000.0 ms Changes:
Startup States Sometimes it may be desired that the salt minion execute a state run when it is started. This alleviates the need for the master to initiate a state run on a new minion and can make provisioning much easier.
As of Salt 0.10.3 the minion config reads options that allow for states to be executed at startup. The options are startup_states, sls_list, and top_file.
The startup_states option can be passed one of a number of arguments to define how to execute states. The available options are:
highstate Execute state.apply
sls    Read in the sls_list option and execute the named sls files
top    Read in the top_file option and execute states based on that top file on the Salt Master
Examples: Execute state.apply to run the highstate when starting the minion:
startup_states: highstate
Execute the sls files edit.vim and hyper:
startup_states: sls
State Testing Executing a Salt state run can potentially change many aspects of a system and it may be desirable to first see what a state run is going to change before applying the run.
Salt has a test interface to report on exactly what will be changed, this interface can be invoked on any of the major state run functions:
salt '*' state.apply test=True salt '*' state.apply mysls test=True salt '*' state.single test=True
The test run is mandated by adding the test=True option to the states. The return information will show states that will be applied in yellow and the result is reported as None.
Default Test If the value test is set to True in the minion configuration file then states will default to being executed in test mode. If this value is set then states can still be run by calling test=False:
salt '*' state.apply test=False salt '*' state.apply mysls test=False salt '*' state.single test=False
The Top File Introduction Most infrastructures are made up of groups of machines, each machine in the group performing a role similar to others. Those groups of machines work in concert with each other to create an application stack.
To effectively manage those groups of machines, an administrator needs to be able to create roles for those groups. For example, a group of machines that serve front-end web traffic might have roles which indicate that those machines should all have the Apache webserver package installed and that the Apache service should always be running.
In Salt, the file which contains a mapping between groups of machines on a network and the configuration roles that should be applied to them is called a top file.
Top files are named top.sls by default and they are so-named because they always exist in the "top" of a directory hierarchy that contains state files.  That directory hierarchy is called a state tree.
A Basic Example Top files have three components:
• Environment: A state tree directory containing a set of state files to configure systems.
• Target: A grouping of machines which will have a set of states applied to them.
• State files: A list of state files to apply to a target. Each state file describes one or more states to be configured and enforced on the targeted machines.
The relationship between these three components is nested as follows:
• Environments contain targets
• Targets contain states
Putting these concepts together, we can describe a scenario in which all minions with an ID that begins with web have an apache state applied to them:
base:          # Apply SLS files from the directory root for the 'base' environment 'web*':      # All minions with a minion_id that begins with 'web' - apache   # Apply the state file named 'apache.sls'
Environments Environments are directory hierarchies which contain a top file and a set of state files.
Environments can be used in many ways, however there is no requirement that they be used at all. In fact, the most common way to deploy Salt is with a single environment, called base. It is recommended that users only create multiple environments if they have a use case which specifically calls for multiple versions of state trees.
Getting Started with Top Files Each environment is defined inside a salt master configuration variable called, file_roots .
In the most common single-environment setup, only the base environment is defined in file_roots along with only one directory path for the state tree.
In the above example, the top file will only have a single environment to pull from.
Next is a simple single-environment top file placed in /srv/salt/top.sls, illustrating that for the environment called base, all minions will have the state files named core.sls and edit.sls applied to them.
base: '*': - core - edit
Assuming the file_roots configuration from above, Salt will look in the /srv/salt directory for core.sls and edit.sls.
Multiple Environments In some cases, teams may wish to create versioned state trees which can be used to test Salt configurations in isolated sets of systems such as a staging environment before deploying states into production.
For this case, multiple environments can be used to accomplish this task.
To create multiple environments, the file_roots option can be expanded:
file_roots: dev: - /srv/salt/dev qa: - /srv/salt/qa prod: - /srv/salt/prod
In the above, we declare three environments: dev, qa and prod.  Each environment has a single directory assigned to it.
Our top file references the environments:
dev: 'webserver*': - webserver 'db*': - db qa: 'webserver*': - webserver 'db*': - db prod: 'webserver*': - webserver 'db*': - db
As seen above, the top file now declares the three environments and for each, target expressions are defined to map minions to state files. For example, all minions which have an ID beginning with the string webserver will have the webserver state from the requested environment assigned to it.
In this manner, a proposed change to a state could first be made in a state file in /srv/salt/dev and then be applied to development webservers before moving the state into QA by copying the state file into /srv/salt/qa.
Choosing an Environment to Target The top file is used to assign a minion to an environment unless overridden using the methods described below. The environment in the top file must match valid fileserver environment (a.k.a. saltenv) in order for any states to be applied to that minion. When using the default fileserver backend, environments are defined in file_roots.
The states that will be applied to a minion in a given environment can be viewed using the state.show_top function.
Minions may be pinned to a particular environment by setting the environment value in the minion configuration file. In doing so, a minion will only request files from the environment to which it is assigned.
The environment may also be dynamically selected at runtime by passing it to the salt, salt-call or salt-ssh command. This is most commonly done with functions in the state module by using the saltenv argument. For example, to run a highstate on all minions, using only the top file and SLS files in the prod environment, run: salt '*' state.highstate saltenv=prod.
NOTE: Not all functions accept saltenv as an argument, see the documentation for an individual function documentation to verify.
Shorthand If you assign only one SLS to a system, as in this example, a shorthand is also available:
base: '*': global dev: 'webserver*': webserver 'db*':        db qa: 'webserver*': webserver 'db*':        db prod: 'webserver*': webserver 'db*':        db
Advanced Minion Targeting In the examples above, notice that all of the target expressions are globs. The default match type in top files (since version 2014.7.0) is actually the compound matcher, not the glob matcher as in the CLI.
A single glob, when passed through the compound matcher, acts the same way as matching by glob, so in most cases the two are indistinguishable. However, there is an edge case in which a minion ID contains whitespace. While it is not recommended to include spaces in a minion ID, Salt will not stop you from doing so. However, since compound expressions are parsed word-by-word, if a minion ID contains spaces it will fail to match. In this edge case, it will be necessary to explicitly use the glob matcher:
base: 'minion 1': - match: glob - foo
The available match types which can be set for a target expression in the top file are:
┌─────────────┬─────────────────────────────┐ │Match Type   │ Description                 │ ├─────────────┼─────────────────────────────┤ │glob         │ Full minion ID or glob      │ │             │ expression to match         │ │             │ multiple minions (e.g.      │ │             │ minion123 or minion*)       │ ├─────────────┼─────────────────────────────┤ │pcre         │ Perl-compatible regular     │ │             │ expression (PCRE) matching  │ │             │ a minion ID (e.g.           │ │             │ web[0-3].domain.com)        │ ├─────────────┼─────────────────────────────┤ │grain        │ Match a grain, optionally   │ │             │ using globbing (e.g.        │ │             │ kernel:Linux or             │ │             │ kernel:*BSD)                │ ├─────────────┼─────────────────────────────┤ │grain_pcre   │ Match a grain using PCRE    │ │             │ (e.g.                       │ │             │ kernel:(Free|Open)BSD)      │ ├─────────────┼─────────────────────────────┤ │list         │ Comma-separated list of     │ │             │ minions (e.g.               │ │             │ minion1,minion2,minion3)    │ ├─────────────┼─────────────────────────────┤ │pillar       │ Pillar match, optionally    │ │             │ using globbing (e.g.        │ │             │ role:webserver or           │ │             │ role:web*)                  │ ├─────────────┼─────────────────────────────┤ │pillar_pcre  │ Pillar match using PCRE     │ │             │ (e.g.                       │ │             │ role:web(server|proxy)      │ ├─────────────┼─────────────────────────────┤ │pillar_exact │ Pillar match with no        │ │             │ globbing or PCRE (e.g.      │ │             │ role:webserver)             │ ├─────────────┼─────────────────────────────┤ │ipcidr       │ Subnet or IP address (e.g.  │ │             │ 172.17.0.0/16 or 10.2.9.80) │ ├─────────────┼─────────────────────────────┤ │data         │ Match values kept in the    │ │             │ minion's datastore (created │ │             │ using the data execution    │ │             │ module)                     │ ├─────────────┼─────────────────────────────┤ │range        │ Range cluster               │ ├─────────────┼─────────────────────────────┤ │compound     │ Complex expression          │ │             │ combining multiple match    │ │             │ types (see here)            │ ├─────────────┼─────────────────────────────┤ │nodegroup    │ Pre-defined compound        │ │             │ expressions in the master   │ │             │ config file (see here)      │ └─────────────┴─────────────────────────────┘
Below is a slightly more complex top file example, showing some of the above match types:
# All files will be taken from the file path specified in the base # environment in the ``file_roots`` configuration value.
base: # All minions which begin with the strings 'nag1' or any minion with # a grain set called 'role' with the value of 'monitoring' will have # the 'server.sls' state file applied from the 'nagios/' directory.
'nag1* or G@role:monitoring': - nagios.server
# All minions get the following three state files applied
'*': - ldap-client - networking - salt.minion
# All minions which have an ID that begins with the phrase # 'salt-master' will have an SLS file applied that is named # 'master.sls' and is in the 'salt' directory, underneath # the root specified in the ``base`` environment in the # configuration value for ``file_roots``.
'salt-master*': - salt.master
# Minions that have an ID matching the following regular # expression will have the state file called 'web.sls' in the # nagios/mon directory applied. Additionally, minions matching # the regular expression will also have the 'server.sls' file # in the apache/ directory applied.
# NOTE! # # Take note of the 'match' directive here, which tells Salt # to treat the target string as a regex to be matched!
'^(memcache|web).(qa|prod).loc$': - match: pcre - nagios.mon.web - apache.server
# Minions that have a grain set indicating that they are running # the Ubuntu operating system will have the state file called # 'ubuntu.sls' in the 'repos' directory applied. # # Again take note of the 'match' directive here which tells # Salt to match against a grain instead of a minion ID.
'os:Ubuntu': - match: grain - repos.ubuntu
# Minions that are either RedHat or CentOS should have the 'epel.sls' # state applied, from the 'repos/' directory.
'os:(RedHat|CentOS)': - match: grain_pcre - repos.epel
# The three minions with the IDs of 'foo', 'bar' and 'baz' should # have 'database.sls' applied.
'foo,bar,baz': - match: list - database
# Any minion for which the pillar key 'somekey' is set and has a value # of that key matching 'abc' will have the 'xyz.sls' state applied.
'somekey:abc': - match: pillar - xyz
How Top Files Are Compiled When a highstate is executed and an environment is specified (either using the environment config option or by passing the saltenv when executing the highstate), then that environment's top file is the only top file used to assign states to minions, and only states from the specified environment will be run.
The remainder of this section applies to cases in which a highstate is executed without an environment specified.
With no environment specified, the minion will look for a top file in each environment, and each top file will be processed to determine the SLS files to run on the minions. By default, the top files from each environment will be merged together. In configurations with many environments, such as with GitFS where each branch and tag is treated as a distinct environment, this may cause unexpected results as SLS files from older tags cause defunct SLS files to be included in the highstate. In cases like this, it can be helpful to set top_file_merging_strategy to same to force each environment to use its own top file.
Another option would be to set state_top_saltenv to a specific environment, to ensure that any top files in other environments are disregarded:
state_top_saltenv: base
With GitFS, it can also be helpful to simply manage each environment's top file separately, and/or manually specify the environment when executing the highstate to avoid any complicated merging scenarios. gitfs_saltenv_whitelist and gitfs_saltenv_blacklist can also be used to hide unneeded branches and tags from GitFS to reduce the number of top files in play.
When using multiple environments, it is not necessary to create a top file for each environment. The easiest-to-maintain approach is to use a single top file placed in the base environment. This is often infeasible with GitFS though, since branching/tagging can easily result in extra top files. However, when only the default (roots) fileserver backend is used, a single top file in the base environment is the most common way of configuring a highstate.
The following minion configuration options affect how top files are compiled when no environment is specified, it is recommended to follow the below four links to learn more about how these options work:
• state_top_saltenv
• top_file_merging_strategy
• env_order
• default_top
Top File Compilation Examples For the scenarios below, assume the following configuration:
file_roots: base: - /srv/salt/base dev: - /srv/salt/dev qa: - /srv/salt/qa
/srv/salt/base/top.sls:
base: '*': - base1 dev: '*': - dev1 qa: '*': - qa1
/srv/salt/dev/top.sls:
base: 'minion1': - base2 dev: 'minion2': - dev2 qa: '*': - qa1 - qa2
NOTE: For the purposes of these examples, there is no top file in the qa environment.
Scenario 1 - dev Environment Specified In this scenario, the highstate was either invoked with saltenv=dev or the minion has environment: dev set in the minion config file. The result will be that only the dev2 SLS from the dev environment will be part of the highstate, and it will be applied to minion2, while minion1 will have no states applied to it.
If the base environment were specified, the result would be that only the base1 SLS from the base environment would be part of the highstate, and it would be applied to all minions.
If the qa environment were specified, the highstate would exit with an error.
Scenario 2 - No Environment Specified, top_file_merging_strategy is merge In this scenario, assuming that the base environment's top file was evaluated first, the base1, dev1, and qa1 states would be applied to all minions. If, for instance, the qa environment is not defined in /srv/salt/base/top.sls, then because there is no top file for the qa environment, no states from the qa environment would be applied.
Scenario 3 - No Environment Specified, top_file_merging_strategy is same Changed in version 2016.11.0: In prior versions, "same" did not quite work as described below (see here). This has now been corrected. It was decided that changing something like top file handling in a point release had the potential to unexpectedly impact users' top files too much, and it would be better to make this correction in a feature release.
In this scenario, base1 from the base environment is applied to all minions. Additionally, dev2 from the dev environment is applied to minion2.
If default_top is unset (or set to base, which happens to be the default), then qa1 from the qa environment will be applied to all minions. If default_top were set to dev, then both qa1 and qa2 from the qa environment would be applied to all minions.
Scenario 4 - No Environment Specified, top_file_merging_strategy is merge_all New in version 2016.11.0.
In this scenario, all configured states in all top files are applied. From the base environment, base1 would be applied to all minions, with base2 being applied only to minion1. From the dev environment, dev1 would be applied to all minions, with dev2 being applied only to minion2. Finally, from the qa environment, both the qa1 and qa2 states will be applied to all minions. Note that the qa1 states would not be applied twice, even though qa1 appears twice.
SLS Template Variable Reference WARNING: In the 3002 release sls_path, tplfile, and tpldir have had some significant improvements which have the potential to break states that rely on old and broken functionality. These fixes can be enabled by setting the enable_slsvars_fixes feature flag to True in your minion's config file.  This functionality will become the default in the 3005 release.
features: enable_slsvars_fixes: True
The template engines available to sls files and file templates come loaded with a number of context variables. These variables contain information and functions to assist in the generation of templates.  See each variable below for its availability -- not all variables are available in all templating contexts.
Salt The salt variable is available to abstract the salt library functions. This variable is a python dictionary containing all of the functions available to the running salt minion.  It is available in all salt templates.
{% for file in salt['cmd.run']('ls -1 /opt/to_remove').splitlines() %} /opt/to_remove/{{ file }}: file.absent {% endfor %}
Opts The opts variable abstracts the contents of the minion's configuration file directly to the template. The opts variable is a dictionary.  It is available in all templates.
{{ opts['cachedir'] }}
The config.get function also searches for values in the opts dictionary.
Pillar The pillar dictionary can be referenced directly, and is available in all templates:
{{ pillar['key'] }}
Using the pillar.get function via the salt variable is generally recommended since a default can be safely set in the event that the value is not available in pillar and dictionaries can be traversed directly:
{{ salt['pillar.get']('key', 'failover_value') }} {{ salt['pillar.get']('stuff:more:deeper') }}
Grains The grains dictionary makes the minion's grains directly available, and is available in all templates:
{{ grains['os'] }}
The grains.get function can be used to traverse deeper grains and set defaults:
{{ salt['grains.get']('os') }}
saltenv The saltenv variable is available in only in sls files when gathering the sls from an environment.
{{ saltenv }}
SLS Only Variables The following are only available when processing sls files. If you need these in other templates, you can usually pass them in as template context.
sls The sls variable contains the sls reference value, and is only available in the actual SLS file (not in any files referenced in that SLS). The sls reference value is the value used to include the sls in top files or via the include option.
{{ sls }}
slspath The slspath variable contains the path to the directory of the current sls file. The value of slspath in files referenced in the current sls depends on the reference method. For jinja includes slspath is the path to the current directory of the file. For salt includes slspath is the path to the directory of the included file. If current sls file is in root of the file roots, this will return ""
{{ slspath }}
sls_path A version of slspath with underscores as path separators instead of slashes.  So, if slspath is path/to/state then sls_path is path_to_state
{{ sls_path }}
slsdotpath A version of slspath with dots as path separators instead of slashes. So, if slspath is path/to/state then slsdotpath is path.to.state. This is same as sls if sls points to a directory instead if a file.
{{ slsdotpath }}
slscolonpath A version of slspath with colons (:) as path separators instead of slashes.  So, if slspath is path/to/state then slscolonpath is path:to:state.
{{ slscolonpath }}
tplpath Full path to sls template file being process on local disk. This is usually pointing to a copy of the sls file in a cache directory. This will be in OS specific format (Windows vs POSIX). (It is probably best not to use this.)
{{ tplpath }}
tplfile Relative path to exact sls template file being processed relative to file roots.
{{ tplfile }}
tpldir Directory, relative to file roots, of the current sls file. If current sls file is in root of the file roots, this will return ".". This is usually identical to slspath except in case of root-level sls, where this will return a ".".
A Common use case for this variable is to generate relative salt urls like:
my-file: file.managed: source: salt://{{ tpldir }}/files/my-template
tpldot A version of tpldir with dots as path separators instead of slashes. So, if tpldir is path/to/state then tpldot is path.to.state. NOTE: if tpldir is ., this will be set to ""
{{ tpldot }}
State Modules State Modules are the components that map to actual enforcement and management of Salt states.
States are Easy to Write! State Modules should be easy to write and straightforward. The information passed to the SLS data structures will map directly to the states modules.
Mapping the information from the SLS data is simple, this example should illustrate:
/etc/salt/master: # maps to "name", unless a "name" argument is specified below file.managed: # maps to <filename>.<function> - e.g. "managed" in https://github.com/saltstack/salt/tree/master/salt/states/file.py - user: root # one of many options passed to the manage function - group: root - mode: 644 - source: salt://salt/master
Therefore this SLS data can be directly linked to a module, function, and arguments passed to that function.
This does issue the burden, that function names, state names and function arguments should be very human readable inside state modules, since they directly define the user interface.
Salt passes a number of keyword arguments to states when rendering them, including the environment, a unique identifier for the state, and more.  Additionally, keep in mind that the requisites for a state are part of the keyword arguments. Therefore, if you need to iterate through the keyword arguments in a state, these must be considered and handled appropriately. One such example is in the pkgrepo.managed state, which needs to be able to handle arbitrary keyword arguments and pass them to module execution functions.  An example of how these keyword arguments can be handled can be found here.
Best Practices A well-written state function will follow these steps:
NOTE: This is an extremely simplified example. Feel free to browse the source code for Salt's state modules to see other examples.
1. Set up the return dictionary and perform any necessary input validation (type checking, looking for use of mutually-exclusive arguments, etc.).
def myfunc(): ret = {"name": name, "result": False, "changes": {}, "comment": ""}
if foo and bar: ret["comment"] = "Only one of foo and bar is permitted" return ret
2. Check if changes need to be made. This is best done with an information-gathering function in an accompanying execution module. The state should be able to use the return from this function to tell whether or not the minion is already in the desired state.
result = __salt__["modname.check"](name)
3. If step 2 found that the minion is already in the desired state, then exit immediately with a True result and without making any changes.
def myfunc(): if result: ret["result"] = True ret["comment"] = "{0} is already installed".format(name) return ret
4. If step 2 found that changes do need to be made, then check to see if the state was being run in test mode (i.e. with test=True). If so, then exit with a None result, a relevant comment, and (if possible) a changes entry describing what changes would be made.
def myfunc(): if __opts__["test"]: ret["result"] = None ret["comment"] = "{0} would be installed".format(name) ret["changes"] = result return ret
5. Make the desired changes. This should again be done using a function from an accompanying execution module. If the result of that function is enough to tell you whether or not an error occurred, then you can exit with a False result and a relevant comment to explain what happened.
result = __salt__["modname.install"](name)
6. Perform the same check from step 2 again to confirm whether or not the minion is in the desired state. Just as in step 2, this function should be able to tell you by its return data whether or not changes need to be made.
ret["changes"] = __salt__["modname.check"](name)
As you can see here, we are setting the changes key in the return dictionary to the result of the modname.check function (just as we did in step 4). The assumption here is that the information-gathering function will return a dictionary explaining what changes need to be made. This may or may not fit your use case.
7. Set the return data and return!
def myfunc(): if ret["changes"]: ret["comment"] = "{0} failed to install".format(name) else: ret["result"] = True ret["comment"] = "{0} was installed".format(name)
Using Custom State Modules Before the state module can be used, it must be distributed to minions. This can be done by placing them into salt://_states/. They can then be distributed manually to minions by running saltutil.sync_states or saltutil.sync_all. Alternatively, when running a highstate custom types will automatically be synced.
NOTE: Writing state modules with hyphens in the filename will cause issues with !pyobjects routines.  Best practice to stick to underscores.
Any custom states which have been synced to a minion, that are named the same as one of Salt's default set of states, will take the place of the default state with the same name. Note that a state module's name defaults to one based on its filename (i.e. foo.py becomes state module foo), but that its name can be overridden by using a __virtual__ function.
Cross Calling Execution Modules from States As with Execution Modules, State Modules can also make use of the __salt__ and __grains__ data. See cross calling execution modules.
It is important to note that the real work of state management should not be done in the state module unless it is needed. A good example is the pkg state module. This module does not do any package management work, it just calls the pkg execution module. This makes the pkg state module completely generic, which is why there is only one pkg state module and many backend pkg execution modules.
On the other hand some modules will require that the logic be placed in the state module, a good example of this is the file module. But in the vast majority of cases this is not the best approach, and writing specific execution modules to do the backend work will be the optimal solution.
Cross Calling State Modules All of the Salt state modules are available to each other and state modules can call functions available in other state modules.
The variable __states__ is packed into the modules after they are loaded into the Salt minion.
The __states__ variable is a Python dictionary containing all of the state modules. Dictionary keys are strings representing the names of the modules and the values are the functions themselves.
Salt state modules can be cross-called by accessing the value in the __states__ dict:
ret = __states__["file.managed"](name="/tmp/myfile", source="salt://myfile")
This code will call the managed function in the file state module and pass the arguments name and source to it.
Return Data A State Module must return a dict containing the following keys/values:
• name: The same value passed to the state as "name".
• changes: A dict describing the changes made. Each thing changed should be a key, with its value being another dict with keys called "old" and "new" containing the old/new values. For example, the pkg state's changes dict has one key for each package changed, with the "old" and "new" keys in its sub-dict containing the old and new versions of the package. For example, the final changes dictionary for this scenario would look something like this:
ret["changes"].update({"my_pkg_name": {"old": "", "new": "my_pkg_name-1.0"}})
• result: A tristate value.  True if the action was successful, False if it was not, or None if the state was run in test mode, test=True, and changes would have been made if the state was not run in test mode.
┌───────────────────┬───────────┬───────────────┐ │                   │ live mode │ test mode     │ ├───────────────────┼───────────┼───────────────┤ │no changes         │ True      │ True          │ ├───────────────────┼───────────┼───────────────┤ │successful changes │ True      │ None          │ ├───────────────────┼───────────┼───────────────┤ │failed changes     │ False     │ False or None │ └───────────────────┴───────────┴───────────────┘
NOTE: Test mode does not predict if the changes will be successful or not, and hence the result for pending changes is usually None.
However, if a state is going to fail and this can be determined in test mode without applying the change, False can be returned.
• comment: A list of strings or a single string summarizing the result. Note that support for lists of strings is available as of Salt 2018.3.0.  Lists of strings will be joined with newlines to form the final comment; this is useful to allow multiple comments from subparts of a state.  Prefer to keep line lengths short (use multiple lines as needed), and end with punctuation (e.g. a period) to delimit multiple comments.
NOTE: States should not return data which cannot be serialized such as frozensets.
Sub State Runs Some states can return multiple state runs from an external engine. State modules that extend tools like Puppet, Chef, Ansible, and idem can run multiple external states and then return their results individually in the "sub_state_run" portion of their return as long as their individual state runs are formatted like salt states with low and high data.
For example, the idem state module can execute multiple idem states via it's runtime and report the status of all those runs by attaching them to "sub_state_run" in it's state return.  These sub_state_runs will be formatted and printed alongside other salt states.
state_return = { "name": None,  # The parent state name "result": None,  # The overall status of the external state engine run "comment": None,  # Comments on the overall external state engine run "changes": {},  # An empty dictionary, each sub state run has it's own changes to report "sub_state_run": [ { "changes": {},  # A dictionary describing the changes made in the external state run "result": None,  # The external state run name "comment": None,  # Comment on the external state run "duration": None,  # Optional, the duration in seconds of the external state run "start_time": None,  # Optional, the timestamp of the external state run's start time "low": { "name": None,  # The name of the state from the external state run "state": None,  # Name of the external state run "__id__": None,  # ID of the external state run "fun": None,  # The Function name from the external state run }, } ], }
Test State All states should check for and support test being passed in the options. This will return data about what changes would occur if the state were actually run. An example of such a check could look like this:
def myfunc(): # Return comment of changes if test. if __opts__["test"]: ret["result"] = None ret["comment"] = "State Foo will execute with param {0}".format(bar) return ret
Make sure to test and return before performing any real actions on the minion.
NOTE: Be sure to refer to the result table listed above and displaying any possible changes when writing support for test. Looking for changes in a state is essential to test=true functionality. If a state is predicted to have no changes when test=true (or test: true in a config file) is used, then the result of the final state should not be None.
Watcher Function If the state being written should support the watch requisite then a watcher function needs to be declared. The watcher function is called whenever the watch requisite is invoked and should be generic to the behavior of the state itself.
The watcher function should accept all of the options that the normal state functions accept (as they will be passed into the watcher function).
A watcher function typically is used to execute state specific reactive behavior, for instance, the watcher for the service module restarts the named service and makes it useful for the watcher to make the service react to changes in the environment.
The watcher function also needs to return the same data that a normal state function returns.
Mod_init Interface Some states need to execute something only once to ensure that an environment has been set up, or certain conditions global to the state behavior can be predefined. This is the realm of the mod_init interface.
A state module can have a function called mod_init which executes when the first state of this type is called. This interface was created primarily to improve the pkg state. When packages are installed the package metadata needs to be refreshed, but refreshing the package metadata every time a package is installed is wasteful. The mod_init function for the pkg state sets a flag down so that the first, and only the first, package installation attempt will refresh the package database (the package database can of course be manually called to refresh via the refresh option in the pkg state).
The mod_init function must accept the Low State Data for the given executing state as an argument. The low state data is a dict and can be seen by executing the state.show_lowstate function. Then the mod_init function must return a bool. If the return value is True, then the mod_init function will not be executed again, meaning that the needed behavior has been set up. Otherwise, if the mod_init function returns False, then the function will be called the next time.
A good example of the mod_init function is found in the pkg state module:
def mod_init(low): """ Refresh the package database here so that it only needs to happen once """ if low["fun"] == "installed" or low["fun"] == "latest": rtag = __gen_rtag() if not os.path.exists(rtag): open(rtag, "w+").write("") return True else: return False
The mod_init function in the pkg state accepts the low state data as low and then checks to see if the function being called is going to install packages, if the function is not going to install packages then there is no need to refresh the package database. Therefore if the package database is prepared to refresh, then return True and the mod_init will not be called the next time a pkg state is evaluated, otherwise return False and the mod_init will be called next time a pkg state is evaluated.
Strings and Unicode A state module author should always assume that strings fed to the module have already decoded from strings into Unicode. In Python 2, these will be of type 'Unicode' and in Python 3 they will be of type str. Calling from a state to other Salt sub-systems, such as execution modules should pass Unicode (or bytes if passing binary data). In the rare event that a state needs to write directly to disk, Unicode should be encoded to a string immediately before writing to disk. An author may use __salt_system_encoding__ to learn what the encoding type of the system is. For example, 'my_string'.encode(__salt_system_encoding__').
Full State Module Example The following is a simplistic example of a full state module and function.  Remember to call out to execution modules to perform all the real work. The state module should only perform "before" and "after" checks.
1. Make a custom state module by putting the code into a file at the following path: /srv/salt/_states/my_custom_state.py.
2. Distribute the custom state module to the minions:
salt '*' saltutil.sync_states
3. Write a new state to use the custom state by making a new state file, for instance /srv/salt/my_custom_state.sls.
4. Add the following SLS configuration to the file created in Step 3:
human_friendly_state_id:        # An arbitrary state ID declaration. my_custom_state:              # The custom state module name. - enforce_custom_thing      # The function in the custom state module. - name: a_value             # Maps to the ``name`` parameter in the custom function. - foo: Foo                  # Specify the required ``foo`` parameter. - bar: False                # Override the default value for the ``bar`` parameter.
Example state module import salt.exceptions
def enforce_custom_thing(name, foo, bar=True): """ Enforce the state of a custom thing
This state module does a custom thing. It calls out to the execution module ``my_custom_module`` in order to check the current system and perform any needed changes.
name The thing to do something to foo A required argument bar : True An argument with a default value """ ret = { "name": name, "changes": {}, "result": False, "comment": "", }
# Start with basic error-checking. Do all the passed parameters make sense # and agree with each-other? if bar == True and foo.startswith("Foo"): raise salt.exceptions.SaltInvocationError( 'Argument "foo" cannot start with "Foo" if argument "bar" is True.' )
# Check the current state of the system. Does anything need to change? current_state = __salt__["my_custom_module.current_state"](name)
if current_state == foo: ret["result"] = True ret["comment"] = "System already in the correct state" return ret
# The state of the system does need to be changed. Check if we're running # in ``test=true`` mode. if __opts__["test"] == True: ret["comment"] = 'The state of "{0}" will be changed.'.format(name) ret["changes"] = { "old": current_state, "new": "Description, diff, whatever of the new state", }
# Return ``None`` when running with ``test=true``. ret["result"] = None
# Finally, make the actual change and return the result. new_state = __salt__["my_custom_module.change_state"](name, foo)
ret["comment"] = 'The state of "{0}" was changed!'.format(name)
ret["changes"] = { "old": current_state, "new": new_state, }
ret["result"] = True
State Management State management, also frequently called Software Configuration Management (SCM), is a program that puts and keeps a system into a predetermined state. It installs software packages, starts or restarts services or puts configuration files in place and watches them for changes.
Having a state management system in place allows one to easily and reliably configure and manage a few servers or a few thousand servers. It allows configurations to be kept under version control.
Salt States is an extension of the Salt Modules that we discussed in the previous remote execution tutorial. Instead of calling one-off executions the state of a system can be easily defined and then enforced.
Understanding the Salt State System Components The Salt state system is comprised of a number of components. As a user, an understanding of the SLS and renderer systems are needed. But as a developer, an understanding of Salt states and how to write the states is needed as well.
NOTE: States are compiled and executed only on minions that have been targeted.  To execute functions directly on masters, see runners.
Salt SLS System The primary system used by the Salt state system is the SLS system. SLS stands for SaLt State.
The Salt States are files which contain the information about how to configure Salt minions. The states are laid out in a directory tree and can be written in many different formats.
The contents of the files and the way they are laid out is intended to be as simple as possible while allowing for maximum flexibility. The files are laid out in states and contains information about how the minion needs to be configured.
SLS File Layout SLS files are laid out in the Salt file server.
A simple layout can look like this:
top.sls ssh.sls sshd_config users/init.sls users/admin.sls salt/master.sls web/init.sls
The top.sls file is a key component. The top.sls files is used to determine which SLS files should be applied to which minions.
The rest of the files with the .sls extension in the above example are state files.
Files without a .sls extensions are seen by the Salt master as files that can be downloaded to a Salt minion.
States are translated into dot notation. For example, the ssh.sls file is seen as the ssh state and the users/admin.sls file is seen as the users.admin state.
Files named init.sls are translated to be the state name of the parent directory, so the web/init.sls file translates to the web state.