Saltstack statefiles
States are where you define a set of instructions to apply to a machine.
Structure
Basics
For a state to be callable, it must exist on your salt-master configuration's
file_roots
. States are written in YAML.
Statefiles are structured similarly to the pillar, but work a slightly differently. Unlike the pillar, all hosts have access to all of it’s saltmaster’s statefiles. You can manually run any state file using the command state.apply (ex:sudo salt-call state.apply progs.editors.vim
).
Adding a state-file to states/top.sls makes that state a part of that hostname’s highstate. A system’s highstate is the default configuration for that system. You can apply a system’s highstate (installing/updating all programs, and configurations) using the command:sudo salt-call state.highstate
.
Finally, inspired by python’s package-structure, statefiles calledinit.sls
located within a directory are referred to as the directory’s name. This is useful if you want to bundle extra configuration files alongside it’s statefile.states/progs/mystate.sls # both considered equivalent, and called using: states/progs/mystate/init.sls # state.apply progs.mystate
name description location state-files .sls files under the states subdirectory are YAML files that let you define variables, and their values. {project}/states/*.sls
states/top.sls A special sls file that contains hostnames, and under each the path to a state-file. Adding a state-file to a hostname makes it a part of that hostname's highstate. Unlike the pillar, all statefiles are accessible to all machines using state.apply
.{project}/pillar/top.sls
top.sls
base: 'dev-* or beta-*': - match: compound - progs.editors.vim 'os_family:(Arch|FreeBSD|Debian)': - match: grain_pcre - progs.editors.vim '* not os_family(MacOS)': - match: grain_pcre # regex match against minion '^{{grains['id]}}(-w\w+)*$': - match: pcre - hosts.{{grains['id']}}statefile format
Every salt statefile is written using the following pattern:
#!stateconf .description of task: Class.method: - param1: - param2: # ... .description of another task: Class.method: - param1: ...including states
States can include other states (calling them if they haven't been called yet):
#!stateconf include: - progs.vim - progs.gittemplating, and dynamic logic
States can also use the jinja2 templating engine to perform simple logic like loops.
#!stateconf {% set personal_users = ['will', 'alex'] %} {% for user in personal_users %} ./home/{{user}}/.bashrc: file.managed: - source: salt://progs/shell/bash/files/bashrc - user: {{user}} - group: {{user}} - mode: 644 {% endfor %}Salt exposes the pillar, and salt-modules this way.
#!stateconf # pillar {% set password = pillar['passwd']['will']['unix'] %} # grains {% set fam = grains['os_family'] %} # salt.modules.* {% if not salt.file.file_exists( '/home/will' ) %} # ... do something ... {% endif %}universal state parameters (requisites)
Some state arguments can be applied to ALL states. Here is a list of the params I use the most frequently:
#!stateconf .your state description: your.state_module: - name: # usually the filepath, package-name, or line-to-be-run on commandline - unless: # if this shell command succeeds, do not run the state - onlyif: # if this shell command succeeds, run state, otherwise do not - watch: - file: /etc/nginx/nginx.conf # run when this file changesSee Also: https://docs.saltstack.com/en/latest/ref/states/requisites.html
applying states
States are applied either as a part of the system-highstate, or individually.
salt-call state.highstate # apply all states in highstate salt-call state.apply progs.shell.bash # apply only {file_roots}/progs/shell/bash/init.sls
recipes
Here are some patterns that I find myself using quite a lot
iterate homedirs
NOTE:
I have also encountered the use of '%h' which expands to a user's homedirectory. I need to look into the semantics of how this works, and what other types of paths this might work for.
{% set home_root = pillar['system']['home_root'] %} # 'C:/Users', '/home', '/Users' {% for user in salt.user.list_users() %} {% for homedir in [ home_root + user ] %} {% if salt.file.directory_exists( homedir ) %} .your description: your.state: -name: blah {% endif %} {% endfor %} {% endfor %}