Is there a way to guarantee idempotence for playbooks that use randomly generated variables?
For example, I want to setup my crontabs to trigger emails on multiple servers at different times, so I create random integers using ansible's set_fact module:
tasks:
- set_fact:
first_run_30="{{ 30 | random }}"
run_once: yes
Then apply those generated variables to my crontab using ansible like so:
- name: Setup cron30job
cron: name=cron30job minute={{first_run_30}},{{first_run_30 | int + 30}} job='/bin/bash /cron30job.sh' state=present user=root
environment:
MAILTO: 'me@somelist.com'
MAILFROM: 'me@somehost.com'
This works very well, however, ansible's indempotence principle is, I believe, broken using this strategy because each time a play is made you see a change:
TASK: [Setup cron30job] *****************************************
changed: [127.0.0.1]
Further, in the crontab checking under root each time during three separate runs:
[ansible]# cat /var/spool/cron/root
#Ansible: cron30job
5,35 * * * * /bin/bash /sw/test/cron30job.sh
#Ansible: cron30job
9,39 * * * * /bin/bash /sw/test/cron30job.sh
#Ansible: cron30job
6,36 * * * * /bin/bash /sw/test/cron30job.sh
If there is a workaround, or maybe indempotence just will not be possible in my scenario, I would like to know.
Instead of a random value, you could get something related to the node, like an hash of the hostname or the last byte of the ip address.
This is an example:
- name: Get a pseudo-random minute
shell: expr $((16#`echo "{{inventory_hostname}}" | md5sum | cut -c 1-4`)) % 30
register: minute
changed_when: false
As of Ansible version 2.3, it’s 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"
Source: random number filter
I've used this pattern to produce random cron start times with:
- different minutes on different target servers
- different minutes for different hours on the same server (randomness)
- the same minute for the same day and server when repeatedly running Ansible (idempotence)
Requires Ansible >=2.3:
cron:
name: "{{some_name}}_{{item.day}}"
state: present
job: "{{some_job}}"
weekday: "{{item.day}}"
hour: "{{item.hour}}"
minute: "{{59|random(seed=inventory_hostname + item.dow)}}"
with_items:
- { day: 0, hour: 3, dow: "sunday" }
- { day: 1, hour: 7, dow: "monday" }
- { day: 2, hour: 1, dow: "tuesday" }
- { day: 3, hour: 5, dow: "wednesday" }
- { day: 4, hour: 2, dow: "thursday" }
- { day: 5, hour: 4, dow: "friday" }
- { day: 6, hour: 7, dow: "saturday" }
Just in case you don't have ansible >= 2.3
available, a hashed approach using jinja2:
{{ (inventory_hostname |hash('md5')|int(0, 16)) % 60 }}