Add comments and pending-update reporting to auto_updates.yml
Documents each play/task, and adds per-host and final summary output showing how many packages are pending on each host.
This commit is contained in:
@@ -3,35 +3,46 @@
|
|||||||
# dnf-automatic on Rocky Linux. Neither reboots automatically; use
|
# dnf-automatic on Rocky Linux. Neither reboots automatically; use
|
||||||
# reboot_check.yml for that.
|
# reboot_check.yml for that.
|
||||||
|
|
||||||
|
# Play 1: probe every ubuntu/rocky host and sort them into a "reachable_hosts"
|
||||||
|
# group so the two update plays below can skip anything that's offline.
|
||||||
- name: Check Host Availability
|
- name: Check Host Availability
|
||||||
hosts: ubuntu:rocky
|
hosts: ubuntu:rocky
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
ignore_unreachable: true
|
ignore_unreachable: true
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
# Ping each host; failures/unreachables are swallowed so the play keeps
|
||||||
|
# going instead of aborting the whole run.
|
||||||
- name: Ping hosts to check availability
|
- name: Ping hosts to check availability
|
||||||
ansible.builtin.ping:
|
ansible.builtin.ping:
|
||||||
register: ping_result
|
register: ping_result
|
||||||
ignore_errors: true
|
ignore_errors: true
|
||||||
ignore_unreachable: true
|
ignore_unreachable: true
|
||||||
|
|
||||||
|
# Dynamically creates/populates the "reachable_hosts" group, used by the
|
||||||
|
# Ubuntu and Rocky plays' host patterns (ubuntu:&reachable_hosts etc.).
|
||||||
- name: Add reachable hosts to group
|
- name: Add reachable hosts to group
|
||||||
ansible.builtin.group_by:
|
ansible.builtin.group_by:
|
||||||
key: reachable_hosts
|
key: reachable_hosts
|
||||||
when: ping_result is reachable and ping_result is succeeded
|
when: ping_result is reachable and ping_result is succeeded
|
||||||
|
|
||||||
|
# Print a message for any host that failed the ping, so offline hosts
|
||||||
|
# are visible in the run output.
|
||||||
- name: Report offline hosts
|
- name: Report offline hosts
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: "{{ inventory_hostname }} - OFFLINE"
|
msg: "{{ inventory_hostname }} - OFFLINE"
|
||||||
when: ping_result is unreachable or ping_result is failed
|
when: ping_result is unreachable or ping_result is failed
|
||||||
delegate_to: localhost
|
delegate_to: localhost
|
||||||
|
|
||||||
|
# Play 2: install and configure unattended-upgrades on Ubuntu hosts, then
|
||||||
|
# report its status. Does not enable automatic reboots.
|
||||||
- name: Auto Updates - Ubuntu
|
- name: Auto Updates - Ubuntu
|
||||||
hosts: ubuntu:&reachable_hosts
|
hosts: ubuntu:&reachable_hosts
|
||||||
become: true
|
become: true
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
# Make sure the unattended-upgrades package is present.
|
||||||
- name: Install unattended-upgrades
|
- name: Install unattended-upgrades
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: unattended-upgrades
|
name: unattended-upgrades
|
||||||
@@ -39,6 +50,8 @@
|
|||||||
update_cache: true
|
update_cache: true
|
||||||
cache_valid_time: 3600
|
cache_valid_time: 3600
|
||||||
|
|
||||||
|
# Turn on apt's periodic package-list refresh, unattended-upgrade run,
|
||||||
|
# and weekly autoclean.
|
||||||
- name: Enable periodic package list refresh and upgrades
|
- name: Enable periodic package list refresh and upgrades
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
dest: /etc/apt/apt.conf.d/20auto-upgrades
|
||||||
@@ -51,6 +64,8 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
|
|
||||||
|
# Local overrides layered on top of the stock 50unattended-upgrades
|
||||||
|
# config: clean up unused deps, but never reboot automatically.
|
||||||
- name: Configure unattended-upgrades behaviour
|
- name: Configure unattended-upgrades behaviour
|
||||||
ansible.builtin.copy:
|
ansible.builtin.copy:
|
||||||
dest: /etc/apt/apt.conf.d/52unattended-upgrades-local
|
dest: /etc/apt/apt.conf.d/52unattended-upgrades-local
|
||||||
@@ -63,6 +78,8 @@
|
|||||||
group: root
|
group: root
|
||||||
mode: '0644'
|
mode: '0644'
|
||||||
|
|
||||||
|
# Enable and start the systemd timers that actually trigger apt's
|
||||||
|
# periodic update/upgrade cycle.
|
||||||
- name: Enable apt timers
|
- name: Enable apt timers
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: "{{ item }}"
|
name: "{{ item }}"
|
||||||
@@ -72,17 +89,32 @@
|
|||||||
- apt-daily.timer
|
- apt-daily.timer
|
||||||
- apt-daily-upgrade.timer
|
- apt-daily-upgrade.timer
|
||||||
|
|
||||||
|
# Simulate an unattended-upgrade run (no changes made) so the report
|
||||||
|
# below shows what would actually be upgraded.
|
||||||
- name: Dry-run unattended-upgrade
|
- name: Dry-run unattended-upgrade
|
||||||
ansible.builtin.command: unattended-upgrade --dry-run -v
|
ansible.builtin.command: unattended-upgrade --dry-run -v
|
||||||
register: uu_dry_run
|
register: uu_dry_run
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
|
|
||||||
|
# Pull the package list out of the dry-run's "Packages that will be
|
||||||
|
# upgraded: ..." line, used for the per-host and final summary reports.
|
||||||
|
- name: Extract pending package list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pending_update_packages: "{{ (uu_dry_run.stdout | regex_findall('Packages that will be upgraded:\\s*(.*)') | first | default('')).split() }}"
|
||||||
|
|
||||||
|
- name: Compute pending update count
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pending_update_count: "{{ pending_update_packages | length }}"
|
||||||
|
|
||||||
|
# Grab the current state of the apt-daily timers for the report.
|
||||||
- name: List apt timers # noqa: command-instead-of-module (read-only listing)
|
- name: List apt timers # noqa: command-instead-of-module (read-only listing)
|
||||||
ansible.builtin.command: systemctl list-timers apt-daily* --no-pager --all
|
ansible.builtin.command: systemctl list-timers apt-daily* --no-pager --all
|
||||||
register: apt_timers
|
register: apt_timers
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
|
# Summarize timer status, pending update count, and the tail of the
|
||||||
|
# dry-run output per host.
|
||||||
- name: Print report
|
- name: Print report
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
msg: |
|
||||||
@@ -91,22 +123,30 @@
|
|||||||
======================================================
|
======================================================
|
||||||
TIMERS:
|
TIMERS:
|
||||||
{{ apt_timers.stdout }}
|
{{ apt_timers.stdout }}
|
||||||
|
------------------------------------------------------
|
||||||
|
PENDING UPDATES: {{ pending_update_count }}
|
||||||
|
{{ pending_update_packages | join(', ') }}
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
DRY RUN (last lines):
|
DRY RUN (last lines):
|
||||||
{{ (uu_dry_run.stdout_lines + uu_dry_run.stderr_lines)[-6:] | join('\n') }}
|
{{ (uu_dry_run.stdout_lines + uu_dry_run.stderr_lines)[-6:] | join('\n') }}
|
||||||
======================================================
|
======================================================
|
||||||
|
|
||||||
|
# Play 3: install and configure dnf-automatic on Rocky Linux hosts (security
|
||||||
|
# updates only), then report its status.
|
||||||
- name: Auto Updates - Rocky Linux
|
- name: Auto Updates - Rocky Linux
|
||||||
hosts: rocky:&reachable_hosts
|
hosts: rocky:&reachable_hosts
|
||||||
become: true
|
become: true
|
||||||
gather_facts: false
|
gather_facts: false
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
|
# Make sure the dnf-automatic package is present.
|
||||||
- name: Install dnf-automatic
|
- name: Install dnf-automatic
|
||||||
ansible.builtin.dnf:
|
ansible.builtin.dnf:
|
||||||
name: dnf-automatic
|
name: dnf-automatic
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
# Set dnf-automatic to download and apply security updates only, and
|
||||||
|
# log output to stdout (captured by the systemd journal).
|
||||||
- name: Configure dnf-automatic for security updates
|
- name: Configure dnf-automatic for security updates
|
||||||
community.general.ini_file:
|
community.general.ini_file:
|
||||||
path: /etc/dnf/automatic.conf
|
path: /etc/dnf/automatic.conf
|
||||||
@@ -122,17 +162,38 @@
|
|||||||
loop_control:
|
loop_control:
|
||||||
label: "{{ item.section }}/{{ item.option }} = {{ item.value }}"
|
label: "{{ item.section }}/{{ item.option }} = {{ item.value }}"
|
||||||
|
|
||||||
|
# Enable and start the systemd timer that triggers dnf-automatic runs.
|
||||||
- name: Enable dnf-automatic timer
|
- name: Enable dnf-automatic timer
|
||||||
ansible.builtin.systemd:
|
ansible.builtin.systemd:
|
||||||
name: dnf-automatic.timer
|
name: dnf-automatic.timer
|
||||||
enabled: true
|
enabled: true
|
||||||
state: started
|
state: started
|
||||||
|
|
||||||
|
# Grab the current state of the dnf-automatic timer for the report.
|
||||||
- name: List dnf-automatic timers # noqa: command-instead-of-module (read-only listing)
|
- name: List dnf-automatic timers # noqa: command-instead-of-module (read-only listing)
|
||||||
ansible.builtin.command: systemctl list-timers dnf-automatic* --no-pager --all
|
ansible.builtin.command: systemctl list-timers dnf-automatic* --no-pager --all
|
||||||
register: dnf_timers
|
register: dnf_timers
|
||||||
changed_when: false
|
changed_when: false
|
||||||
|
|
||||||
|
# Check what security updates are pending. rc 100 means updates are
|
||||||
|
# available, rc 0 means none, so only other codes count as a failure.
|
||||||
|
- name: Check for pending security updates
|
||||||
|
ansible.builtin.command: dnf check-update --security
|
||||||
|
register: dnf_check
|
||||||
|
changed_when: false
|
||||||
|
failed_when: dnf_check.rc not in [0, 100]
|
||||||
|
|
||||||
|
# Pull the "name.arch version repo" lines out of the check-update
|
||||||
|
# output, used for the per-host and final summary reports.
|
||||||
|
- name: Extract pending package list
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pending_update_packages: "{{ dnf_check.stdout_lines | select('match', '^\\S+\\.\\S+\\s+\\S+\\s+\\S+$') | map('regex_replace', '^(\\S+)\\s.*$', '\\1') | list }}"
|
||||||
|
|
||||||
|
- name: Compute pending update count
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
pending_update_count: "{{ pending_update_packages | length }}"
|
||||||
|
|
||||||
|
# Summarize timer status and pending update count per host.
|
||||||
- name: Print report
|
- name: Print report
|
||||||
ansible.builtin.debug:
|
ansible.builtin.debug:
|
||||||
msg: |
|
msg: |
|
||||||
@@ -141,4 +202,37 @@
|
|||||||
======================================================
|
======================================================
|
||||||
TIMERS:
|
TIMERS:
|
||||||
{{ dnf_timers.stdout }}
|
{{ dnf_timers.stdout }}
|
||||||
|
------------------------------------------------------
|
||||||
|
PENDING UPDATES: {{ pending_update_count }}
|
||||||
|
{{ pending_update_packages | join(', ') }}
|
||||||
======================================================
|
======================================================
|
||||||
|
|
||||||
|
# Play 4: after both OS-specific plays have run, print one consolidated
|
||||||
|
# summary of which hosts have pending updates and how many packages.
|
||||||
|
- name: Summary - Pending Updates
|
||||||
|
hosts: localhost
|
||||||
|
gather_facts: false
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- name: Print hosts with pending updates
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ item }} - {{ hostvars[item].pending_update_count }} package(s) pending: {{ hostvars[item].pending_update_packages | join(', ') }}"
|
||||||
|
loop: "{{ groups['reachable_hosts'] | default([]) }}"
|
||||||
|
when: hostvars[item].pending_update_count | default(0) | int > 0
|
||||||
|
|
||||||
|
- name: Print hosts fully up to date
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: "{{ item }} - up to date"
|
||||||
|
loop: "{{ groups['reachable_hosts'] | default([]) }}"
|
||||||
|
when: hostvars[item].pending_update_count is defined and hostvars[item].pending_update_count | int == 0
|
||||||
|
|
||||||
|
- name: Print totals
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
{{ groups['reachable_hosts'] | default([]) | length }} host(s) reachable,
|
||||||
|
{{ groups['reachable_hosts'] | default([])
|
||||||
|
| map('extract', hostvars, 'pending_update_count') | select('defined') | map('int') | select('>', 0) | list | length }}
|
||||||
|
with pending updates,
|
||||||
|
{{ groups['reachable_hosts'] | default([])
|
||||||
|
| map('extract', hostvars, 'pending_update_count') | select('defined') | map('int') | sum }}
|
||||||
|
package(s) total pending.
|
||||||
|
|||||||
Reference in New Issue
Block a user