IaB (Infrastructure as Bash script)

Infrastructure as Code relies on idempotent scripts. This Article describes some techniques to make bash scripts idempotent.

In 2012 when I installed a compute cluster at the university I needed a configuration management tool that kept everything in sync. Ansible did not exist back then and rather than installing a tool like CFEngine or Puppet I wrote my own scripts that got triggered by FAI. That turned out to work very well as there was not much variation between the nodes. I want to share some techniques that can be used with IaB (Infrastructure as Bash script) ;-).

Most of our CLI tools are already idempotent

rsync

Rsync is an easy way to keep configuration files in sync especially if there is a large amount of files. If our scripts run as cron jobs we can edit a config file in our source directory and we are done. There is not even single line of code we have to change in our scripts. Btw. Ansible has the ansible.posix.synchronize wrapper for that.

Do not rely on assumptions

The environment of our target system may be different from what we have on our local machine. We can not rely on assumptions that certain packages are installed and config exist. We have to be prepared for the unexpected.

Our scripts should work in an environment that we define explicitly rather than relying on what is already there.

#!/bin/bash
export PATH=/scripts:/bin:/sbin:/usr/bin

apt-get -y install sed awk expect

Use the proper options for your commands

Most of our CLI commands have an option that makes them idempotent

rm -f ${PATH_TO_FILE}
mkdir -p {dir1,dir2}/{subdir1,subdir2,subdir3} 
ln -sfn ${SOURCE} ${TARGET} 

Overwrite files

Appending lines to a file assumes that we know what is already in there. We should replace the entire files whenever possible.

echo "127.0.0.1 $(hostname)" > /etc/hosts

Validate

If we have to rely on something (e.g. line does not exist yet) we must validate it.

grep "^127.0.0.1.*$(hostname)$" /etc/hosts || echo "127.0.0.1 $(hostname) >> /etc/hosts

Search and replace

Instead of replacing an entire file we can replace single lines if we have to

sed -i "s/127.0.0.1.*/127.0.0.1 $(hostname)/" /etc/hosts

Do not hesitate to remove unnecessary things

Removing a package but keeping configuration files that might be useful someday is not a good idea. Especially not if we use an automated setup.

Use

apt-get purge ${PACKAGE}

rather than

apt-get remove ${PACKAGE

Setting up infrastructure with bash scripts still can be useful today in some cases. We should not make things more complicated than they have to be.

Contact