feat: say where a pending update comes from, whether it is a security fix, and whether the host needs a reboot

netOrk MVP 5 measures "security updates applied within N days" and starts
patch runs inside agreed windows. That needs three things every Linux driver
reads the same way, so they live here once:

- UpdateDict gains optional `origin` and `security` (None = unknown).
- package_updates: APT_UPGRADABLE_COMMAND and parse_apt_upgradable(). apt's
  suites are the origin; a "-security" suite makes it a security update;
  several architectures of one package are one entry. The command runs
  through a pipe with its exit status printed inside the group: through a
  pseudo-terminal apt drew progress and keypad codes, one of which (ESC >)
  a screen-scraping read took for a prompt and stopped at. The parser raises
  ValueError when apt failed or its status never arrived.
  DNF_SECURITY_COMMAND / parse_dnf_security() / nevra_name() for dnf/yum.
- host_status: HOST_STATUS_COMMAND, parse_host_status(), HostStatusMixin
  (template form, hook _run_host_status_command). reboot_required from
  /var/run/reboot-required, needs-restarting -r, or a newer kernel of the
  running flavour (a Raspberry Pi carries two flavours side by side);
  auto_updates from APT::Periodic::Unattended-Upgrade with its timer, or
  dnf-automatic. HostStatusDict in models.py.
- terminal.strip_terminal_codes(): CSI, OSC and two-character escapes, now
  also used by the systemd parser.
- UpdateMixin: contract refresh_available_updates(); get_available_updates'
  docstring now states the rule that a reader raises when it cannot read and
  never returns [] for "don't know".

Fixtures are real output from Ubuntu 24.04, Debian 13 / OMV, Raspberry Pi OS
and Proxmox VE 9. Version 2.3.0.
This commit is contained in:
2026-10-06 00:19:24 +02:00
parent 4071f35050
commit 18676a573f
11 changed files with 731 additions and 8 deletions
+11
View File
@@ -79,6 +79,17 @@ everywhere, so the command and its parse are concrete here and a driver supplies
`_run_kernel_facts_command`. `OSDriver` does not carry it — a Windows host is an OS driver
too, and `hasattr(driver, "get_kernel_facts")` has to stay truthful.
`HostStatusMixin` (`get_host_status`) is mixed in the same way: whether a Linux host needs
a reboot to finish an update (`/var/run/reboot-required`, `needs-restarting -r`, or a newer
kernel of the running flavour installed) and whether it patches itself (unattended-upgrades,
dnf-automatic). `package_updates` holds the shared apt and dnf parsers: apt's suites become
an update's `origin`, a `-security` suite makes it a security update, and dnf's security
advisories do the same.
**Update readers raise when they cannot read.** `get_available_updates` returns an empty
list only when nothing is pending; netOrk keeps "pending since" per package, and an empty
list for "don't know" would reset it.
`SystemdServicesMixin` (`get_services`, `manage_service`) is mixed in the same way, by
the drivers whose host runs systemd. Listing the services, checking a unit name and
reading an action's exit status are the same on every such host, so they are concrete