From 20e912b73470d47f7287b29fe6315db4795301ac Mon Sep 17 00:00:00 2001 From: akumari Date: Fri, 7 Nov 2025 03:40:25 +0530 Subject: [PATCH 1/7] add quadlet recurring job runner and timers for core rake tasks - define templated Quadlet container `foreman-recurring@.container` via `containers.podman.podman_container` (state: quadlet) - run rake tasks inside the foreman image with same secrets/mounts as foreman.service - so far added per-instance timers for: - reports:daily - db:sessions:clear - reports:expire - audits:expire Refs: theforeman/foremanctl#61 --- src/roles/foreman/defaults/main.yaml | 14 ++++ src/roles/foreman/tasks/main.yaml | 83 +++++++++++++++++++ .../templates/foreman-recurring-sysconfig.j2 | 2 + .../templates/foreman-recurring@.timer.j2 | 12 +++ 4 files changed, 111 insertions(+) create mode 100644 src/roles/foreman/templates/foreman-recurring-sysconfig.j2 create mode 100644 src/roles/foreman/templates/foreman-recurring@.timer.j2 diff --git a/src/roles/foreman/defaults/main.yaml b/src/roles/foreman/defaults/main.yaml index 1aee8039..5a99710a 100644 --- a/src/roles/foreman/defaults/main.yaml +++ b/src/roles/foreman/defaults/main.yaml @@ -16,3 +16,17 @@ foreman_url: "http://{{ ansible_facts['fqdn'] }}:3000" # CPU based calculation is based on https://github.com/puma/puma/blob/master/docs/deployment.md#mri # Memory based calculation is based on https://docs.gitlab.com/ee/install/requirements.html#puma-settings foreman_puma_workers: "{{ [32, ansible_facts['processor_nproc'] * 1.5, (ansible_facts['memtotal_mb'] / 1024) - 1.5] | min | int }}" +foreman_recurring_tasks_enabled: true +foreman_recurring_tasks: + - instance: reports-daily + rake: "reports:daily" + schedule: "daily" + - instance: db-sessions-clear + rake: "db:sessions:clear" + schedule: "*-*-* 23:15:00" + - instance: reports-expire + rake: "reports:expire" + schedule: "*-*-* 07:30:00" + - instance: audits-expire + rake: "audits:expire" + schedule: "*-*-* 01:00:00" diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index f08bdb17..4a0a8308 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -161,6 +161,79 @@ - worker - worker-hosts-queue +- name: Define templated Quadlet for recurring Foreman rake tasks + when: foreman_recurring_tasks_enabled | default(true) + containers.podman.podman_container: + name: "foreman-recurring-%i" + quadlet_filename: "foreman-recurring@" + state: quadlet + image: "{{ foreman_container_image }}:{{ foreman_container_tag }}" + sdnotify: false + network: host + hostname: "{{ ansible_facts['fqdn'] }}" + user: foreman + working_dir: /usr/share/foreman + command: "bash -lc 'foreman-rake ${RAKE_TASK}'" + volume: + - 'foreman-data-run:/var/run/foreman:z' + secrets: + - 'foreman-database-url,type=env,target=DATABASE_URL' + - 'foreman-seed-admin-user,type=env,target=SEED_ADMIN_USER' + - 'foreman-seed-admin-password,type=env,target=SEED_ADMIN_PASSWORD' + - 'foreman-settings-yaml,type=mount,target=/etc/foreman/settings.yaml' + - 'foreman-katello-yaml,type=mount,target=/etc/foreman/plugins/katello.yaml' + - 'foreman-ca-cert,type=mount,target=/etc/foreman/katello-default-ca.crt' + - 'foreman-client-cert,type=mount,target=/etc/foreman/client_cert.pem' + - 'foreman-client-key,type=mount,target=/etc/foreman/client_key.pem' + quadlet_options: + - | + [Install] + WantedBy=default.target foreman.target + [Unit] + PartOf=foreman.target + Requires=foreman.service + After=foreman.service + StartLimitIntervalSec=0 + - | + [Service] + Environment=RAILS_ENV=production + EnvironmentFile=/etc/sysconfig/foreman-recurring@%i + ExecStartPre=/usr/bin/flock -n /run/foreman-recurring-%i.lock -c /usr/bin/true + TimeoutStartSec=30m + TimeoutStopSec=2m + KillMode=mixed + SyslogIdentifier=foreman-recurring-%i + +- name: Render per-instance env (RAKE_TASK) + when: foreman_recurring_tasks_enabled | default(true) + ansible.builtin.template: + src: foreman-recurring-sysconfig.j2 + dest: "/etc/sysconfig/foreman-recurring@{{ item.instance }}" + mode: "0644" + loop: "{{ foreman_recurring_tasks }}" + loop_control: + label: "{{ item.instance }}" + +- name: Render timers for recurring tasks + when: foreman_recurring_tasks_enabled | default(true) + ansible.builtin.template: + src: foreman-recurring@.timer.j2 + dest: "/etc/systemd/system/foreman-recurring@{{ item.instance }}.timer" + mode: "0644" + loop: "{{ foreman_recurring_tasks }}" + loop_control: + label: "{{ item.instance }}" + +- name: Create Quadlet instance links (recurring) + when: foreman_recurring_tasks_enabled | default(true) + ansible.builtin.file: + state: link + src: "/etc/containers/systemd/foreman-recurring@.container" + dest: "/etc/containers/systemd/foreman-recurring@{{ item.instance }}.container" + loop: "{{ foreman_recurring_tasks }}" + loop_control: + label: "{{ item.instance }}" + - name: Run daemon reload to make Quadlet create the service files ansible.builtin.systemd: daemon_reload: true @@ -207,6 +280,16 @@ delay: 5 register: foreman_status +- name: Enable & start recurring timers + when: foreman_recurring_tasks_enabled | default(true) + ansible.builtin.systemd: + name: "foreman-recurring@{{ item.instance }}.timer" + enabled: true + state: started + loop: "{{ foreman_recurring_tasks }}" + loop_control: + label: "{{ item.instance }}" + - name: Wait for Foreman tasks to be ready ansible.builtin.uri: url: '{{ foreman_url }}/api/v2/ping' diff --git a/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 b/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 new file mode 100644 index 00000000..5c20a904 --- /dev/null +++ b/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 @@ -0,0 +1,2 @@ +RAILS_ENV=production +RAKE_TASK={{ item.rake }} diff --git a/src/roles/foreman/templates/foreman-recurring@.timer.j2 b/src/roles/foreman/templates/foreman-recurring@.timer.j2 new file mode 100644 index 00000000..b22dcbe1 --- /dev/null +++ b/src/roles/foreman/templates/foreman-recurring@.timer.j2 @@ -0,0 +1,12 @@ +[Unit] +Description=Timer for Foreman recurring: {{ item.rake }} + +[Timer] +Unit=foreman-recurring@{{ item.instance }}.service +OnCalendar={{ item.schedule }} +Persistent=true +RandomizedDelaySec=120 +AccuracySec=1min + +[Install] +WantedBy=timers.target From 0f506f5259336f706930273a427141bfafce299c Mon Sep 17 00:00:00 2001 From: akumari Date: Tue, 11 Nov 2025 17:11:50 +0530 Subject: [PATCH 2/7] simplify recurring rake timers, use instance-as-task, drop sysconfig --- src/roles/foreman/defaults/main.yaml | 4 ---- src/roles/foreman/tasks/main.yaml | 14 +------------- .../templates/foreman-recurring-sysconfig.j2 | 2 -- .../foreman/templates/foreman-recurring@.timer.j2 | 2 +- 4 files changed, 2 insertions(+), 20 deletions(-) delete mode 100644 src/roles/foreman/templates/foreman-recurring-sysconfig.j2 diff --git a/src/roles/foreman/defaults/main.yaml b/src/roles/foreman/defaults/main.yaml index 5a99710a..71530906 100644 --- a/src/roles/foreman/defaults/main.yaml +++ b/src/roles/foreman/defaults/main.yaml @@ -19,14 +19,10 @@ foreman_puma_workers: "{{ [32, ansible_facts['processor_nproc'] * 1.5, (ansible_ foreman_recurring_tasks_enabled: true foreman_recurring_tasks: - instance: reports-daily - rake: "reports:daily" schedule: "daily" - instance: db-sessions-clear - rake: "db:sessions:clear" schedule: "*-*-* 23:15:00" - instance: reports-expire - rake: "reports:expire" schedule: "*-*-* 07:30:00" - instance: audits-expire - rake: "audits:expire" schedule: "*-*-* 01:00:00" diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index 4a0a8308..ffd550cc 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -173,7 +173,7 @@ hostname: "{{ ansible_facts['fqdn'] }}" user: foreman working_dir: /usr/share/foreman - command: "bash -lc 'foreman-rake ${RAKE_TASK}'" + command: "bash -lc 'foreman-rake %I'" volume: - 'foreman-data-run:/var/run/foreman:z' secrets: @@ -196,24 +196,12 @@ StartLimitIntervalSec=0 - | [Service] - Environment=RAILS_ENV=production - EnvironmentFile=/etc/sysconfig/foreman-recurring@%i ExecStartPre=/usr/bin/flock -n /run/foreman-recurring-%i.lock -c /usr/bin/true TimeoutStartSec=30m TimeoutStopSec=2m KillMode=mixed SyslogIdentifier=foreman-recurring-%i -- name: Render per-instance env (RAKE_TASK) - when: foreman_recurring_tasks_enabled | default(true) - ansible.builtin.template: - src: foreman-recurring-sysconfig.j2 - dest: "/etc/sysconfig/foreman-recurring@{{ item.instance }}" - mode: "0644" - loop: "{{ foreman_recurring_tasks }}" - loop_control: - label: "{{ item.instance }}" - - name: Render timers for recurring tasks when: foreman_recurring_tasks_enabled | default(true) ansible.builtin.template: diff --git a/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 b/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 deleted file mode 100644 index 5c20a904..00000000 --- a/src/roles/foreman/templates/foreman-recurring-sysconfig.j2 +++ /dev/null @@ -1,2 +0,0 @@ -RAILS_ENV=production -RAKE_TASK={{ item.rake }} diff --git a/src/roles/foreman/templates/foreman-recurring@.timer.j2 b/src/roles/foreman/templates/foreman-recurring@.timer.j2 index b22dcbe1..d08a3da3 100644 --- a/src/roles/foreman/templates/foreman-recurring@.timer.j2 +++ b/src/roles/foreman/templates/foreman-recurring@.timer.j2 @@ -1,5 +1,5 @@ [Unit] -Description=Timer for Foreman recurring: {{ item.rake }} +Description=Timer for Foreman recurring: {{ item.instance }} [Timer] Unit=foreman-recurring@{{ item.instance }}.service From 7829890ec1d9116f943ecc468027b534410e86e0 Mon Sep 17 00:00:00 2001 From: akumari Date: Thu, 13 Nov 2025 04:59:33 +0530 Subject: [PATCH 3/7] use explicit rake tasks per instance --- src/roles/foreman/defaults/main.yaml | 16 ++++++++++++++++ src/roles/foreman/tasks/main.yaml | 7 +++++-- .../templates/foreman-recurring@.timer.j2 | 2 +- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/roles/foreman/defaults/main.yaml b/src/roles/foreman/defaults/main.yaml index 71530906..335e736c 100644 --- a/src/roles/foreman/defaults/main.yaml +++ b/src/roles/foreman/defaults/main.yaml @@ -19,10 +19,26 @@ foreman_puma_workers: "{{ [32, ansible_facts['processor_nproc'] * 1.5, (ansible_ foreman_recurring_tasks_enabled: true foreman_recurring_tasks: - instance: reports-daily + rake: "reports:daily" schedule: "daily" - instance: db-sessions-clear + rake: "db:sessions:clear" schedule: "*-*-* 23:15:00" - instance: reports-expire + rake: "reports:expire" schedule: "*-*-* 07:30:00" - instance: audits-expire + rake: "audits:expire" schedule: "*-*-* 01:00:00" + - instance: reports-weekly + rake: "reports:weekly" + schedule: "Sun *-*-* 05:00:00" + - instance: reports-monthly + rake: "reports:monthly" + schedule: "*-*-01 03:00:00" + - instance: notifications-clean + rake: "notifications:clean" + schedule: "Sun *-*-* 06:00:00" + - instance: ldap-refresh_usergroups + rake: "ldap:refresh_usergroups" + schedule: "*-*-* *:00,30:00" diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index ffd550cc..84d5a335 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -163,8 +163,11 @@ - name: Define templated Quadlet for recurring Foreman rake tasks when: foreman_recurring_tasks_enabled | default(true) + loop: "{{ foreman_recurring_tasks }}" + loop_control: + label: "{{ item.instance }}" containers.podman.podman_container: - name: "foreman-recurring-%i" + name: "foreman-recurring-{{ item.instance }}" quadlet_filename: "foreman-recurring@" state: quadlet image: "{{ foreman_container_image }}:{{ foreman_container_tag }}" @@ -173,7 +176,7 @@ hostname: "{{ ansible_facts['fqdn'] }}" user: foreman working_dir: /usr/share/foreman - command: "bash -lc 'foreman-rake %I'" + command: "bash -lc 'foreman-rake {{ item.rake }}'" volume: - 'foreman-data-run:/var/run/foreman:z' secrets: diff --git a/src/roles/foreman/templates/foreman-recurring@.timer.j2 b/src/roles/foreman/templates/foreman-recurring@.timer.j2 index d08a3da3..b22dcbe1 100644 --- a/src/roles/foreman/templates/foreman-recurring@.timer.j2 +++ b/src/roles/foreman/templates/foreman-recurring@.timer.j2 @@ -1,5 +1,5 @@ [Unit] -Description=Timer for Foreman recurring: {{ item.instance }} +Description=Timer for Foreman recurring: {{ item.rake }} [Timer] Unit=foreman-recurring@{{ item.instance }}.service From f446a34fd8b4693b5e66ea6b1d5650a11cc48dfb Mon Sep 17 00:00:00 2001 From: akumari Date: Mon, 17 Nov 2025 16:46:54 +0530 Subject: [PATCH 4/7] drop symlink approach and generate real per-instance --- src/roles/foreman/tasks/main.yaml | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index 84d5a335..7a62e625 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -168,7 +168,7 @@ label: "{{ item.instance }}" containers.podman.podman_container: name: "foreman-recurring-{{ item.instance }}" - quadlet_filename: "foreman-recurring@" + quadlet_filename: "foreman-recurring@{{ item.instance }}" state: quadlet image: "{{ foreman_container_image }}:{{ foreman_container_tag }}" sdnotify: false @@ -192,6 +192,7 @@ - | [Install] WantedBy=default.target foreman.target + - | [Unit] PartOf=foreman.target Requires=foreman.service @@ -215,16 +216,6 @@ loop_control: label: "{{ item.instance }}" -- name: Create Quadlet instance links (recurring) - when: foreman_recurring_tasks_enabled | default(true) - ansible.builtin.file: - state: link - src: "/etc/containers/systemd/foreman-recurring@.container" - dest: "/etc/containers/systemd/foreman-recurring@{{ item.instance }}.container" - loop: "{{ foreman_recurring_tasks }}" - loop_control: - label: "{{ item.instance }}" - - name: Run daemon reload to make Quadlet create the service files ansible.builtin.systemd: daemon_reload: true From 812c042e3b87f9a6a1954134a33ca0d45ac0c390 Mon Sep 17 00:00:00 2001 From: akumari Date: Thu, 27 Nov 2025 04:04:02 +0530 Subject: [PATCH 5/7] updated the schedules to use daily/weekly/monthly --- src/roles/foreman/defaults/main.yaml | 12 ++++++------ src/roles/foreman/tasks/main.yaml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/roles/foreman/defaults/main.yaml b/src/roles/foreman/defaults/main.yaml index 335e736c..94fa8140 100644 --- a/src/roles/foreman/defaults/main.yaml +++ b/src/roles/foreman/defaults/main.yaml @@ -23,22 +23,22 @@ foreman_recurring_tasks: schedule: "daily" - instance: db-sessions-clear rake: "db:sessions:clear" - schedule: "*-*-* 23:15:00" + schedule: "daily" - instance: reports-expire rake: "reports:expire" - schedule: "*-*-* 07:30:00" + schedule: "daily" - instance: audits-expire rake: "audits:expire" - schedule: "*-*-* 01:00:00" + schedule: "daily" - instance: reports-weekly rake: "reports:weekly" - schedule: "Sun *-*-* 05:00:00" + schedule: "weekly" - instance: reports-monthly rake: "reports:monthly" - schedule: "*-*-01 03:00:00" + schedule: "monthly" - instance: notifications-clean rake: "notifications:clean" - schedule: "Sun *-*-* 06:00:00" + schedule: "weekly" - instance: ldap-refresh_usergroups rake: "ldap:refresh_usergroups" schedule: "*-*-* *:00,30:00" diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index 7a62e625..c7667527 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -207,7 +207,7 @@ SyslogIdentifier=foreman-recurring-%i - name: Render timers for recurring tasks - when: foreman_recurring_tasks_enabled | default(true) + when: foreman_recurring_tasks_enabled ansible.builtin.template: src: foreman-recurring@.timer.j2 dest: "/etc/systemd/system/foreman-recurring@{{ item.instance }}.timer" From 3868d820df1530d0246491e1fb6f01d8f4844d2c Mon Sep 17 00:00:00 2001 From: akumari Date: Thu, 27 Nov 2025 13:30:44 +0530 Subject: [PATCH 6/7] align recurring timers with foreman.target and clean up units --- src/roles/foreman/defaults/main.yaml | 1 + src/roles/foreman/tasks/main.yaml | 14 +++----------- .../foreman/templates/foreman-recurring@.timer.j2 | 3 ++- 3 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/roles/foreman/defaults/main.yaml b/src/roles/foreman/defaults/main.yaml index 94fa8140..b9fbe5c7 100644 --- a/src/roles/foreman/defaults/main.yaml +++ b/src/roles/foreman/defaults/main.yaml @@ -16,6 +16,7 @@ foreman_url: "http://{{ ansible_facts['fqdn'] }}:3000" # CPU based calculation is based on https://github.com/puma/puma/blob/master/docs/deployment.md#mri # Memory based calculation is based on https://docs.gitlab.com/ee/install/requirements.html#puma-settings foreman_puma_workers: "{{ [32, ansible_facts['processor_nproc'] * 1.5, (ansible_facts['memtotal_mb'] / 1024) - 1.5] | min | int }}" + foreman_recurring_tasks_enabled: true foreman_recurring_tasks: - instance: reports-daily diff --git a/src/roles/foreman/tasks/main.yaml b/src/roles/foreman/tasks/main.yaml index c7667527..b772d2c5 100644 --- a/src/roles/foreman/tasks/main.yaml +++ b/src/roles/foreman/tasks/main.yaml @@ -162,7 +162,7 @@ - worker-hosts-queue - name: Define templated Quadlet for recurring Foreman rake tasks - when: foreman_recurring_tasks_enabled | default(true) + when: foreman_recurring_tasks_enabled loop: "{{ foreman_recurring_tasks }}" loop_control: label: "{{ item.instance }}" @@ -174,9 +174,7 @@ sdnotify: false network: host hostname: "{{ ansible_facts['fqdn'] }}" - user: foreman - working_dir: /usr/share/foreman - command: "bash -lc 'foreman-rake {{ item.rake }}'" + command: "foreman-rake {{ item.rake }}" volume: - 'foreman-data-run:/var/run/foreman:z' secrets: @@ -189,18 +187,12 @@ - 'foreman-client-cert,type=mount,target=/etc/foreman/client_cert.pem' - 'foreman-client-key,type=mount,target=/etc/foreman/client_key.pem' quadlet_options: - - | - [Install] - WantedBy=default.target foreman.target - | [Unit] PartOf=foreman.target - Requires=foreman.service - After=foreman.service StartLimitIntervalSec=0 - | [Service] - ExecStartPre=/usr/bin/flock -n /run/foreman-recurring-%i.lock -c /usr/bin/true TimeoutStartSec=30m TimeoutStopSec=2m KillMode=mixed @@ -263,7 +255,7 @@ register: foreman_status - name: Enable & start recurring timers - when: foreman_recurring_tasks_enabled | default(true) + when: foreman_recurring_tasks_enabled ansible.builtin.systemd: name: "foreman-recurring@{{ item.instance }}.timer" enabled: true diff --git a/src/roles/foreman/templates/foreman-recurring@.timer.j2 b/src/roles/foreman/templates/foreman-recurring@.timer.j2 index b22dcbe1..c696d969 100644 --- a/src/roles/foreman/templates/foreman-recurring@.timer.j2 +++ b/src/roles/foreman/templates/foreman-recurring@.timer.j2 @@ -1,5 +1,6 @@ [Unit] Description=Timer for Foreman recurring: {{ item.rake }} +PartOf=foreman.target [Timer] Unit=foreman-recurring@{{ item.instance }}.service @@ -9,4 +10,4 @@ RandomizedDelaySec=120 AccuracySec=1min [Install] -WantedBy=timers.target +WantedBy=timers.target foreman.target From 711d4a27a17e075b5c2757e2a69ddc910bb90762 Mon Sep 17 00:00:00 2001 From: akumari Date: Tue, 2 Dec 2025 17:46:12 +0530 Subject: [PATCH 7/7] add tests to verify recurring timer and service units --- tests/foreman_test.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/tests/foreman_test.py b/tests/foreman_test.py index ba0e80fe..40c467c4 100644 --- a/tests/foreman_test.py +++ b/tests/foreman_test.py @@ -5,6 +5,16 @@ FOREMAN_HOST = 'localhost' FOREMAN_PORT = 3000 +RECURRING_INSTANCES = [ + "reports-daily", + "db-sessions-clear", + "reports-expire", + "audits-expire", + "reports-weekly", + "reports-monthly", + "notifications-clean", + "ldap-refresh_usergroups", +] @pytest.fixture(scope="module") @@ -59,3 +69,16 @@ def test_foreman_dynflow_container_instances(server, dynflow_instance): def test_foreman_dynflow_service_instances(server, dynflow_instance): service = server.service(f"dynflow-sidekiq@{dynflow_instance}") assert service.is_running + + +@pytest.mark.parametrize("instance", RECURRING_INSTANCES) +def test_foreman_recurring_timers_enabled_and_running(server, instance): + timer = server.service(f"foreman-recurring@{instance}.timer") + assert timer.is_enabled + assert timer.is_running + + +@pytest.mark.parametrize("instance", RECURRING_INSTANCES) +def test_foreman_recurring_services_exist(server, instance): + service = server.service(f"foreman-recurring@{instance}.service") + assert service.exists