Monitoring a Repository for Changes with Ansible and Ascender
I recently had an ask of “how do we know when a package is updated in a repository?” This ask is for all packages in a repo, not just the installed ones.
To solve this problem, I wrote a simple playbook that fetches a list of all available packages from a repository, compares it to the previous day’s list, and sends an email if a change is detected.
It should be as simple as scheduling the playbook to run nightly on one of your servers using the repository you want to monitor!
Playbook
The playbook can be found here.
As of the time of this writing, the playbook looks like this:
---
- name: Playbook to monitor packages in a repository and alert when there's a
new package
hosts: Greg-rocky9
gather_facts: false
vars:
# Where will the package files be stored
file_location: /root
# Email settings
email_subject: Repository Package Version Change
email_host: relay.gregsowell.com
email_from: system@gregsowell.com
email_to: greg@gregsowell.com
tasks:
- name: Use dnf shell command to get a list of all packages in repository
ansible.builtin.shell: "dnf list available | tail -n +3 > {{ file_location
}}/available-packages-temp.txt"
# register: installed_packages
# changed_when: false
# - name: Debug installed_packages variable
# ansible.builtin.debug:
# var: installed_packages.stdout_lines
- name: Check if available-packages.txt file exists
ansible.builtin.stat:
path: "{{ file_location }}/available-packages.txt"
register: file_check
- name: Perform block if file_check file doesn't exist
when: file_check.stat.exists == false
block:
- name: Copy available-packages-temp.txt to available-packages.txt
ansible.builtin.shell: "cp {{ file_location }}/available-packages-temp.txt {{
file_location }}/available-packages.txt"
- name: Compare available-packages-temp.txt and available-packages.txt
ansible.builtin.shell: "diff {{ file_location }}/available-packages-temp.txt {{
file_location }}/available-packages.txt"
register: package_change
changed_when: false
failed_when: package_change.rc != 1 and package_change.rc != 0
- name: Block to process when package_change is not empty
when: package_change.stdout | default("") != ""
block:
- name: Copy package files from remote host to localhost
ansible.builtin.fetch:
src: "{{ item }}"
dest: "{{ playbook_dir }}/"
flat: yes
loop:
- "{{ file_location }}/available-packages-temp.txt"
- "{{ file_location }}/available-packages.txt"
- name: Copy available-packages-temp.txt to available-packages.txt
ansible.builtin.copy:
src: "{{ file_location }}/available-packages-temp.txt"
dest: "{{ file_location }}/available-packages.txt"
force: yes
remote_src: true
- name: Send Email
community.general.mail:
host: "{{ email_host }}"
from: "{{ email_from }}"
port: 25
to: "{{ email_to }}"
subject: "[Ansible] {{ email_subject }}"
body: "{{ package_change.stdout_lines }}"
attach:
- "{{ playbook_dir }}/available-packages-temp.txt"
- "{{ playbook_dir }}/available-packages.txt"
subtype: html
delegate_to: localhost
run_once: true
I’m going to pick a few interesting pieces to discuss here.
First is task number 1. I use the dnf list available command piped to tail to remove the first two lines (line one shows date of command run and line two is column headings). If there were specific repositories you were looking to monitor, you could further pipe this to a grep of those specific repos. Lastly, it all gets piped to a temp file for comparison.
- name: Use dnf shell command to get a list of all packages in repository
ansible.builtin.shell: "dnf list available | tail -n +3 > {{ file_location
}}/available-packages-temp.txt"
The diff command is then used to quickly detect if the files are different, and if they are, note what those differences are:
- name: Check if available-packages.txt file exists
ansible.builtin.stat:
path: "{{ file_location }}/available-packages.txt"
register: file_check
If a difference has been detected, then first copy those files to the container, overwrite the packages file with the temp file (it’s our new baseline), and last, send an email with the diff info and files attached:
- name: Block to process when package_change is not empty
when: package_change.stdout | default("") != ""
block:
- name: Copy package files from remote host to localhost
ansible.builtin.fetch:
src: "{{ item }}"
dest: "{{ playbook_dir }}/"
flat: yes
loop:
- "{{ file_location }}/available-packages-temp.txt"
- "{{ file_location }}/available-packages.txt"
- name: Copy available-packages-temp.txt to available-packages.txt
ansible.builtin.copy:
src: "{{ file_location }}/available-packages-temp.txt"
dest: "{{ file_location }}/available-packages.txt"
force: yes
remote_src: true
- name: Send Email
community.general.mail:
host: "{{ email_host }}"
from: "{{ email_from }}"
port: 25
to: "{{ email_to }}"
subject: "[Ansible] {{ email_subject }}"
body: "{{ package_change.stdout_lines }}"
attach:
- "{{ playbook_dir }}/available-packages-temp.txt"
- "{{ playbook_dir }}/available-packages.txt"
subtype: html
delegate_to: localhost
run_once: true
Conclusion As noted, this can be extended pretty easily, but I’m curious how you might go about that… what would you adjust? I appreciate any and all feedback!
As always, thanks for reading, and happy automating.