Coming from puppet I really got used to the hierarchical data structure hiera provides. It's easy to use, yet pretty powerful when used in combination with the module_data puppet module, which allows you to put your hiera-data in the module directory itself, while still being able to globally override stuff from the main hiera source.
The approach I present here allows you to do something similar with salt and it's pillar data by using Jinja2 only. No additional modules whatsoever are required for this to work.
Configuration data is being split up into four levels:
- state defaults
- filtered state defaults
- pillar data
- filtered pillar data
Each level overrides its predecessor, allowing you to easily set and override defaults for different envoronments, platforms, etc...
State defaults are set in settings.yml in the state directory. Common defaults are set under the config key. config is a simple python dict which may be of arbitrary depth and complexity.
The second key in settings.yml is lookup. lookup is a list of dicts of dicts. Elements of the list are always processed in FIFO order, thus also being a hierarchy of its own:
lookup:
- [grain_A1]:
[grain_A1_value_1]:
[config_key_1]: value_1
[config_key_2]: value_2
...
[grain_A1_value_2]:
...
[grain_An]:
[grain_An_value_1]:
...
- [grain_B1]:
...See state_foo_settings.yml and pillar_foo_init.sls for an example. The resulting data would look like this:
# On a non-Debian minion:
settings:
a: 1
b: 99
c:
ca: 31
cb: 32
d: [ 5,6 ]
# On a Debian minion:
settings:
a: 99
b: 99
c:
ca: 31
d: [ 5,6 ]There are only four rules for merging:
- merging is always done in FIFO order
- dicts get recursively merged
- everything else gets overridden
- null values delete keys
So when you want to unset a value/remove a key, simply set it to null and it will be completely removed from the resulting dict.
Merged settings can be loaded via Jinja2 in a single line, similar to the map.jinja approach which is currently being used for salt formulas:
{%- from 'foo/settings.sls' import settings with context %}
{%- set some_var = settings.get('some_key', 'default_value') %}To use this in your state, all you have to do is:
- provide a
/srv/salt/YOUR_STATE/settings.ymlcontaining your defaults - copy contents from
state_foo_settings.slsbelow into/srv/salt/YOUR_STATE/settings.sls - copy contents from
state_macro.slsbelow into/srv/salt/macro.sls - load the settings in your state files as shown above
If you do not want to ship the macros apart from you module, macro.sls can also be merged with your settings.sls.
NOTICE:
Pillar data follows the same rules settings.yml does, except that you of course have to put all your data into a dict whose key is named after your module. Just like you do without this merging stuff.
just extended the deep_merge() macro to handle N dicts instead of just two, extracted lookup_merge() out of load_settings() so it can be used on it's own, and switched parameter positions for load_settings() to match the other two macros.