Normalized run_once_, made openresty handlers without when aviable and forced flush in run_once when blocks to avoid handlers with when conditions

This commit is contained in:
Kevin Veen-Birkenbach 2025-08-08 15:32:26 +02:00
parent e675aa5886
commit c7b25ed093
No known key found for this signature in database
GPG Key ID: 44D8F11FD62F878E
58 changed files with 410 additions and 293 deletions

View File

@ -12,10 +12,10 @@
- name: setup git
command: gitconfig --merge-option rebase --name "{{users.client.full_name}}" --email "{{users.client.email}}" --website "{{users.client.website}}" --signing gpg --gpg-key "{{users.client.gpg}}"
when: run_once_gitconfig is not defined
when: run_once_desk_git is not defined
become: false
- name: run the gitconfig tasks once
set_fact:
run_once_gitconfig: true
when: run_once_gitconfig is not defined
run_once_desk_git: true
when: run_once_desk_git is not defined

View File

@ -3,9 +3,9 @@
pacman:
name: fakeroot
state: present
when: run_once_fakeroot is not defined
when: run_once_dev_fakeroot is not defined
- name: run the fakeroot tasks once
set_fact:
run_once_fakeroot: true
when: run_once_fakeroot is not defined
run_once_dev_fakeroot: true
when: run_once_dev_fakeroot is not defined

View File

@ -6,7 +6,7 @@ This Ansible role installs Git on the target system using the Pacman package man
## Overview
Designed for Arch Linux systems, this role leverages the `pacman` module to install Git. It uses a fact (`run_once_git`) to control task execution, ensuring that the Git installation is performed only once per run.
Designed for Arch Linux systems, this role leverages the `pacman` module to install Git. It uses a fact (`run_once_dev_git`) to control task execution, ensuring that the Git installation is performed only once per run.
## Purpose

View File

@ -2,9 +2,9 @@
pacman:
name: git
state: present
when: run_once_git is not defined
when: run_once_dev_git is not defined
- name: run the git tasks once
set_fact:
run_once_git: true
when: run_once_git is not defined
run_once_dev_git: true
when: run_once_dev_git is not defined

View File

@ -3,9 +3,9 @@
pacman:
name: python-pip
state: present
when: run_once_python_pip is not defined
when: run_once_dev_python_pip is not defined
- name: run the python_pip tasks once
set_fact:
run_once_python_pip: true
when: run_once_python_pip is not defined
run_once_dev_python_pip: true
when: run_once_dev_python_pip is not defined

View File

@ -3,9 +3,9 @@
pacman:
name: python-yaml
state: present
when: run_once_python_yaml is not defined
when: run_once_dev_python_yaml is not defined
- name: run the python_yaml tasks once
set_fact:
run_once_python_yaml: true
when: run_once_python_yaml is not defined
run_once_dev_python_yaml: true
when: run_once_dev_python_yaml is not defined

View File

@ -18,10 +18,5 @@
set_fact:
docker_enabled: true
- name: flush docker service
meta: flush_handlers
- name: run the docker tasks once
set_fact:
run_once_docker: true
when: run_once_docker is not defined
- include_tasks: utils/run_once.yml
when: run_once_docker_core is not defined

View File

@ -12,7 +12,7 @@
state: directory
mode: '0755'
become: yes
when: run_once_package_manager is not defined
when: run_once_pkgmgr is not defined
- name: Clone Kevin's Package Manager repository
git:
@ -21,21 +21,21 @@
version: "HEAD"
force: yes
become: yes
when: run_once_package_manager is not defined
when: run_once_pkgmgr is not defined
- name: Ensure main.py is executable
file:
path: "{{ pkgmgr_install_path }}/main.py"
mode: '0755'
become: yes
when: run_once_package_manager is not defined
when: run_once_pkgmgr is not defined
- name: create config.yaml
template:
src: config.yaml.j2
dest: "{{pkgmgr_config_path}}"
become: yes
when: run_once_package_manager is not defined
when: run_once_pkgmgr is not defined
- name: Run the Package Manager install command to create an alias for Kevins package manager
shell: |
@ -45,9 +45,9 @@
chdir: "{{ pkgmgr_install_path }}"
executable: /bin/bash
become: yes
when: run_once_package_manager is not defined
when: run_once_pkgmgr is not defined
- name: run run_once_package_manager tasks once
- name: run run_once_pkgmgr tasks once
set_fact:
run_once_package_managerr: true
when: run_once_package_manager is not defined
run_once_pkgmgr: true
when: run_once_pkgmgr is not defined

View File

@ -2,7 +2,7 @@
template:
src: "srv-proxy-6-6-tls-deploy.sh.j2"
dest: "{{nginx_docker_cert_deploy_script}}"
when: run_once_nginx_docker_cert_deploy is not defined
when: run_once_srv_proxy_6_6_tls_deploy is not defined
notify: restart srv-proxy-6-6-tls-deploy.infinito.service
- name: "create {{cert_mount_directory}}"
@ -28,5 +28,5 @@
- name: run the run_once_srv_proxy_6_6_tls_deploy tasks once
set_fact:
run_once_backup_directory_validator: true
when: run_once_nginx_docker_cert_deploy is not defined
run_once_srv_proxy_6_6_tls_deploy: true
when: run_once_srv_proxy_6_6_tls_deploy is not defined

View File

@ -3,19 +3,19 @@
name:
- certbot-nginx
state: present
when: run_once_nginx_certbot is not defined
when: run_once_srv_web_6_6_tls_renew is not defined
- name: configure srv-web-6-6-tls-renew.infinito.service
template:
src: srv-web-6-6-tls-renew.service.j2
dest: /etc/systemd/system/srv-web-6-6-tls-renew.infinito.service
notify: reload certbot service
when: run_once_nginx_certbot is not defined
when: run_once_srv_web_6_6_tls_renew is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_nginx_certbot is not defined
when: run_once_srv_web_6_6_tls_renew is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
@ -23,9 +23,9 @@
vars:
on_calendar: "{{on_calendar_renew_lets_encrypt_certificates}}"
persistent: "true"
when: run_once_nginx_certbot is not defined
when: run_once_srv_web_6_6_tls_renew is not defined
- name: run the nginx_certbot tasks once
set_fact:
run_once_nginx_certbot: true
when: run_once_nginx_certbot is not defined
run_once_srv_web_6_6_tls_renew: true
when: run_once_srv_web_6_6_tls_renew is not defined

