--- - name: Patch Test Rocky servers and report to generic webhook hosts: rocky_servers become: true gather_facts: true vars: discord_webhook_id: "1396804024942198794" discord_webhook_token: "zdT2wisqhAZuZ_UPKmAmtV0z_rX-PZnyHIXsjpPHQO3uakBO2xQ3R44kOxb9QZ4QxebL" tasks: - name: Verify target is RedHat family ansible.builtin.assert: that: - ansible_os_family == "RedHat" fail_msg: "This playbook is intended for Rocky/RHEL-family systems only." success_msg: "Target is RedHat family." - name: Refresh DNF metadata ansible.builtin.raw: | dnf -y makecache register: dnf_cache_result changed_when: false failed_when: dnf_cache_result.rc != 0 become: true - name: Check for available updates ansible.builtin.raw: | dnf check-update register: dnf_check_update_result changed_when: false failed_when: dnf_check_update_result.rc not in [0, 100] become: true - name: Upgrade packages ansible.builtin.raw: | dnf -y upgrade register: dnf_upgrade_result changed_when: dnf_check_update_result.rc == 100 failed_when: dnf_upgrade_result.rc != 0 become: true when: dnf_check_update_result.rc == 100 - name: Mark package upgrade unchanged when no updates exist ansible.builtin.set_fact: dnf_upgrade_result: changed: false rc: 0 stdout: "No package updates available" when: dnf_check_update_result.rc == 0 - name: Autoremove unused packages ansible.builtin.raw: | dnf -y autoremove register: dnf_autoremove_result changed_when: > 'Removed:' in dnf_autoremove_result.stdout or 'Removing' in dnf_autoremove_result.stdout failed_when: dnf_autoremove_result.rc != 0 become: true - name: Check if reboot is required ansible.builtin.command: cmd: needs-restarting -r register: reboot_required_check changed_when: false failed_when: reboot_required_check.rc not in [0, 1] - name: Set reboot required fact ansible.builtin.set_fact: reboot_required: "{{ reboot_required_check.rc == 1 }}" - name: Reboot server if required ansible.builtin.reboot: pre_reboot_delay: 60 post_reboot_delay: 60 reboot_timeout: 600 msg: "Rebooting in 60 seconds, initiated by Ansible" when: reboot_required register: reboot_result - name: Build per-host summary ansible.builtin.set_fact: patch_summary: | Host: {{ inventory_hostname }} OS: {{ ansible_distribution }} {{ ansible_distribution_version }} DNF Utils Changed: {{ 'Yes' if (dnf_utils_result.changed | default(false)) else 'No' }} DNF Cache Updated: {{ 'Yes' if (dnf_cache_result.changed | default(false)) else 'No' }} Packages Upgraded: {{ 'Yes' if (dnf_upgrade_result.changed | default(false)) else 'No' }} Autoremove Changed: {{ 'Yes' if (dnf_autoremove_result.changed | default(false)) else 'No' }} Reboot Required: {{ 'Yes' if (reboot_required | default(false)) else 'No' }} Reboot Performed: {{ 'Yes' if (reboot_result.changed | default(false)) else 'No' }} Reboot Check Output: {{ reboot_required_check.stdout | default('') | indent(10, true) }} - name: Build per-host summary ansible.builtin.set_fact: patch_summary: | Host: {{ inventory_hostname }} OS: {{ ansible_distribution }} {{ ansible_distribution_version }} DNF Utils Changed: {{ 'Yes' if (dnf_utils_result.changed | default(false)) else 'No' }} DNF Cache Updated: {{ 'Yes' if (dnf_cache_result.changed | default(false)) else 'No' }} Packages Upgraded: {{ 'Yes' if (dnf_upgrade_result.changed | default(false)) else 'No' }} Autoremove Changed: {{ 'Yes' if (dnf_autoremove_result.changed | default(false)) else 'No' }} Reboot Required: {{ 'Yes' if (reboot_required | default(false)) else 'No' }} Reboot Performed: {{ 'Yes' if (reboot_result.changed | default(false)) else 'No' }} Reboot Check Output: {{ reboot_required_check.stdout | default('') | indent(10, true) }} - name: Send Discord Rocky update report community.general.discord: webhook_id: "{{ discord_webhook_id }}" webhook_token: "{{ discord_webhook_token }}" username: "Ansible" content: | ====================================== Rocky Servers Update Report Generated: {{ lookup('pipe', 'date +%Y-%m-%dT%H:%M:%SZ') }} ====================================== Total Hosts: {{ ansible_play_hosts_all | length }} {% for host in ansible_play_hosts_all | sort %} {{ hostvars[host].patch_summary | default('Host: ' ~ host ~ '\n No summary generated\n') }} {% endfor %} ====================================== Summary ====================================== Hosts Requiring Reboot: {{ ansible_play_hosts_all | map('extract', hostvars) | selectattr('reboot_required', 'defined') | selectattr('reboot_required') | list | length }} Hosts Rebooted: {{ ansible_play_hosts_all | map('extract', hostvars) | selectattr('reboot_result.changed', 'defined') | selectattr('reboot_result.changed') | list | length }} delegate_to: localhost run_once: true become: false