SaltStack State Files Written In Python
Generally in SaltStack, state file are written in the YAML + Jinja2 format. However, other options are available. One of these options is Python.
In this post, we will display how a simple state written in YAML + Jinja2 can be written in Python instead. In the example, the function of the state is to take data in a pillar, and serialize it as YAML into a file.
The pillar data used in this example is:
breakfast:
egg: scrambled
meats:
- sausage
- bacon
sides:
spam: gross
beans: baked
dinner:
egg: poached
seafood: lobster
other:
- truffle pate
- mornay sauce
sides:
spam: delicious
egg: over easy
meats:
- steak
The Salt State as written in YAML + Jinja2:
{% set application_pillar = ['breakfast', 'dinner'] %}
{%- for app_pillar in application_pillar %}
serialize key { }:
file.serialize:
- name: /tmp/serialize_test.txt
- user: root
- group: root
- mode: 400
- formatter: yaml
- dataset_pillar: {{ app_pillar }}
- merge_if_exists: True
{% endfor %}
The Salt State as written in Python:
#!py
def _get_data():
keys = ['breakfast', 'dinner']
return keys
def run():
config = {}
for key in _get_data():
config['serialize key '.format(key)] = {
'file.serialize': [
{'name': '/tmp/serialize_test.txt'},
{'user': 'root'},
{'group': 'root'},
{'mode': '400'},
{'formatter': 'yaml'},
{'dataset_pillar': key}
]
}
return config
A few notes on the python version.
- The function name
run
is required. - The
run
function must return a dictionary. - The shebang, must be
!py
Bonus
In SaltStack, the shebang really just specifies the order of renderers to use for the particular file. By default, the shebang is #!jinja|yaml
So… does that mean it’s possible to do #!jinja|py
? Well, it seems like it should be, but I had no luck actually getting this to work. The jinja renderer would output what looked like a clean python file but then the py renderer would choke and spit out a syntax error due to a jinja line. For reference, I'll leave the code that was used. Maybe this is a bug that will get patched at some point.
#!jinja|py
{% set application_pillar = ['breakfast', 'dinner'] %}
def run():
config = {}
{% for key in application_pillar %}
config['serialize key { }'] = {
'file.serialize': [
{'name': '/tmp/serialize_test.txt'},
{'user': 'root'},
{'group': 'root'},
{'mode': '400'},
{'formatter': 'yaml'},
{'dataset_pillar': '{ }' }
]
}
{% endfor %}
return config
Update
I reached out to the salt community and was informed that any render pipelines that includes #!py
only get evaluated with the py renderer. So that answers that.
Bonus Bonus
So… what about json?
Well if you’re a psychopath hell bent on squeezing out all the speed possible…yes
#!jinja|json
{% set application_pillar = ['breakfast', 'dinner'] %}
{
{%- for app_pillar in application_pillar %}
"serialize key { }": {
"file.serialize": [
{"name": "/tmp/serialize_test.txt"},
{"user": "root"},
{"group": "root"},
{"mode": 400},
{"formatter": "yaml"},
{"dataset_pillar": "{ }" },
{"merge_if_exists": "True"}
]
}{% if not loop.last %},{% endif %}
{%- endfor %}
}
All render times are taken from the Salt Minion which is running as a single core vagrant instance on my laptop. YMMV.
jinja|yaml
render time: 0.00130581855774
py
render time: 0.000488996505737
jinja|json
render time: 5.69820404053e-05 or 0.0000569
Generally, render time doesn’t really matter, but if you have a use case where it might, json
is clearly what you're going to want to use.
Originally published at https://smartaleksolutions.com on September 15, 2020.