View File

@ -1,12 +1,15 @@
---
- block:
- name: Include openresty
include_role:
name: svc-prx-openresty
public: false
# Explicit set to guaranty that application_id will not be overwritten.
# Should be anyhow the default case
- name: Include openresty
# Outside of run_once block is necessary for handler loading
# Otherwise the when: condition from the block is added to the handlers
# Inside openresty their is a validation that it doesn't run multiple times
include_role:
name: svc-prx-openresty
public: false
# Explicit set to guaranty that application_id will not be overwritten.
# Should be anyhow the default case
- block:
- name: "reset (if enabled)"
include_tasks: 01_reset.yml
when: mode_reset | bool
@ -51,5 +54,5 @@
- name: run {{ role_name }} once
set_fact:
run_once_srv_web_core: true
when: run_once_srv_web_core is not defined
run_once_srv_web_7_4_core: true
when: run_once_srv_web_7_4_core is not defined

View File

@ -2,14 +2,14 @@
pacman:
name: certbot
state: present
when: run_once_certbot is not defined
when: run_once_srv_web_7_7_certbot is not defined
- name: install certbot DNS plugin
pacman:
name: "certbot-dns-{{ certbot_acme_challenge_method }}"
state: present
when:
- run_once_certbot is not defined
- run_once_srv_web_7_7_certbot is not defined
- certbot_acme_challenge_method != 'webroot'
- name: Ensure /etc/certbot directory exists
@ -20,7 +20,7 @@
group: root
mode: '0755'
when:
- run_once_certbot is not defined
- run_once_srv_web_7_7_certbot is not defined
- certbot_acme_challenge_method != 'webroot'
- name: Install plugin credentials file
@ -32,10 +32,10 @@
group: root
mode: '0600'
when:
- run_once_certbot is not defined
- run_once_srv_web_7_7_certbot is not defined
- certbot_acme_challenge_method != 'webroot'
- name: run the certbot role once
set_fact:
run_once_certbot: true
when: run_once_certbot is not defined
run_once_srv_web_7_7_certbot: true
when: run_once_srv_web_7_7_certbot is not defined

View File

@ -3,19 +3,19 @@
src: "letsencrypt.conf.j2"
dest: "{{nginx.directories.http.global}}letsencrypt.conf"
notify: restart openresty
when: run_once_letsencrypt is not defined
when: run_once_srv_web_7_7_letsencrypt is not defined
- name: "Set CAA records for all base domains"
include_tasks: set-caa-records.yml
when:
- dns_provider == 'cloudflare'
- run_once_letsencrypt is not defined
- run_once_srv_web_7_7_letsencrypt is not defined
- name: flush nginx service
meta: flush_handlers
when: run_once_letsencrypt is not defined
when: run_once_srv_web_7_7_letsencrypt is not defined
- name: run the letsencrypt logic just once
set_fact:
run_once_letsencrypt: true
when: run_once_letsencrypt is not defined
run_once_srv_web_7_7_letsencrypt: true
when: run_once_srv_web_7_7_letsencrypt is not defined

View File

@ -4,7 +4,7 @@
state: present
ipam_config:
- subnet: "{{ mariadb_subnet }}"
when: run_once_docker_mariadb is not defined
when: run_once_svc_db_mariadb is not defined
- name: install MariaDB
docker_container:
@ -27,14 +27,14 @@
interval: 10s
timeout: 5s
retries: 18
when: run_once_docker_mariadb is not defined
when: run_once_svc_db_mariadb is not defined
register: setup_mariadb_container_result
- name: install python-mysqlclient
pacman:
name: python-mysqlclient
state: present
when: run_once_docker_mariadb is not defined
when: run_once_svc_db_mariadb is not defined
- name: "Wait until the MariaDB container with hostname '{{ mariadb_name }}' is healthy"
community.docker.docker_container_info:
@ -48,7 +48,7 @@
when:
- setup_mariadb_container_result is defined
- setup_mariadb_container_result.changed
- run_once_docker_mariadb is not defined
- run_once_svc_db_mariadb is not defined
- name: "Initialize database for '{{ database_name }}'"
include_tasks: init.yml
@ -56,5 +56,5 @@
- name: run the docker_mariadb tasks once
set_fact:
run_once_docker_mariadb: true
when: run_once_docker_mariadb is not defined
run_once_svc_db_mariadb: true
when: run_once_svc_db_mariadb is not defined

View File

@ -1,36 +1,30 @@
- name: Create Docker network for PostgreSQL
docker_network:
name: "{{ postgres_network_name }}"
state: present
ipam_config:
- subnet: "{{ postgres_subnet }}"
when: run_once_svc_db_postgres is not defined
- block:
- name: Create Docker network for PostgreSQL
docker_network:
name: "{{ postgres_network_name }}"
state: present
ipam_config:
- subnet: "{{ postgres_subnet }}"
- name: "include docker-compose role"
include_role:
name: docker-compose
when: run_once_svc_db_postgres is not defined
- name: "include docker-compose role"
include_role:
name: docker-compose
- name: Wait for Postgres inside the container
shell: "docker exec {{ postgres_name }} pg_isready -U postgres"
register: pg_ready
until: pg_ready.rc == 0
retries: 30
delay: 5
when:
- run_once_svc_db_postgres is not defined
- name: Wait for Postgres inside the container
shell: "docker exec {{ postgres_name }} pg_isready -U postgres"
register: pg_ready
until: pg_ready.rc == 0
retries: 30
delay: 5
- name: install python-psycopg2
pacman:
name: python-psycopg2
state: present
- name: install python-psycopg2
pacman:
name: python-psycopg2
state: present
- include_tasks: utils/run_once.yml
when: run_once_svc_db_postgres is not defined
- name: "Initialize database for '{{ database_name }}'"
include_tasks: init.yml
when: "{{ postgres_init }}"
- name: Run the docker_postgres tasks once
set_fact:
run_once_svc_db_postgres: true
when: run_once_svc_db_postgres is not defined
when: "{{ postgres_init }}"

