Editing Files in Saltstack
As much as I ought to put up tech blogs as I work through projects, I can't keep up. So I tend to tech blog only when I discover something I couldn't find on the Internet beforehand. Today's entry is kind of like that and kind of not.
I have been working on configuring a server using the Saltstack configuration management system. A big part of this involves setting configuration files. Often these edits are done using the salt.states.file module. There are many methods provided. Which should you use?
In order of preference, here are the strategies I use when configuring a package:
- If the package has a
conf.d
folder into which you can drop a configuration snippet, usefile.managed
and do that. - If it makes sense to replace the entire configuration file, use
file.managed
and do that with a Jinja template. I used to frown upon this option because if upgrades to the package provided new configuration options, you would miss them in your template. But when you upgrade these configuration files, Debian/Ubuntu bugs you about the changes, so maybe this is not so bad. - If it does not make sense to replace the entire configuration file,
but you can insert a block at the beginning or end of the file, use
file.blockreplace
to do that. This leaves the original file intact, so you can look at available configuration options later. You may need to usefile.comment
to get rid of some default values. In my experiencefile.blockreplace
is fairly robust to changes (although it does weird things with whitespace). - If you really need to edit a configuration file in place, use
file.line
and the trick I document below.
I do not think there are any other good options. file.append
is
terrible because it is not robust -- if you change one byte of the
stuff you appended, Salt will re-append the snippet entirely. Maybe
there are other good options, but I have not found them.
Editing Lines in a File
For some (probably stupid) reason, I decided I needed to edit the
/etc/exim4/update-exim4.conf.conf
file line by line. My strategy was
as follows:
- Define the changes I wanted to make using a Python dictionary
- Loop through that dictionary and use
file.line
with thereplace
option to change the options.
Here is the dictionary I used in my edit. The first element of each tuple is the configuration option, and the second is the value:
{% set configlines = [('dc_eximconfig_configtype', 'satellite',),
('dc_other_hostnames', '' ),
('dc_use_split_config', 'true'),
('dc_readhost', grains['fqdn']),
('dc_smarthost', pillar['exim4']['smarthost']),
('dc_hide_mailname', 'true'),
] %}
Now you can loop through each of these elements and make a change.
Note that each edit needs its own Salt ID, which is where conf[0]
comes in:
{% for conf in configlines %}
/etc/exim4/update-conf-{{ conf[0] }}:
file.line:
- name: /etc/exim4/update-exim4.conf.conf
- match: {{ conf[0] }}=
- content: {{ conf[0] }}='{{ conf[1] }}'
- mode: replace
- backup: False
- require:
- pkg: exim4
{% endfor %}
The match
directive looks for the line to edit. The content
one sets the
option. The replace
actually does the change. Note that if the line
is commented out then this will leave the line commented.
It is very important to note that you should only change ONE line at a
time using file.line
. Trying to change multiple lines will work, but
is very fragile in the same way file.append
is. In that situation
you should use file.blockreplace
instead.