Ansible Tips Part 2: Don't Format Lists of Things

Something I've encountered while searching for answers to Ansible around the Internet is how to deal with configuration values that are character-delimited. For instance, the fail2ban jail file allows you to specify a set of hostname/IPs to whitelist.

...
# /etc/fail2ban/jail.conf
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
#ignoreip = 127.0.0.1/8
ignoreip = 127.0.0.1/8 1.2.3.4/8
bantime = 600
...

I have commented out the default value for ignoreip so that you can see a before-and-after view of the configuration setting.

In this case, one might think they want to specify those IPs as a variable, doing the following:

...
# /roles/fail2ban/templates/jail.conf.j2
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
#ignoreip = 127.0.0.1/8
ignoreip = 127.0.0.1/8 {{ ignoreips }}
bantime = 600
...

with the corresponding template file:

---
# roles/fail2ban/vars/main.yml
ignoreips: 1.2.3.4/8 5.6.7.8/8

But this causes a potential problem for anyone modifying the variables file because they must remember the format and meaning of the variable or else it will cause fail2ban to load. In software development, this is known as cognitive load and you want to minimize this as much as possible, to the point where things are obvious to the programmer.

In this case, we know that fail2ban requires a whitespace-delimited list of IPs/hostnames, so let's use a JSON array. Let's also move the 127.0.0.1/8 into the variables file so that we don't end up with a whitespace at the end of the configuration setting, should the ignoreips Ansible variable be empty.

...
# /roles/fail2ban/templates/jail.conf.j2
# "ignoreip" can be an IP address, a CIDR mask or a DNS host
ignoreip = {{ ignoreips|join(" ") }}
bantime = 600
...

---
# roles/fail2ban/vars/main.yml
ignoreips:
  - 127.0.0.1/8
  - 1.2.3.4/8
  - 5.6.7.8/8

What happened here is that we told the template to expect an array, then join each element of that array into one string, separating each item with a whitespace. In this way, we have enforced the requirement that the IPs must be whitespace-delimited, without requiring the person running Ansible to know it.

In the variables file, we converted the ignoreips variable to a JSON array, then set the 127.0.0.1/8 as the first array element. By doing this, we have shown the person running Ansible that we expect an array (we could have used a comment, but this is more permanent). This person can now add another IP/hostname without needing to remember that it must be an array, because they will already know it.