View File

@ -1,9 +1,6 @@
- name: "For '{{ application_id }}': Load docker-compose"
include_role:
name: docker-compose
when: run_once_svc_prx_openresty is not defined
- name: Run the docker_postgres tasks once
set_fact:
run_once_svc_prx_openresty: true
- block:
- name: "For '{{ application_id }}': Load docker-compose"
include_role:
name: docker-compose
- include_tasks: utils/run_once.yml
when: run_once_svc_prx_openresty is not defined

View File

@ -2,9 +2,4 @@
systemd:
name: sys-alm-compose.infinito.service
daemon_reload: yes
when: run_once_systemd_notifier_service is not defined
- name: run the systemd_notifier_service tasks once
set_fact:
run_once_systemd_notifier_service: true
when: run_once_systemd_notifier_service is not defined
when: run_once_sys_alm_compose is not defined

View File

@ -4,9 +4,9 @@
src: sys-alm-compose@.service.j2
dest: "/etc/systemd/system/sys-alm-compose.infinito@.service"
notify: "restart sys-alm-compose service"
when: run_once_systemd_notifier_service is not defined
when: run_once_sys_alm_compose is not defined
- name: run the systemd_notifier_service tasks once
set_fact:
run_once_systemd_notifier_service: true
when: run_once_systemd_notifier_service is not defined
run_once_sys_alm_compose: true
when: run_once_sys_alm_compose is not defined

View File

@ -3,22 +3,22 @@
path: "{{systemd_notifier_email_folder}}"
state: directory
mode: 0755
when: run_once_systemd_notifier_email is not defined
when: run_once_sys_alm_email is not defined
- name: configure sys-alm-email.sh
template:
src: sys-alm-email.sh.j2
dest: "{{systemd_notifier_email_folder}}sys-alm-email.sh"
when: run_once_systemd_notifier_email is not defined
when: run_once_sys_alm_email is not defined
- name: configure sys-alm-email.infinito.service
template:
src: sys-alm-email@.service.j2
dest: /etc/systemd/system/sys-alm-email.infinito@.service
notify: restart sys-alm-email service
when: run_once_systemd_notifier_email is not defined
when: run_once_sys_alm_email is not defined
- name: run the systemd_notifier_email tasks once
set_fact:
run_once_systemd_notifier_email: true
when: run_once_systemd_notifier_email is not defined
run_once_sys_alm_email: true
when: run_once_sys_alm_email is not defined

View File

@ -8,35 +8,35 @@
Please provide nonempty values for:
- telegram_bot_token # Your Telegram bots API token
- telegram_chat_id # The Telegram chat ID to send messages to
when: run_once_systemd_notifier_telegram is not defined
when: run_once_sys_alm_telegram is not defined
- name: install curl
pacman:
name: curl
state: present
when: run_once_systemd_notifier_telegram is not defined
when: run_once_sys_alm_telegram is not defined
- name: Create a directory with a subdirectory
ansible.builtin.file:
path: "{{systemd_telegram_folder}}"
state: directory
mode: '0755'
when: run_once_systemd_notifier_telegram is not defined
when: run_once_sys_alm_telegram is not defined
- name: configure sys-alm-telegram.sh
template:
src: sys-alm-telegram.sh.j2
dest: "{{ systemd_telegram_script }}"
when: run_once_systemd_notifier_telegram is not defined
when: run_once_sys_alm_telegram is not defined
- name: configure sys-alm-telegram.infinito.service
template:
src: sys-alm-telegram@.service.j2
dest: "/etc/systemd/system/sys-alm-telegram.infinito@.service"
notify: "restart sys-alm-telegram service"
when: run_once_systemd_notifier_telegram is not defined
when: run_once_sys_alm_telegram is not defined
- name: run the systemd_notifier_telegram tasks once
set_fact:
run_once_systemd_notifier_telegram: true
when: run_once_systemd_notifier_telegram is not defined
run_once_sys_alm_telegram: true
when: run_once_sys_alm_telegram is not defined

View File

@ -3,9 +3,9 @@
name: pkgmgr-install
vars:
package_name: directory-validator
when: run_once_backup_directory_validator is not defined
when: run_once_sys_bkp_directory_validator is not defined
- name: run the backup_directory_validator tasks once
set_fact:
run_once_backup_directory_validator: true
when: run_once_backup_directory_validator is not defined
run_once_sys_bkp_directory_validator: true
when: run_once_sys_bkp_directory_validator is not defined

View File

@ -43,10 +43,10 @@
- name: run the backup_docker_to_local tasks once
set_fact:
run_once_bkp_docker_to_local: true
run_once_sys_bkp_docker_2_loc: true
when:
- run_once_bkp_docker_to_local is not defined
- run_once_sys_bkp_docker_2_loc is not defined
- database_type is defined and database_type
- name: "include seed-database-to-backup.yml"

View File

@ -2,7 +2,7 @@
user:
name: backup
create_home: yes
when: run_once_backups_provider_user is not defined
when: run_once_sys_bkp_provider_user is not defined
- name: create .ssh directory
file:
@ -11,7 +11,7 @@
owner: backup
group: backup
mode: '0700'
when: run_once_backups_provider_user is not defined
when: run_once_sys_bkp_provider_user is not defined
- name: create /home/backup/.ssh/authorized_keys
template:
@ -20,7 +20,7 @@
owner: backup
group: backup
mode: '0644'
when: run_once_backups_provider_user is not defined
when: run_once_sys_bkp_provider_user is not defined
- name: create /home/backup/ssh-wrapper.sh
copy:
@ -29,7 +29,7 @@
owner: backup
group: backup
mode: '0700'
when: run_once_backups_provider_user is not defined
when: run_once_sys_bkp_provider_user is not defined
- name: grant backup sudo rights
copy:
@ -39,9 +39,9 @@
owner: root
group: root
notify: sshd restart
when: run_once_backups_provider_user is not defined
when: run_once_sys_bkp_provider_user is not defined
- name: run the backups_provider_user tasks once
set_fact:
run_once_backups_provider_user: true
when: run_once_backups_provider_user is not defined
run_once_sys_bkp_provider_user: true
when: run_once_sys_bkp_provider_user is not defined

