SaltStack State Files Written In Python

Aleksandr Rain
3 min readSep 15, 2020

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.

--

--

Aleksandr Rain
0 Followers

Climber, surfer, yogi, dad who does some IT on the side to get by.