Jinja & templating in states
Data-driven, portable SLS.
SLS files are rendered through Jinja before they are parsed as YAML, which makes states data-driven and portable. You use Jinja to pull values from pillar and grains, loop to generate repeated blocks, and branch on the platform — so one state file adapts to every minion. This two-stage rendering (Jinja first, then YAML) is powerful and, occasionally, a source of confusing errors when the two layers interact.
{% for user in pillar.get('users', []) %}create_{{ user['name'] }}:user.present:- name: {{ user['name'] }}- shell: {{ user.get('shell', '/bin/bash') }}- groups: {{ user.get('groups', []) }}{% endfor %}# one loop renders a user.present state per entry in pillar
Keep logic light, and know the render order
The discipline mirrors Ansible/Puppet templating: use Jinja to inject data and lightly branch, but do not turn SLS into a program — heavy logic belongs in pillar (data) or execution modules (code). And remember Jinja runs first: it produces text that must then be valid YAML, so a Jinja loop that emits mis-indented lines yields a YAML error that points at the rendered output, not your source. When puzzled, render the SLS to see what YAML Jinja actually produced.
# see the YAML that Jinja actually produced (invaluable for debugging)$ salt 'web1' state.show_sls users$ salt 'web1' slsutil.renderer salt://users/init.sls default_renderer=jinja