diff --git a/apt_updates/defaults/main.yml b/apt_updates/defaults/main.yml new file mode 100644 index 0000000..28dbd55 --- /dev/null +++ b/apt_updates/defaults/main.yml @@ -0,0 +1,2 @@ +# Directory the node exporter reads textfile metrics from (the Debian default). +apt_updates_textfile_dir: /var/lib/prometheus/node-exporter diff --git a/apt_updates/tasks/main.yml b/apt_updates/tasks/main.yml new file mode 100644 index 0000000..7611b1a --- /dev/null +++ b/apt_updates/tasks/main.yml @@ -0,0 +1,41 @@ +# The metrics run after apt-daily (update) and apt-daily-upgrade (upgrade), so they show the state apt +# just reached without a timer of their own. The "-" lets a failing metrics run leave apt's units green. +- name: Create the textfile directory + ansible.builtin.file: + path: "{{ apt_updates_textfile_dir }}" + state: directory + mode: "0755" + +- name: Install the apt metrics script + ansible.builtin.template: + src: apt-updates-metrics.j2 + dest: /usr/local/sbin/apt-updates-metrics + mode: "0755" + +- name: Create the apt timers' drop-in directories + ansible.builtin.file: + path: /etc/systemd/system/{{ item }}.service.d + state: directory + mode: "0755" + loop: + - apt-daily + - apt-daily-upgrade + +- name: Run the apt metrics after the apt timers' services + ansible.builtin.copy: + dest: /etc/systemd/system/{{ item }}.service.d/apt-updates-metrics.conf + mode: "0644" + content: | + [Service] + ExecStartPost=-/usr/local/sbin/apt-updates-metrics + loop: + - apt-daily + - apt-daily-upgrade + register: apt_updates_dropins + +- name: Reload systemd after the apt metrics drop-ins + ansible.builtin.systemd: + daemon_reload: true + when: apt_updates_dropins.changed + # Check mode does not write the drop-ins. + ignore_errors: "{{ ansible_check_mode }}" diff --git a/apt_updates/templates/apt-updates-metrics.j2 b/apt_updates/templates/apt-updates-metrics.j2 new file mode 100644 index 0000000..1c686c1 --- /dev/null +++ b/apt_updates/templates/apt-updates-metrics.j2 @@ -0,0 +1,63 @@ +#!/bin/bash +# Pending upgrades, held packages, reboot flag and age of the package lists as node exporter textfile +# metrics. apt-daily and apt-daily-upgrade run it after their update and upgrade (drop-ins). +set -euo pipefail + +host={{ inventory_hostname }} +textfile_dir={{ apt_updates_textfile_dir }} +out=$textfile_dir/apt_updates.prom +tmp=$(mktemp "$textfile_dir/.apt_updates.XXXXXX") +trap 'rm -f "$tmp"' EXIT + +arch=$(dpkg --print-architecture) + +# The simulated upgrade lists "Inst [] ( [])" per package. +# An origin containing "security" counts as security, everything else as regular. +pending=$(apt-get -s upgrade | awk -v host="$host" -v arch="$arch" ' + /^Inst / { + line = $0 + sub(/^[^(]*\(/, "", line) + sub(/\).*$/, "", line) + n = split(line, f, " ") + pkg_arch = f[n] + gsub(/\[|\]/, "", pkg_arch) + origin = (n >= 3) ? f[2] : "" + kind = (tolower(origin) ~ /security/) ? "security" : "regular" + count[kind, pkg_arch]++ + } + END { + if (!(("security", arch) in count)) count["security", arch] = 0 + if (!(("regular", arch) in count)) count["regular", arch] = 0 + for (key in count) { + split(key, k, SUBSEP) + printf "apt_upgrades_pending{host=\"%s\",origin=\"%s\",arch=\"%s\"} %d\n", host, k[1], k[2], count[key] + } + }') + +held=$(apt-mark showhold | wc -l) + +reboot=0 +if [[ -e /run/reboot-required ]]; then + reboot=1 +fi + +# Debian writes update-stamp, Ubuntu update-success-stamp; both are touched by a successful update. +stamp=/var/lib/apt/periodic/update-success-stamp +if [[ ! -e $stamp ]]; then + stamp=/var/lib/apt/periodic/update-stamp +fi +cache=$(stat -c %Y "$stamp") + +{ + echo '# TYPE apt_upgrades_pending gauge' + echo "$pending" + echo '# TYPE apt_upgrades_held gauge' + echo "apt_upgrades_held{host=\"$host\"} $held" + echo '# TYPE node_reboot_required gauge' + echo "node_reboot_required{host=\"$host\"} $reboot" + echo '# TYPE apt_package_cache_timestamp_seconds gauge' + echo "apt_package_cache_timestamp_seconds{host=\"$host\"} $cache" +} > "$tmp" + +chmod 0644 "$tmp" +mv -f "$tmp" "$out"