View File

@ -9,19 +9,19 @@
vars:
package_name: dockreap
when:
- run_once_cleanup_docker_anonymous_volumes is not defined
- run_once_sys_cln_anon_volumes is not defined
- docker_bin.stat.exists
- name: run dockreap with --no-confirmation
command:
cmd: "dockreap --no-confirmation"
when:
- run_once_cleanup_docker_anonymous_volumes is not defined
- run_once_sys_cln_anon_volumes is not defined
- docker_bin.stat.exists
- name: mark dockreap as run
set_fact:
run_once_cleanup_docker_anonymous_volumes: true
run_once_sys_cln_anon_volumes: true
when:
- run_once_cleanup_docker_anonymous_volumes is not defined
- run_once_sys_cln_anon_volumes is not defined
- docker_bin.stat.exists

View File

@ -4,29 +4,29 @@
- lsof
- python-psutil
state: present
when: run_once_cleanup_backups_service is not defined
when: run_once_sys_cln_bkps_service is not defined
- name: "create {{cleanup_backups_directory}}"
file:
path: "{{cleanup_backups_directory}}"
state: directory
mode: 0755
when: run_once_cleanup_backups_service is not defined
when: run_once_sys_cln_bkps_service is not defined
- name: create sys-cln-backups.py
copy:
src: "sys-cln-backups.py"
dest: "{{cleanup_backups_directory}}sys-cln-backups.py"
when: run_once_cleanup_backups_service is not defined
when: run_once_sys_cln_bkps_service is not defined
- name: create sys-cln-backups.infinito.service
template:
src: "sys-cln-backups.service.j2"
dest: "/etc/systemd/system/sys-cln-backups.infinito.service"
notify: reload sys-cln-backups.infinito.service
when: run_once_cleanup_backups_service is not defined
when: run_once_sys_cln_bkps_service is not defined
- name: run the cleanup_backups_service tasks once
set_fact:
run_once_cleanup_backups_service: true
when: run_once_cleanup_backups_service is not defined
run_once_sys_cln_bkps_service: true
when: run_once_sys_cln_bkps_service is not defined

View File

@ -1,16 +1,16 @@
- name: set service_name to sys-cln-backups
set_fact:
service_name: "sys-cln-backups"
when: run_once_cleanup_backups_timer is not defined
when: run_once_sys_cln_bkps_timer is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_cleanup_backups}}"
when: run_once_cleanup_backups_timer is not defined
when: run_once_sys_cln_bkps_timer is not defined
- name: run the cleanup_backups_timer tasks once
set_fact:
run_once_cleanup_backups_timer: true
when: run_once_cleanup_backups_timer is not defined
run_once_sys_cln_bkps_timer: true
when: run_once_sys_cln_bkps_timer is not defined

View File

@ -10,7 +10,7 @@ This Ansible role automates the detection, revocation and deletion of unused Let
- Deploys and configures a `sys-cln-certs.infinito.service` systemd unit
- (Optionally) Sets up a recurring cleanup via a systemd timer using the `sys-timer` role
- Integrates with `sys-alm-compose` to send failure notifications
- Ensures idempotent execution with a `run_once_cleanup_certs` flag
- Ensures idempotent execution with a `run_once_sys_cln_certs` flag
## Features
@ -24,7 +24,7 @@ This Ansible role automates the detection, revocation and deletion of unused Let
Optionally wires in a timer via the `sys-timer` role, controlled by the `on_calendar_cleanup_certs` variable.
- **Smart Execution Logic**
Prevents multiple runs in one play by setting a `run_once_cleanup_certs` fact.
Prevents multiple runs in one play by setting a `run_once_sys_cln_certs` fact.
- **Failure Notification**
Triggers `sys-alm-compose.infinito@sys-cln-certs.infinito.service` on failure.

View File

@ -3,28 +3,28 @@
name: pkgmgr-install
vars:
package_name: certreap
when: run_once_cleanup_certs is not defined
when: run_once_sys_cln_certs is not defined
- name: configure sys-cln-certs.infinito.service
template:
src: sys-cln-certs.service.j2
dest: /etc/systemd/system/sys-cln-certs.infinito.service
notify: Reload and restart sys-cln-certs.infinito.service
when: run_once_cleanup_certs is not defined
when: run_once_sys_cln_certs is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_cleanup_certs is not defined
when: run_once_sys_cln_certs is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{ on_calendar_cleanup_certs }}"
when: run_once_cleanup_certs is not defined
when: run_once_sys_cln_certs is not defined
- name: run the run_once_cleanup_certs tasks once
- name: run the run_once_sys_cln_certs tasks once
set_fact:
run_once_cleanup_certs: true
when: run_once_cleanup_certs is not defined
run_once_sys_cln_certs: true
when: run_once_sys_cln_certs is not defined

View File

@ -8,7 +8,7 @@
domain: "{{ item }}"
when:
- mode_cleanup | bool
- run_once_nginx_domains_cleanup is not defined
- run_once_sys_cln_domains is not defined
## The revoking just works for the base domain
#- name: "Revoke Certbot certificate for {{ item }}"
@ -20,7 +20,7 @@
# label: "{{ item }}"
# when:
# - mode_cleanup | bool
# - run_once_nginx_domains_cleanup is not defined
# - run_once_sys_cln_domains is not defined
# register: certbot_revoke_result
# failed_when: >
# certbot_revoke_result.rc != 0 and
@ -38,7 +38,7 @@
# label: "{{ item }}"
# when:
# - mode_cleanup | bool
# - run_once_nginx_domains_cleanup is not defined
# - run_once_sys_cln_domains is not defined
# register: certbot_delete_result
# failed_when: >
# certbot_delete_result.rc != 0 and
@ -48,5 +48,5 @@
- name: run the nginx_domains_cleanup role once
set_fact:
run_once_nginx_domains_cleanup: true
when: run_once_nginx_domains_cleanup is not defined
run_once_sys_cln_domains: true
when: run_once_sys_cln_domains is not defined

