-1

I have an Ansible task which renews a LetsEncrypt certificate. I only want this task to run once a week, in order to avoid getting blocked for hammering the API, but the rest of the playbook needs to run daily — and will be run way more than that during development.

Is it possible to rate-limit an Ansible task so it will be skipped if it has already been run in the last n hours?

The best way I can think of is to touch a file if the task was run, and skip the task if the file exists and is newer than a certain timestamp — but Ansible doesn't seem to be big on date and time calculations. It also makes the playbook pretty chunky.

I did think of applying a tag to the task and marking the tag as skipped by default in ansible.cfg, then only running with that tag enabled once a week, but ansible.cfg seems to override --tags rather than the other way around.

John Y
  • 1,161
  • 12
  • 23
  • Does this answer your question? [Run CRON job everyday at specific time](https://stackoverflow.com/questions/35574603/run-cron-job-everyday-at-specific-time) – Marcin Orlowski Jun 22 '20 at 17:14
  • It's not the running from cron that's the problem, it's getting Ansible to log the time a task was last run and skip it if it's been run recently. In this case, the job runs daily, but the cert renewal should be skipped if it's been run less than 7 days ago. – John Y Jun 23 '20 at 13:00

1 Answers1

1

Option 1

You could run the playbook by executing a Bash script via CRON, something like:

#!/usr/bin/env bash

DAY=$(date +%u)
HOUR=$(date +%k)

# This only runs all tasks (including letsencrypt) if it's 1 AM on Monday, at 
# all other times letsencrypt will be omitted. 
if [[ $(($DAY)) -eq 1 && $(($HOUR)) -eq 1 ]]; then
        echo "ansible-playbook [...]"
else
        echo 'ansible-playbook [...] --tags "!letsencrypt"'
fi

Then you would have to tag your task with letsencrypt. It would then only run - together with all the other tasks - at whatever specific time you set in your script, otherwise it will be skipped. The cronjob itself can still run on an hourly basis.

Option 2

Another way is using ansible_date_time in your playbook. This way, you don't need the cronjob crutch. See the following simple example playbook.

- hosts: localhost
  pre_tasks:

    - name: Get current weekday.
      set_fact:
        weekday: "{{ ansible_date_time.weekday }}"

    - name: Get current hour.
      set_fact:
        hour: "{{ ansible_date_time.hour }}"

  tasks:

    - debug:
        msg: "Replace this task with your Let's Encrypt task."
      when:
        - weekday is match("Thursday")
        - hour is match("22")

Maybe have a look at this post about working with date/time in Ansible and the other possible output formats.

bellackn
  • 1,939
  • 13
  • 22
  • I had tried that, but when I run with `--tags letsencrypt` *nothing* gets run. – John Y Jun 23 '20 at 12:58
  • Ah right, you would have to tag all other tasks with always. Or, even better: Run the regular task with --tags "!letsencrypt". I've updated my answer accordingly. – bellackn Jun 23 '20 at 14:30
  • Ah, so Ansible doesn't provide a way to do this that isn't a pain in the backside. That's a shame. Fortunately, in this case, I solved it by having a cron job renew the certificates elsewhere and just copying the files across in the playbook — if the file's not changed, no problem :) – John Y Jun 25 '20 at 10:24
  • I have just updated my answer, maybe the second option is a better fit for you. :) – bellackn Jun 25 '20 at 21:07