Idempotency in Automation

One characteristic of robust automation is to make sure that our script is idempotent, this is a big word that describes a behavior where our automation will produce the same end system state no matter how many times the automation is triggered.

Here's a simple example of a non-idempotent vs idempotent implementation of a script that makes sure a list of directories exists and has the correct permission in a Linux environment:

#!/usr/bin/env python3

import os


def resolve_path(path):
    return os.path.abspath(os.path.expanduser(path))

def check_integrity(dirs, mode=0o644):
    for dir in dirs:
       dir = resolve_path(dir)
       os.makedirs(dir)
       os.chmod(dir, mode)

if __name__ == '__main__':
    check_integrity(['~/.www', '~/.common'])

This first implementation is non-idempotent because if the script is interrupted during its execution, the execution might raise an error because the script doesn't handle the system state where directories had already existed. To make it idempotent we can change it a little bit by passing exists_ok=True parameter.

#!/usr/bin/env python3

import os


def resolve_path(path):
    return os.path.abspath(os.path.expanduser(path))

def check_integrity(dirs, mode=0o644):
    for dir in dirs:
       dir = resolve_path(dir)
       os.makedirs(dir, exists_ok=True)
       os.chmod(dir, mode)

if __name__ == '__main__':
    check_integrity(['~/.www', '~/.common'])

Now the second script is idempotent because even if the script fails during its first execution when it's run again, it will produce the same end system state with no problem.

This example might seem a bit too obvious and trivial but it's enough to introduce the concept of idempotency. When we set up our automation to be idempotent we can expect our automation can fail during its execution because of factors we might haven't considered. To fix the problem we don't need manual intervention anymore instead we just need to run the script one more time.