View File

@ -3,40 +3,40 @@
name: pkgmgr-install
vars:
package_name: "{{ cln_failed_docker_backups_pkg }}"
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: "Retrieve {{ cln_failed_docker_backups_pkg }} path from pkgmgr"
command: "pkgmgr path {{ cln_failed_docker_backups_pkg }}"
register: pkgmgr_output
changed_when: false
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: Set fact for backup_docker_to_local_cleanup_script
set_fact:
backup_docker_to_local_cleanup_script: "{{ pkgmgr_output.stdout.rstrip('/') ~ '/cleanup-all.sh' }}"
changed_when: false
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: configure sys-cln-faild-bkps.infinito.service
template:
src: sys-cln-faild-bkps.service.j2
dest: /etc/systemd/system/sys-cln-faild-bkps.infinito.service
notify: Reload sys-cln-faild-bkps.infinito.service
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_cleanup_failed_docker}}"
when: run_once_cln_failed_docker_backups is not defined
when: run_once_sys_cln_faild_bkps is not defined
- name: run the cleanup_failed_docker_backups tasks once
set_fact:
run_once_cln_failed_docker_backups: true
when: run_once_cln_failed_docker_backups is not defined
run_once_sys_cln_faild_bkps: true
when: run_once_sys_cln_faild_bkps is not defined

View File

@ -3,7 +3,7 @@
name: pkgmgr-install
vars:
package_name: checkcsp
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: rebuild checkcsp docker image
shell: checkcsp build
@ -14,35 +14,35 @@
path: "{{ health_csp_crawler_folder }}"
state: directory
mode: 0755
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: copy sys-hlth-csp.py
copy:
src: sys-hlth-csp.py
dest: "{{ health_csp_crawler_script }}"
mode: 0755
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: create sys-hlth-csp.infinito.service
template:
src: sys-hlth-csp.service.j2
dest: /etc/systemd/system/sys-hlth-csp.infinito.service
notify: reload sys-hlth-csp.infinito.service
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: set service_name to role_name
set_fact:
service_name: "{{ role_name }}"
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: include systemd timer role
include_role:
name: sys-timer
vars:
on_calendar: "{{ on_calendar_health_csp_crawler }}"
when: run_once_health_csp is not defined
when: run_once_sys_hlth_csp is not defined
- name: run the health_csp tasks once
set_fact:
run_once_health_csp: true
when: run_once_health_csp is not defined
run_once_sys_hlth_csp: true
when: run_once_sys_hlth_csp is not defined

View File

@ -3,34 +3,34 @@
path: "{{health_docker_container_folder}}"
state: directory
mode: 0755
when: run_once_health_docker_container is not defined
when: run_once_sys_hlth_docker_container is not defined
- name: create sys-hlth-docker-container.sh
copy:
src: sys-hlth-docker-container.sh
dest: "{{health_docker_container_folder}}sys-hlth-docker-container.sh"
when: run_once_health_docker_container is not defined
when: run_once_sys_hlth_docker_container is not defined
- name: create sys-hlth-docker-container.infinito.service
template:
src: sys-hlth-docker-container.service.j2
dest: /etc/systemd/system/sys-hlth-docker-container.infinito.service
notify: reload sys-hlth-docker-container.infinito.service
when: run_once_health_docker_container is not defined
when: run_once_sys_hlth_docker_container is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_health_docker_container is not defined
when: run_once_sys_hlth_docker_container is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_health_docker_container}}"
when: run_once_health_docker_container is not defined
when: run_once_sys_hlth_docker_container is not defined
- name: run the health_docker_container tasks once
set_fact:
run_once_health_docker_container: true
when: run_once_health_docker_container is not defined
run_once_sys_hlth_docker_container: true
when: run_once_sys_hlth_docker_container is not defined

View File

@ -3,34 +3,34 @@
path: "{{health_docker_volumes_folder}}"
state: directory
mode: 0755
when: run_once_health_docker_volumes is not defined
when: run_once_sys_hlth_docker_volumes is not defined
- name: create sys-hlth-docker-volumes.sh
copy:
src: sys-hlth-docker-volumes.sh
dest: "{{health_docker_volumes_folder}}sys-hlth-docker-volumes.sh"
when: run_once_health_docker_volumes is not defined
when: run_once_sys_hlth_docker_volumes is not defined
- name: create sys-hlth-docker-volumes.infinito.service
template:
src: sys-hlth-docker-volumes.service.j2
dest: /etc/systemd/system/sys-hlth-docker-volumes.infinito.service
notify: reload sys-hlth-docker-volumes.infinito.service
when: run_once_health_docker_volumes is not defined
when: run_once_sys_hlth_docker_volumes is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_health_docker_volumes is not defined
when: run_once_sys_hlth_docker_volumes is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_health_docker_volumes}}"
when: run_once_health_docker_volumes is not defined
when: run_once_sys_hlth_docker_volumes is not defined
- name: run the health_docker_volumes tasks once
set_fact:
run_once_health_docker_volumes: true
when: run_once_health_docker_volumes is not defined
run_once_sys_hlth_docker_volumes: true
when: run_once_sys_hlth_docker_volumes is not defined

View File

@ -3,34 +3,34 @@
path: "{{health_journalctl_folder}}"
state: directory
mode: 0755
when: run_once_health_journalctl is not defined
when: run_once_sys_hlth_journalctl is not defined
- name: create sys-hlth-journalctl.sh
copy:
src: sys-hlth-journalctl.sh
dest: "{{health_journalctl_folder}}sys-hlth-journalctl.sh"
when: run_once_health_journalctl is not defined
when: run_once_sys_hlth_journalctl is not defined
- name: create sys-hlth-journalctl.infinito.service
template:
src: sys-hlth-journalctl.service.j2
dest: /etc/systemd/system/sys-hlth-journalctl.infinito.service
notify: reload sys-hlth-journalctl.infinito.service
when: run_once_health_journalctl is not defined
when: run_once_sys_hlth_journalctl is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_health_journalctl is not defined
when: run_once_sys_hlth_journalctl is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_health_journalctl}}"
when: run_once_health_journalctl is not defined
when: run_once_sys_hlth_journalctl is not defined
- name: run the health_journalctl tasks once
set_fact:
run_once_health_journalctl: true
when: run_once_health_journalctl is not defined
run_once_sys_hlth_journalctl: true
when: run_once_sys_hlth_journalctl is not defined

