Complex Cronline do not work

Problem:
We’re trying to create some Recurring Logic that require complex Cronlines.
For example, we want to install updates on a subset of the machines on the second Tuesday of every month.
The Cronline for this situation looks like this:

0 8 7-14 * */2 - https://crontab.guru/#0_8_8-14__/2

irb(main):002:0> ForemanTasks::RecurringLogic.find(30)
=> #<ForemanTasks::RecurringLogic id: 30, cron_line: “0 8 7-14 * */2”, end_time: nil, max_iteration: nil, iteration: 1, task_group_id: 146, state: “active”, triggering_id: 80, purpose: nil>

At 08AM, on the days between the 7th and 14th (all possible days where the second Tuesday of the month can fall), on every month, on every second day of the week

We also tested

0 8 7-14 * 2 - https://crontab.guru/#0_8_8-14_*_2

irb(main):004:0> ForemanTasks::RecurringLogic.find(35)
=> #<ForemanTasks::RecurringLogic id: 35, cron_line: “0 8 8-14 * 2”, end_time: nil, max_iteration: nil, iteration: 1, task_group_id: 159, state: “active”, triggering_id: 86, purpose: nil>

Which is similar as the one above but instead of on every second day of the week it’s on Tuesday, but not even the crontab.guru shows the expected date of the next occurrence.

Another example: trying to run something every 4th Wednesday (today)

30 12 22-31 * */4 - https://crontab.guru/#30_12_22-31__/4

It correctly scheduled and run the job today, but the next occurrence is tomorrow which is Thursday.

irb(main):003:0> ForemanTasks::RecurringLogic.find(33)
=> #<ForemanTasks::RecurringLogic id: 33, cron_line: “30 12 22-31 * */4”, end_time: nil, max_iteration: nil, iteration: 2, task_group_id: 153, state: “active”, triggering_id: 83, purpose: nil>

Expected outcome:

We expected the schedules to be correct.

Foreman and Proxy versions:

  • foreman: 3.5.1

Foreman and Proxy plugin versions:

  • foreman-tasks: 7.1.1
  • foreman_remote_execution: 8.2.0
  • katello: 4.7.2

Distribution and version:
Oracle Linux 8.7

Other relevant data:

@MariaAga do you have an idea about what’s happening here?

We’re using a third party library for dealing with cron lines and this seems to be a known issue there[1]. However, the library was last updated in 2016 so whether this will get fixed there is questionable at best.

[1] - Bad calculation · Issue #28 · siebertm/parse-cron · GitHub

1 Like

@aruzicka do you think it’s worth a redmine? The PR for that issue has been open for 7 years, doesn’t bode well for the library in general.

As in have a redmine for swapping the library for something else? Probably

Sounds good, @mindo do you mind making an issue for the bug you’ve found? That way you’ll get updates on it.

Link: Foreman

I’ve had similar issues when trying to set up Ansible playbooks that I wanted to run the Xth Monday etc. I eventually moved to SystemD user timers, with the following playbook:

---
- name: 'Ensure systemd timers for Ansible Controller'
  hosts: 'ansible_controller'
  become_user: 'ansible'
  gather_facts: false
  vars:
    - systemd_timers:
        - name: '<name>'
          timespec: 'Mon *-*-8..14 06:00:00'  # 2nd Monday of the month
          command: '<job>'
  tasks:
    - name: 'Ensure Environment vars file for systems'
      ansible.builtin.copy:
        content: |
          PATH="/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/bin:/sbin"
        dest: '/home/ansible/.config/systemd/ansible_envvars'
    - name: 'Ensure Timer unit files'
      ansible.builtin.copy:
        content: |
          [Unit]
          Description={{ timer['description'] | default(timer['name']) }}
          
          [Timer]
          OnCalendar={{ timer['timespec'] }}
          Persistent=false
         
          [Install]
          WantedBy=timers.target
        dest: "/home/ansible/.config/systemd/user/{{ timer['name'] }}.timer"
        mode: '0644'
      loop: "{{ systemd_timers }}"
      loop_control:
        loop_var: 'timer'

    - name: 'Ensure Service unit files'
      ansible.builtin.copy:
        content: |
          [Unit]
          Description={{ timer['description'] | default(timer['name']) }}
          RefuseManualStart=true
          
          [Service]
          Type=oneshot
          ExecStart={{ timer['command'] }}
          EnvironmentFile=/home/ansible/.config/systemd/ansible_envvars
        dest: "/home/ansible/.config/systemd/user/{{ timer['name'] }}.service"
        mode: '0644'
      loop: "{{ systemd_timers }}"
      loop_control:
        loop_var: 'timer'

    - name: 'Ensure timers enabled'
      ansible.builtin.systemd:
        name: "{{ timer['name'] }}.timer"
        state: 'started'
        enabled: true
        scope: 'user'
        daemon_reload: true
      loop: "{{ systemd_timers }}"
      loop_control:
        loop_var: 'timer'

This has the added benefit that SystemD can tell you the next occurrence of the timer when you run systemctl --user list-timers

1 Like

Hello,

Sorry for the late reply.
I took so long to open the ticket that in the meantime another was opened to replace the cron parser library and there’s already an PR.