Idempotent random number

Need a random number for your Ansible playbook? But want to be idempotent on subsequent runs? There is an answer!

Let's say you want to register cron jobs on a bunch of servers and don't want it to start on the same time. You can use:

minutes: "{{ 60 | random }}"  

but this will generate random number during each playbook execution, giving you unnecessary changed state for tasks.

Update for Ansible 2.3:

As of Ansible version 2.3, it’s also possible to initialize the random number generator from a seed. This way, you can create random-but-idempotent numbers:

"{{ 59 |random(seed=inventory_hostname) }} * * * * root /script/from/cron"

For previous Ansible versions:

But you can craft a pseudo-random number based on any variable/fact you want. For example, you can choose inventory_hostname to make this number different between servers but the same on subsequent playbook runs:

minutes: "{{ ( inventory_hostname | hash | list | map('int',0,16) | sum ) % 60 }}"  

Magic explained:

  • we take inventory_hostname string (e.g. "myserver")
  • make a hash from it ("c3a7a35a28dcce27daad3a7a90caad99b967a904")
  • split it into array of characters (["c","3","a",...])
    where every character is a hexadecimal digit
  • apply int filter with base=16 to every character to convert it to number 0..15 ([12,3,10,...])
  • sum all numbers (334)
  • limit our pseudo-random number by taking the remainder of division % 60 (34)

So your cron task may look like:

cron:  
  name: myjob
  job: myscript.sh
  minute: "{{ ( inventory_hostname | hash | list | map('int',0,16) | sum ) % 60 }}"
  hour: "{{ (( inventory_hostname | hash | list | map('int',0,16) | sum ) % 2) + 6 }}"

This will start myscript.sh at some random time between 6:00 and 7:59 and this time will be idempotent on subsequent playbook runs.