View File

@ -2,42 +2,42 @@
pacman:
name: python-requests
state: present
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: "create {{ health_nginx_folder }}"
file:
path: "{{ health_nginx_folder }}"
state: directory
mode: 0755
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: create sys-hlth-webserver.py
template:
src: sys-hlth-webserver.py.j2
dest: "{{ health_nginx_folder }}sys-hlth-webserver.py"
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: create sys-hlth-webserver.infinito.service
template:
src: sys-hlth-webserver.service.j2
dest: /etc/systemd/system/sys-hlth-webserver.infinito.service
notify: reload sys-hlth-webserver.infinito.service
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_health_nginx}}"
when: run_once_health_nginx is not defined
when: run_once_sys_hlth_webserver is not defined
- name: run the health_nginx tasks once
set_fact:
run_once_health_nginx: true
when: run_once_health_nginx is not defined
run_once_sys_hlth_webserver: true
when: run_once_sys_hlth_webserver is not defined

View File

@ -3,11 +3,11 @@
copy:
src: sys-lock.py
dest: "{{path_system_lock_script}}"
when: run_once_system_maintenance_lock is not defined
when: run_once_sys_lock is not defined
## Runtime Variable Setting
- name: run the system_maintenance_service_freezer tasks once
set_fact:
run_once_system_maintenance_lock: true
when: run_once_system_maintenance_lock is not defined
run_once_sys_lock: true
when: run_once_sys_lock is not defined

View File

@ -3,28 +3,28 @@
name: pkgmgr-install
vars:
package_name: btrfs-auto-balancer
when: run_once_system_btrfs_auto_balancer is not defined
when: run_once_sys_rpr_btrfs_blnc is not defined
- name: configure sys-rpr-btrfs-blnc.infinito.service
template:
src: sys-rpr-btrfs-blnc.service.j2
dest: /etc/systemd/system/sys-rpr-btrfs-blnc.infinito.service
notify: reload sys-rpr-btrfs-blnc.infinito.service
when: run_once_system_btrfs_auto_balancer is not defined
when: run_once_sys_rpr_btrfs_blnc is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_system_btrfs_auto_balancer is not defined
when: run_once_sys_rpr_btrfs_blnc is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_btrfs_auto_balancer}}"
when: run_once_system_btrfs_auto_balancer is not defined
when: run_once_sys_rpr_btrfs_blnc is not defined
- name: run the system_btrfs_auto_balancer tasks once
set_fact:
run_once_system_btrfs_auto_balancer: true
when: run_once_system_btrfs_auto_balancer is not defined
run_once_sys_rpr_btrfs_blnc: true
when: run_once_sys_rpr_btrfs_blnc is not defined

View File

@ -26,8 +26,5 @@
vars:
on_calendar: "{{on_calendar_restart_docker}}"
- name: run the restart_docker_volumes tasks once
set_fact:
run_once_restart_docker: true
when: run_once_restart_docker is not defined
- include_tasks: utils/run_once.yml
when: run_once_sys_rpr_docker_hard is not defined

View File

@ -3,35 +3,35 @@
path: "{{heal_docker}}"
state: directory
mode: 0755
when: run_once_heal_docker is not defined
when: run_once_sys_rpr_docker_soft is not defined
- name: create sys-rpr-docker-soft.py
copy:
src: sys-rpr-docker-soft.py
dest: "{{heal_docker}}sys-rpr-docker-soft.py"
notify: restart sys-rpr-docker-soft.infinito.service
when: run_once_heal_docker is not defined
when: run_once_sys_rpr_docker_soft is not defined
- name: create sys-rpr-docker-soft.infinito.service
template:
src: sys-rpr-docker-soft.service.j2
dest: /etc/systemd/system/sys-rpr-docker-soft.infinito.service
notify: restart sys-rpr-docker-soft.infinito.service
when: run_once_heal_docker is not defined
when: run_once_sys_rpr_docker_soft is not defined
- name: "set 'service_name' to '{{ role_name }}'"
set_fact:
service_name: "{{ role_name }}"
when: run_once_heal_docker is not defined
when: run_once_sys_rpr_docker_soft is not defined
- name: "include role for sys-timer for {{service_name}}"
include_role:
name: sys-timer
vars:
on_calendar: "{{on_calendar_heal_docker}}"
when: run_once_heal_docker is not defined
when: run_once_sys_rpr_docker_soft is not defined
- name: run the heal_docker tasks once
set_fact:
run_once_heal_docker: true
when: run_once_heal_docker is not defined
run_once_sys_rpr_docker_soft: true
when: run_once_sys_rpr_docker_soft is not defined

View File

@ -6,7 +6,7 @@ This Ansible role handles resetting and cleaning up “Infinito.Nexus” systemd
When enabled via the `mode_reset` flag, this role will:
1. Run its reset tasks exactly once per play (`run_once_core_daemon` guard).
1. Run its reset tasks exactly once per play (`run_once_sys_rst_daemon` guard).
2. Find all `/etc/systemd/system/*.infinito.service` units.
3. Stop and disable each unit.
4. Remove the unit files.

View File

@ -1,8 +1,8 @@
- name: "reset (if enabled)"
include_tasks: reset.yml
when: mode_reset | bool and run_once_core_daemon is not defined
when: mode_reset | bool and run_once_sys_rst_daemon is not defined
- name: run {{ role_name }} once
set_fact:
run_once_core_daemon: true
when: run_once_core_daemon is not defined
run_once_sys_rst_daemon: true
when: run_once_sys_rst_daemon is not defined

View File

