SaltStack State Files Written In Python
By: Alek Tant
Tags: Saltstack, Yaml, Python, DevOps, Json

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.

is required. The run function must return a dictionary.

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