Blog

Ansible - Querying Linux Update Status

7/31/2024 3 min read

Imagine being able to retrieve the patch status of hundreds of Linux servers with a simple script and display it neatly in a table. Sounds practical, right?
This small Ansible playbook makes exactly that possible.

The playbook checks the number of pending updates on Debian-based Linux systems and sends the results in a tabular format by email.
This way, you can instantly see whether a server was forgotten or if automatic updates are working properly.

The output includes:

  • Hostname
  • Number of pending updates (numeric)

Ansible Playbook: Number of Pending Updates

---
- name: Check update status and send email
  hosts: all
  become: yes
  vars:
    mailserver: dein-mailserver
    mail_sender: ansible@example.com
    mail_recipient: admin@example.com

  pre_tasks:
    # Ensure the temporary file for storing update statuses is empty and exists
    - name: Ensure /tmp/all_update_status.txt is empty and exists
      copy:
        content: ""
        dest: /tmp/all_update_status.txt
      delegate_to: localhost
      run_once: true

  tasks:
    # Update the APT cache
    - name: Update the apt cache
      apt:
        update_cache: yes

    # Check for available updates on Debian systems
    - name: Check for available updates on Debian
      shell: apt list --upgradable 2>/dev/null | grep -v 'Listing...'
      register: update_output

    # Ensure update_output.stdout_lines is defined to avoid errors
    - name: Ensure update_output.stdout_lines is defined
      set_fact:
        update_lines: "{{ update_output.stdout_lines | default([]) }}"

    # Format the update status for the current host
    - name: Format update status
      set_fact:
        host_update_status: |
          
            {{ inventory_hostname }}
            {{ update_lines | length }}
          
      delegate_to: localhost

    # Add the formatted update status to the consolidated list
    - name: Add host update status to list
      lineinfile:
        path: /tmp/all_update_status.txt
        line: "{{ host_update_status }}"
      delegate_to: localhost

- name: Send consolidated update status email
  hosts: localhost
  gather_facts: no
  vars:
    msmtp_config_path: "/root/.msmtprc"
    mail_sender: ansible@example.com
    mail_recipient: admin@example.com
  tasks:
    # Read all update statuses from the temporary file
    - name: Read all update statuses
      command: cat /tmp/all_update_status.txt
      register: all_update_status_content

    # Send the update status email using msmtp
    - name: Send update status email with msmtp
      shell: |
        echo -e "From: {{ mail_sender }}\nTo: {{ mail_recipient }}\nSubject: Debian Update Status\nMIME-Version: 1.0\nContent-Type: text/html\n\nHostnameAusstehende Updates{{ all_update_status_content.stdout }}" | msmtp --file={{ msmtp_config_path }} "{{ mail_recipient }}"
      register: msmtp_result
      ignore_errors: yes

What the Ansible Playbook does

  1. Empties and creates a collection file on the control host
  2. Updates the APT cache on all target systems
  3. Determines the number of available updates
  4. Constructs HTML table rows from this data
  5. Consolidates all hosts into a single table
  6. Sends the result via email (HTML format)

Variant: Pending Updates including Package Names

If you want not only the number but also the names of the pending updates, you can use this playbook.


Ansible Playbook: Updates including Package Names

---
- name: Check update status and send email
  hosts: all
  become: yes
  vars:
    mailserver: dein_mail_server
    mail_sender: sender@example.com
    mail_recipient: recipient@example.com

  pre_tasks:
    - name: Ensure /tmp/all_update_status.txt is empty and exists
      copy:
        content: ""
        dest: /tmp/all_update_status.txt
      delegate_to: localhost
      run_once: true

  tasks:
    - name: Update the apt cache
      apt:
        update_cache: yes

    - name: Check for available updates on Debian
      shell: apt list --upgradable 2>/dev/null | grep -v 'Listing...'
      register: update_output

    - name: Ensure update_output.stdout_lines is defined
      set_fact:
        update_lines: "{{ update_output.stdout_lines | default([]) }}"

    - name: Format update status
      set_fact:
        host_update_status: |
          
            {{ inventory_hostname }}
            {{ update_lines | length }}
            
              {% for line in update_lines %}
                {{ line }}

              {% endfor %}
            
          
      delegate_to: localhost

    - name: Add host update status to list
      lineinfile:
        path: /tmp/all_update_status.txt
        line: "{{ host_update_status }}"
      delegate_to: localhost

- name: Send consolidated update status email
  hosts: localhost
  gather_facts: no
  vars:
    msmtp_config_path: "/root/.msmtprc"
    mail_sender: sender@example.com
    mail_recipient: recipient@example.com
  tasks:
    - name: Read all update statuses
      command: cat /tmp/all_update_status.txt
      register: all_update_status_content

    - name: Send update status email with msmtp
      shell: |
        echo -e "From: {{ mail_sender }}\nTo: {{ mail_recipient }}\nSubject: Debian Update Status\nMIME-Version: 1.0\nContent-Type: text/html\n\nHostnameAnzahl der UpdatesUpdate-Namen{{ all_update_status_content.stdout }}" | msmtp --file={{ msmtp_config_path }} "{{ mail_recipient }}"
      register: msmtp_result
      ignore_errors: yes

Extension Ideas

  • Sorting by number of updates
  • Highlighting critical packages
  • CSV or JSON export
  • Slack / Matrix / Teams notifications instead of email
  • Cron execution on the control node