@ -2,9 +2,9 @@
package:
name: sudo
state: present
when: run_once_sudo is not defined
when: run_once_sys_sudo is not defined
- name: run the sudo tasks once
set_fact:
run_once_sudo: true
when: run_once_sudo is not defined
run_once_sys_sudo: true
when: run_once_sys_sudo is not defined

View File

@ -4,16 +4,16 @@
- msmtp
- msmtp-mta
state: present
when: run_once_msmtp is not defined
when: run_once_sys_svc_msmtp is not defined
- name: configure msmtprc.conf.j2
template:
src: "msmtprc.conf.j2"
dest: "/root/.msmtprc"
mode: 600
when: run_once_msmtp is not defined
when: run_once_sys_svc_msmtp is not defined
- name: run the msmtp tasks once
set_fact:
run_once_msmtp: true
when: run_once_msmtp is not defined
run_once_sys_svc_msmtp: true
when: run_once_sys_svc_msmtp is not defined

View File

@ -9,7 +9,7 @@ This Ansible role configures the OpenSSH daemon (`sshd`) by deploying a template
- Renders `sshd_config.j2` into `/etc/ssh/sshd_config` with customizable options
- Sets file ownership (`root:root`) and permissions (`0644`)
- Automatically reloads and restarts the SSH service via a Systemd handler
- Uses a `run_once_sshd` fact to ensure idempotent execution
- Uses a `run_once_sys_svc_sshd` fact to ensure idempotent execution
## Features
@ -25,7 +25,7 @@ This Ansible role configures the OpenSSH daemon (`sshd`) by deploying a template
Handles daemon reload and service restart seamlessly on configuration changes.
- **Idempotency**
Ensures tasks run only once per play by setting the `run_once_sshd` fact.
Ensures tasks run only once per play by setting the `run_once_sys_svc_sshd` fact.
## Further Resources

View File

@ -6,9 +6,9 @@
group: root
mode: '0644'
notify: sshd restart
when: run_once_sshd is not defined
when: run_once_sys_svc_sshd is not defined
- name: run the sshd tasks once
set_fact:
run_once_sshd: true
when: run_once_sshd is not defined
run_once_sys_svc_sshd: true
when: run_once_sys_svc_sshd is not defined

View File

@ -4,27 +4,27 @@
path: "{{ path_docker_compose_instances }}"
register: docker_compose_directory_stat
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- name: "Update with pacman"
include_role:
name: update-pacman
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- ansible_distribution == 'Archlinux'
- name: "Update with apt"
include_role:
name: update-apt
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- ansible_distribution == "Debian"
- name: "Update Docker Images"
include_role:
name: update-docker
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- docker_compose_directory_stat.stat.exists
- name: "Check if yay is installed"
@ -33,13 +33,13 @@
changed_when: false
failed_when: false
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- name: "Update with yay"
include_role:
name: update-yay
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- yay_installed.rc == 0
@ -49,13 +49,13 @@
changed_when: false
failed_when: false
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- name: "Update with pip"
include_role:
name: update-pip
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- name: "Check if pkgmgr command is available"
@ -63,16 +63,16 @@
register: pkgmgr_available
failed_when: false
when:
- run_once_update is not defined
- run_once_update_compose is not defined
- name: "Update all repositories using pkgmgr"
include_role:
name: update-pkgmgr
when:
- pkgmgr_available.rc == 0
- run_once_update is not defined
- run_once_update_compose is not defined
- name: run the update tasks once
set_fact:
run_once_update: true
when: run_once_update is not defined
run_once_update_compose: true
when: run_once_update_compose is not defined

View File

@ -14,5 +14,4 @@
state: present
ipam_config:
- subnet: "{{ networks.local[application_id].subnet }}"
when: run_once_docker_mariadb is not defined

View File

@ -78,7 +78,4 @@
when:
- applications | get_app_conf(application_id, 'features.central_database', False)
- name: run the docker_discourse tasks once
set_fact:
run_once_web_app_discourse: true
when: run_once_web_app_discourse is not defined
- include_tasks: utils/run_once.yml

View File

@ -1,9 +1,9 @@
- name: "load docker, db and proxy for {{application_id}}"
include_role:
name: cmp-db-docker-proxy
when: run_once_docker_libretranslate is not defined
when: run_once_web_app_libretranslate is not defined
- name: run the libretranslate tasks once
set_fact:
run_once_docker_libretranslate: true
when: run_once_docker_libretranslate is not defined
run_once_web_app_libretranslate: true
when: run_once_web_app_libretranslate is not defined

View File

@ -2,16 +2,16 @@
- name: "load docker, db and proxy for {{application_id}}"
include_role:
name: cmp-db-docker-proxy
when: run_once_docker_mailu is not defined
when: run_once_web_app_mailu is not defined
- name: "Include the srv-proxy-6-6-tls-deploy role"
include_role:
name: srv-proxy-6-6-tls-deploy
when: run_once_docker_mailu is not defined
when: run_once_web_app_mailu is not defined
- name: Flush docker service handlers
meta: flush_handlers
when: run_once_docker_mailu is not defined
when: run_once_web_app_mailu is not defined
- name: "Create Mailu accounts"
include_tasks: create-mailu-user.yml
@ -34,7 +34,7 @@
loop: "{{ users | dict2items }}"
loop_control:
loop_var: item
when: run_once_docker_mailu is not defined
when: run_once_web_app_mailu is not defined
- name: Set Mailu DNS records
include_tasks: set-mailu-dns-records.yml
@ -42,5 +42,5 @@
- name: Run the docker_mailu roles once
set_fact:
run_once_docker_mailu: true
when: run_once_docker_mailu is not defined
run_once_web_app_mailu: true
when: run_once_web_app_mailu is not defined

View File

@ -26,10 +26,7 @@
command: "infinito build roles_list --no-signal --alarm-timeout 0 -o {{ mig_roles_meta_list }}"
when:
- mig_build_data
- name: run the web-app-mig tasks once
set_fact:
run_once_docker_web_app_mig: true
- include_tasks: utils/run_once.yml
name: "Setup Meta Infinite Graph"
when: run_once_docker_web_app_mig is not defined
when: run_once_web_app_mig is not defined

