Overview

When I switched companies a while ago, I came across some a pattern in several Salt formulas that the CTO at my new company had written. To this day, it’s still one of the more brilliant snippets I’ve seen for the use case of templating out several files in a state all at once.

Context

Some applications can require a great many configuration files. For example a webserver such as apache or nginx can have many configuration files to support various virtual hosts. In this case, the most straightforward way of writing a formula would dictate creating a declaration for each file or template. A time consuming, repetitive task that requires upkeep.

Setup

Let’s say our application is nginx and we need to drop in a few configuration files for it. For arguments sake, some of these conf files need to be templated, and some do not.

The Conventional Method

Now, lets say that we need to manage a static nginx.conf, but also drop some templated config files, eggs.conf and bacon.conf, into the /etc/nginx/conf.d directory.

nginx
├── files
│ ├── bacon.conf.jinja
│ ├── eggs.conf.jinja
│ └── nginx.conf
└── init.sls
{{ sls }} - manage the nginx config file:
file.managed:
- name: /etc/nginx/nginx.conf
- source: salt://nginx/files/nginx.conf

{{ sls }} - managed the eggs config file:
file.managed:
- name: /etc/nginx/conf.d/eggs.conf
- source: salt://nginx/files/eggs.conf.jinja

{{ sls }} - managed the bacon config file:
file.managed:
- name: /etc/nginx/conf.d/bacon.conf
- source: salt://nginx/files/bacon.conf.jinja

Add Jinja Magic

Now, instead of being conventional, lets use jinja to make this dynamic. First, we’ll need to revise the layout of our states’s files directory to:

nginx
├── files
│ └── etc
│ └── nginx
│ ├── conf.d
│ │ ├── bacon.conf.jinja
│ │ └── eggs.conf.jinja
│ └── nginx.conf
└── init.sls
nginx/init.sls - manage the config file /etc/nginx/nginx.conf: 
file.managed:
- source: salt://nginx/files/etc/nginx/nginx.conf
- name: /etc/nginx/nginx.conf
- makedirs: True

nginx/init.sls - manage the template file /etc/nginx/conf.d/bacon.conf:
file.managed:
- source: salt://nginx/files/etc/nginx/conf.d/bacon.conf.jinja
- name: /etc/nginx/conf.d/bacon.conf
- makedirs: True
- template: jinja

nginx/init.sls - manage the template file /etc/nginx/conf.d/eggs.conf:
file.managed:
- source: salt://nginx/files/etc/nginx/conf.d/eggs.conf.jinja
- name: /etc/nginx/conf.d/eggs.conf
- makedirs: True
- template: jinja

How It Works

The single declaration in our state uses the salt cp.list_master function to retrieve a list of full paths to all files on the master that match the prefix we specify. In our declaration, we specify the prefix as being the files directory of the running formula ( nginx/files). So now that we have a list of full paths to retrieve source files from the master with, we only need to know where to put the file on the minion. Since the directory layout once inside the SLS files directory fully matches the path for each config file on the minion, we are able to derive the target location by using a jinja replace function to substitute the prefix with nothing for each file.

A Note On Security

Working with the salt cp.list_master function gives us a good reminder that ALL minions have access to everything in the salt master's file roots. Keep this in mind with static config files and make sure you never put secrets in anything in the file roots.

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

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