One of the really useful featurs of SaltStack is the ability to store staic values in Pillar. These values can be anything from values to be used in configuration files, to passwords or any value that will be static. The values are stored on the Salt master, then through various available methods of targetng they are made available to the minions.

Similar to how Salt states work, the default location for where pillar values are sourced is a top.sls file. Within the top.sls other files can be referenced and included by methods of targeting. A typical top.sls looks like the following:

base:
  '*':
    - packages

Here we see the top.sls is using the default Salt pillar environment of base, using the '*' as the target this will apply to all hosts managed by the Salt master. The top file is also including the pillar value packages.sls referenced here as just packages.

In a previous Salt environment that I managed, I had a need to setup pillar in a way that pillar files would be automatically targeted based on the minion name. The goal was to setup the structure for pillar in a way that would allow a basic form of inheritance in place. For example, a host with a minion name of abc-def-int01 could pull Pillar values from files named abc.sls, abc-def.sls, and abc-dev-int.sls.

In order to accomplish this setup, we use the following top.sls for pillar values. Using the standard base environment and targeting at all minions. We include the ignore_missing option to ensure that Salt will not error out if it tries to import a pillar file that doesn't exist. we then include a pillar file, environment.sls.

top.sls

base:
  '*':
    - ignore_missing: True
    - environment

Inside the environment.sls file, which is used to gather up and include the various pillar files, we use some simple Jinja templating. In this particular Salt environment, the naming convention for the minion names included numbers. For this step, we want to remove the numbers from the minion name.

One of the useful things that Salt allows us to do is write & include custom modules. In this case I wrote some helper functions to strip out any digits in the minion name.

helpers.py

import re

def re_replace(pattern, replacement, string):
  return re.sub(pattern, replacement, string)

def re_contains(pattern, string):
  if re.search(pattern, string):
      return True
  return False

Including this file under /srv/salt/base/_modules, then including /srv/salt/base in the module_dirs configuration item for the Salt master, makes the re_replace and re_contains functions available as Salt module calls.

Stepping through the environment.sls file, first we identify the file to Salt as a combination of jinja, yaml and gpg data. This will ensure that the right renderer is used to process the file. Then we use the new helpers.re_replace function to remove any digits that occur at the end of the minion name and save that value into host_string. We then run a split on the host_string to build an array of the pieces. Following that we include a default_env file (which can be empty) to ensure ithe environment.sls file includes something. Next we loop through the array, gathering up the pieces that make up the host_string variable. At each step they're added to a string, we use the function in the pillar module to ensure that a pillar file with the specified name exists and if it does then we include it.

environment.sls

# !jinja|yaml|gpg (note: extra space after hash)

{%- set host_string = salt['helpers.re_replace']('\d+$', '', grains['id']) -%}
{%- set list = host_string.split('-') -%}
include:
  - default_env
{% for item in list %}
{%- if common_string is defined -%}
{%- set common_string = common_string + '-' + item -%}
{%- else -%}
{%- set common_string = item -%}
{%- endif -%}
  {% if salt['pillar.file_exists'](common_string + '.sls') %}  - {{ common_string }}{% endif %}
{% endfor -%}

Using environment.sls we can have common pillar data shared across minions, from very common items drilling down to specify environment items. It would also be nice to be automatically include host specific data. For that we include another pillar file in our top.sls appropriately named host.sls

top.sls

base:
  '*':
    - ignore_missing: True
    - environment
    - host

Inside host.sls we again specify that the file contains a combination of jinja, yaml and gpg items to ensure that the right renderer is used. We then use some jinja templating and the grains list that is available courtesy of Salt to gather up the name of the minion. We use the jinja function include and specify ignore missing in case the file does not exist. If a pillar file with the name of our minion exists it will be included, if not then this block of jinja will simply be ignored.

host.sls

# !jinja|yaml|gpg (note: extra space after hash)

{% include grains['id'] + '.sls' ignore missing %}

We can now add pillar files into the pillar directory and have them included for our minions automatically, not having to worry about managing the top.sls file. Definitely room for improvements, specifically in the use of Jinja templating, but I found this worked quite well.