View File

@ -60,8 +60,6 @@
src: docker-compose.yml.j2
dest: "{docker_compose.directories.instance}}docker-compose.yml"
notify: docker compose up
- name: run the portfolio tasks once
set_fact:
run_once_web_app_port_ui: true
- include_tasks: utils/run_once.yml
when: run_once_web_app_port_ui is not defined

View File

@ -2,9 +2,9 @@
- name: "load docker and db for {{application_id}}"
include_role:
name: cmp-db-docker
when: run_once_docker_pretix is not defined
when: run_once_web_app_pretix is not defined
- name: run the pretix tasks once
set_fact:
run_once_docker_pretix: true
when: run_once_docker_pretix is not defined
run_once_web_app_pretix: true
when: run_once_web_app_pretix is not defined

View File

@ -10,7 +10,5 @@
dest: "{{ [ docker_repository_path, '.env' ] | path_join }}"
state: link
- name: run the web svc logout tasks once
set_fact:
run_once_web_svc_logout: true
- include_tasks: utils/run_once.yml
when: run_once_web_svc_logout is not defined

View File

@ -14,8 +14,5 @@
- { source: "package.json.j2", target: "{{ simpleicons_host_package_file }}" }
notify:
- docker compose up
- name: run the simpleicons tasks once
set_fact:
run_once_docker_simpleicon: true
when: run_once_docker_simpleicons is not defined
- include_tasks: utils/run_once.yml
when: run_once_web_svc_simpleicons is not defined

8
tasks/utils/run_once.yml Normal file
View File

@ -0,0 +1,8 @@
---
# This is necessary to flush the handlers before the when is set, because otherwise the when will be attached to the handlers
- meta: flush_handlers
- name: Set “run-once” fact for role {{ role_name }}
set_fact:
"{{ 'run_once_' + (role_name | lower | replace('-', '_')) }}": true

View File

@ -0,0 +1,84 @@
import os
import re
import unittest
import glob
import yaml
def find_role_task_yml_files(root_dir):
"""
Find all .yml or .yaml files under roles/*/tasks directories from project root.
"""
pattern_yml = os.path.join(root_dir, 'roles', '*', 'tasks', '*.yml')
pattern_yaml = os.path.join(root_dir, 'roles', '*', 'tasks', '*.yaml')
return glob.glob(pattern_yml) + glob.glob(pattern_yaml)
class RunOnceInclusionTest(unittest.TestCase):
"""
Ensure that every Ansible block in roles/*/tasks with a when condition matching
either the dynamic Jinja scheme or a literal run_once_<role_name> is not defined,
and containing an include_role/import_role also ends with
include_tasks: utils/run_once.yml as its last task.
"""
WHEN_PATTERN = re.compile(
r"(?:run_once_\+\s*\(role_name\s*\|\s*lower\s*\|\s*replace\('\-','\_'\)\)\s*is\s*(?:not\s+)?defined"
r"|run_once_[a-z0-9_]+\s*is\s*(?:not\s+)?defined)",
re.IGNORECASE
)
def test_run_once_blocks(self):
# tests/integration -> tests -> project root
project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..')
)
violations = []
for filepath in find_role_task_yml_files(project_root):
with open(filepath, 'r') as f:
try:
docs = list(yaml.safe_load_all(f))
except yaml.YAMLError as e:
self.fail(f"Failed to parse YAML file {filepath}: {e}")
for doc in docs:
# Determine tasks list
tasks = None
if isinstance(doc, dict) and isinstance(doc.get('tasks'), list):
tasks = doc['tasks']
elif isinstance(doc, list):
tasks = doc
if not tasks:
continue
for item in tasks:
if not isinstance(item, dict) or 'block' not in item:
continue
when = item.get('when')
if not isinstance(when, str) or not self.WHEN_PATTERN.search(when):
continue
block = item['block']
# Check for include_role or import_role within block
has_role_include = any(
isinstance(t, dict) and ('include_role' in t or 'import_role' in t)
for t in block
)
# Check that last task is include_tasks: utils/run_once.yml
last_task = block[-1] if block else None
has_run_once_include = (
isinstance(last_task, dict)
and last_task.get('include_tasks') == 'utils/run_once.yml'
)
if has_role_include and not has_run_once_include:
violations.append(
f"{filepath}: block with when='{when}' missing final include_tasks: utils/run_once.yml"
)
if violations:
self.fail("Run-once blocks missing include_tasks:\n" + "\n".join(violations))
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,58 @@
import os
import glob
import re
import unittest
class RunOnceSchemaTest(unittest.TestCase):
"""
Ensure that any occurrence of 'run_once_' in roles/*/tasks/main.yml
matches the pattern 'run_once_' + (role_name with '-' replaced by '_').
"""
RUN_ONCE_PATTERN = re.compile(r"run_once_([A-Za-z0-9_]+)")
def test_run_once_suffix_matches_role(self):
# Determine project root: two levels up from this test file (tests/integration -> tests -> project)
project_root = os.path.abspath(
os.path.join(os.path.dirname(__file__), '..', '..')
)
violations = []
# Find all roles/*/tasks/main.yml files
pattern = os.path.join(project_root, 'roles', '*', 'tasks', 'main.yml')
for filepath in glob.glob(pattern):
# Extract role name from path
parts = os.path.normpath(filepath).split(os.sep)
try:
role_index = parts.index('roles') + 1
role_name = parts[role_index]
except ValueError:
continue # skip unexpected path
# Compute expected suffix
expected_suffix = role_name.lower().replace('-', '_')
# Read file content
with open(filepath, 'r', encoding='utf-8') as f:
content = f.read()
# Find all run_once_ suffixes
matches = self.RUN_ONCE_PATTERN.findall(content)
if not matches:
# No run_once_ in this file, skip
continue
# Check each occurrence
for suffix in matches:
if suffix != expected_suffix:
violations.append(
f"{filepath}: found run_once_{suffix}, expected run_once_{expected_suffix}"
)
if violations:
self.fail("Invalid run_once_ suffixes found:\n" + "\n".join(violations))
if __name__ == '__main__':
unittest.main()