Configuration management programs such as CFEngine, Chef, Puppet, Ansible, and Salt talk about idempotency. What exactly does that mean? Lets look at the Merriam-Webster definition:

idempotent (adjective | idem·po·tent | \ˈī-dəm-ˌpō-tənt) relating to or being a mathematical quantity which when applied to itself under a given binary operation (as multiplication) equals itself; also : relating to or being an operation under which a mathematical quantity is idempotent

I'm not sure that helps us. Lets look at Wikipedia's definition:

Idempotence (/ˌaɪdᵻmˈpoʊtəns/ eye-dəm-poh-təns) is the property of certain operations in mathematics and computer science, that can be applied multiple times without changing the result beyond the initial application.

This definition is much closer. In terms of Configuration Management, idempotency is the desired state. Running a configuration management utility like Ansible will bring the system to this state. With a brand new server, this will be every change necessary to have a properly configured server. In the case of an existing running machine, idempotency is about detecting any changes and correcting only these changes. Lets give some examples using simple BASH commands.

Suppose you have a dev server with certain directories owned by the developer. Some of the developers have sudo capability, and every once in awhile some of their files end up being owned as root. Lets say this is a web project with the files located under /var/www/html. You could easily run sudo chown -R ${OWNER} /var/www/html/${SITE} but this will cause a few problems. Looking at these files with stat, we see every single file has it's change timestamp updated. I attribute all these writes from doing exactly this to the death of a server's SSD after only two months. This shotgun approach will fix the problem, but it's not idempotent as all files in the directory are being changed, not just the files with incorrect ownership. Not only do we have excessive unneeded writes to the drive but more importantly we have no logging or understanding what went wrong and what we fixed.

╭─michael@devserver /var/www/html/michael.example.com
╰─➤  ls -al
total 32
drwxr-xr-x 2 michael www-data  4096 Jul 31 18:18 .
drwxr-xr-x 4 root    root      4096 Jul 31 18:15 ..
-rw-rw-r-- 1 michael www-data  1903 Jul 31 18:17 file1
-rw-rw-r-- 1 root    www-data 13857 Jul 31 18:17 file2
-rw-r--r-- 1 michael www-data   669 Jul 31 18:18 file3

This is an example that is idempotent:

╰─➤  SITE=michael.example.com
╰─➤  OWNER=michael
╰─➤  sudo find /var/www/html/${SITE} ! -user ${OWNER} ! -type l -exec stat --printf=%U {} \; -exec echo " -> ${OWNER} (chown): {}" \; -exec chown ${OWNER} {} \;
root -> michael (chown): /var/www/html/www.example.com/file2

In this example we used the find utility to locate the file (or files) set incorrectly, and changed only these files to the correct ownership. As /var/www/html/www.example.com/file2 had incorrect owenership, it was fixed and reported. If we run the find command again, nothing will be found and nothing will be changed. This is idempotence; we have our desired state.

Here is an example from a BASH script that fixes ownership, group, and permissions:

MSG=$(find . ! -user $OWNER -print0 | xargs -0 -I {} sh -c "stat --printf=\"{}: Change Owner %U -> $OWNER\n\" \"{}\"; chown $OWNER \"{}\" 2>&1 || exit 255" 2>/dev/null)
if [ $? -ne 0 ]; then echo "$MSG"; exit 5; fi
if [ -n "$MSG" ]; then echo "$MSG"; fi

MSG=$(find . ! -group $GROUP -print0 | xargs -0 -I {} sh -c "stat --printf=\"{} Change Group %G -> $GROUP\n\" \"{}\"; chgrp $GROUP \"{}\" 2>&1 || exit 255" 2>/dev/null)
if [ $? -ne 0 ]; then echo "$MSG" exit 6; fi
if [ -n "$MSG" ]; then echo "$MSG"; fi

MSG=$(find . -type f ! -perm 644 -print0 | xargs -0 -I {} sh -c "stat --printf=\"{}: Change Permission %a -> 0644\n\" \"{}\"; chmod 0644 \"{}\" 2>&1 || exit 255" 2>/dev/null)
if [ $? -ne 0 ]; then echo "$MSG"; exit 7; fi
if [ -n "$MSG" ]; then echo "$MSG"; fi

MSG=$(find . -type d ! -perm 755 -print0 | xargs -0 -I {} sh -c "stat --printf=\"{}: Change Permission %a -> 0755\n\" \"{}\"; chmod 0755 \"{}\" 2>&1 || exit 255" 2>/dev/null)
if [ $? -ne 0 ]; then echo "$MSG"; exit 8; fi
if [ -n "$MSG" ]; then echo "$MSG"; fi

The changes are dumped into the MSG variable so that we could easily send it off to syslog or elasticsearch.

Previous Post