'Ensure a certain amount of time has elapsed between two tasks in ansible playbook, in real time

I will be notifying users that an event will happen in 15 minutes; I then perform tasks that take a variable amount of time which is less than 15 minutes, and I then need to wait the rest of the time and perform the said event at exactly 15 minutes from when I notified the users.

Can someone propose such a real-time timer in ansible? pause won't work, because it's static. Also, async doesn't work on a pause task, so we can't start a pause asynchronously with poll: 0, move on to other tasks, and then come back and ensure it has succeeded with async_status right before our waited-for task.

This is my best attempt, but the until conditional doesn't seem to be getting updated with the actual current time, because it never terminates:

- name: Ensure a certain amount of time has elapsed between two tasks
  hosts: localhost
  gather_facts: no
  vars:
    wait_time: 10
    timer_delay_interval: 1
  tasks:
    - name: Debug start time
      debug:
        var: ansible_date_time

    - name: Set current time
      set_fact:
        start_time: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}"
    
    - name: Other task
      pause:
        seconds: 2

    - name: Timer
      set_fact:
        current_time: "{{ ansible_date_time.date }} {{ ansible_date_time.time }}"
      until: ((current_time | to_datetime) - (start_time | to_datetime)).total_seconds() >= wait_time
      retries: 1000000
      delay: "{{ timer_delay_interval }}"
      register: timer_task

    - name: Waited for task
      debug:
        msg: |
          The timer has completed with {{ timer_task.attempts }} attempts,
          for a total of {{ timer_task.attempts*timer_delay_interval | int }} seconds.
          The original wait time was {{ wait_time }}, which means that intervening
          tasks took {{ wait_time - timer_task.attempts*timer_delay_interval | int }} seconds.

NOTE: The to_datetime filter requires datetimes to be formatted like %Y-%m-%d %H:%M:%S, which is why I'm formatting them that way.



Solution 1:[1]

There are more options.

  1. Run tasks concurrently. Run the module wait_for asynchronously. Then, use async_status to wait for the remaining wait_time to elapse. The number of retries is the difference between wait_time and pause in 1 second delay to ensure the module will cover the remaining time. In practice, the number of retries will be smaller, of course. See comments below about offset
- name: Ensure a certain amount of time has elapsed between two tasks
  hosts: localhost
  gather_facts: false
  vars:
    wait_time: 10
    pause: 5
    offset: 2
  tasks:
    - debug:
        msg: "start_time: {{ '%Y-%m-%d %H:%M:%S'|strftime }}"
    - wait_for:
        timeout: "{{ wait_time|int - offset|int }}"
      async: 20
      poll: 0
      register: async_result
    - pause:
        seconds: "{{ pause }}"
    - async_status:
        jid: "{{ async_result.ansible_job_id }}"
      register: job_result
      until: job_result.finished
      retries: "{{ wait_time|int - pause|int }}"
      delay: 1
    - debug:
        msg: "Something happened at {{ '%Y-%m-%d %H:%M:%S'|strftime }}"

gives (abridged)

TASK [debug] *********************************************************************************
ok: [localhost] => 
  msg: 'start_time: 2022-05-12 09:43:11'

TASK [wait_for] ******************************************************************************
changed: [localhost]

TASK [pause] *********************************************************************************
Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]

TASK [async_status] **************************************************************************
FAILED - RETRYING: [localhost]: async_status (5 retries left).
FAILED - RETRYING: [localhost]: async_status (4 retries left).
ok: [localhost]

TASK [debug] *********************************************************************************
ok: [localhost] => 
  msg: Something happened at 2022-05-12 09:43:21

  1. The next option is the calculation of the remaining time
- name: Ensure a certain amount of time has elapsed between two tasks
  hosts: localhost
  gather_facts: false
  vars:
    wait_time: 10
    pause: 5
    offset: 2
  tasks:
    - set_fact:
        start_time: "{{ '%Y-%m-%d %H:%M:%S'|strftime }}"
        start_time_sec: "{{ '%s'|strftime }}"
    - set_fact:
        stop_time: "{{ '%Y-%m-%d %H:%M:%S'|strftime(start_time_sec|int + wait_time|int) }}"
        stop_time_sec: "{{ start_time_sec|int + wait_time|int }}"
    - debug:
        msg: "start_time: {{ start_time }}"
    - pause:
        seconds: "{{ pause }}"
    - set_fact:
        wait_time: "{{ stop_time_sec|int - '%s'|strftime|int - offset|int }}"
    - debug:
        msg: |-
          wait_time: {{ wait_time }}
      when: debug|d(false)|bool
    - wait_for:
        timeout: "{{ wait_time|int }}"
    - debug:
        msg: "Something happened at {{ '%Y-%m-%d %H:%M:%S'|strftime }}"

gives (abridged)

TASK [debug] *********************************************************************************
ok: [localhost] => 
  msg: 'start_time: 2022-05-12 09:55:08'

TASK [pause] *********************************************************************************
Pausing for 5 seconds
(ctrl+C then 'C' = continue early, ctrl+C then 'A' = abort)
ok: [localhost]

TASK [set_fact] ******************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************
skipping: [localhost]

TASK [wait_for] ******************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************
ok: [localhost] => 
  msg: Something happened at 2022-05-12 09:55:18

Fit offset to your system.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1