feat(host_agent): a host says what updates it has waiting and whether it needs a reboot (#5765)
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 1m22s
CI / unit (push) Successful in 1m26s
CI / publish (push) Successful in 1m20s

Step 1 of milestone 514 "Updates and reboots on a cadence, carried out by
each host's agent…": #5765 "The agent reports pending updates and whether
a reboot is required".

- Agent 1.16.0: an hourly background check (apt-check, apt list, or dnf -C
  with updateinfo and needs-restarting), each command bounded at 120 s, no
  root and no network. Debian's reboot-required marker and its packages
  are read every sample.
- Ingest records updates_pending, updates_security and reboot_required for
  rules, and keeps the newest on the registration (host_agent_005_updates).
- The Agent card leads with "Reboot required since …", then the updates
  waiting or "Up to date", with when the check ran. list_agents carries
  them too.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-14 18:23:44 -04:00
co-authored by Claude Opus 5.5
parent b1db1e8b0d
commit 210a35c956
12 changed files with 472 additions and 4 deletions
@@ -122,3 +122,37 @@ def test_the_agent_s_own_footprint_is_recorded():
idx = _index(_expand_sample_to_metrics(sample, "d3", datetime.now(timezone.utc)))
assert idx[("agent_rss_bytes", "d3")] == 18_000_000
assert idx[("agent_cpu_pct", "d3")] == 1.5
def test_pending_updates_and_a_needed_reboot_are_metrics():
"""#5765: rules can watch what a host has waiting."""
sample = {"ts": "2026-10-14T22:00:00+00:00",
"updates": {"manager": "apt", "pending": 12, "security": 3, "checked_at": "2026-10-14T21:30:00+00:00"},
"reboot": {"required": True, "since": "2026-10-14T06:00:00+00:00", "packages": ["libc6"]}}
idx = _index(_expand_sample_to_metrics(sample, "d3", datetime.now(timezone.utc)))
assert idx[("updates_pending", "d3")] == 12
assert idx[("updates_security", "d3")] == 3
assert idx[("reboot_required", "d3")] == 1
def test_the_registration_keeps_the_host_s_updates_and_reboot():
from types import SimpleNamespace
from plugins.host_agent.routes import apply_updates
now = datetime(2026, 10, 14, 22, tzinfo=timezone.utc)
reg = SimpleNamespace(updates_manager=None, updates_pending=None, updates_security=None,
updates_checked_at=None, updates_error=None, reboot_required=None,
reboot_required_since=None, reboot_packages=None)
sample = {"updates": {"manager": "apt", "pending": 4, "security": 1,
"checked_at": "2026-10-14T21:30:00+00:00"},
"reboot": {"required": True, "since": "2026-10-14T06:00:00Z",
"packages": ["libc6", "linux-image-6.8.0-146-generic"]}}
assert apply_updates(reg, sample, now) is True
assert (reg.updates_pending, reg.updates_security, reg.reboot_required) == (4, 1, True)
assert reg.reboot_required_since.hour == 6
assert reg.reboot_packages == "libc6, linux-image-6.8.0-146-generic"
assert apply_updates(reg, sample, now) is False # nothing new
# A failed check keeps the last counts and says why; the reboot clears.
apply_updates(reg, {"updates": {"error": "apt is locked", "checked_at": "2026-10-14T22:30:00Z"},
"reboot": {"required": False}}, now)
assert reg.updates_pending == 4 and reg.updates_error == "apt is locked"
assert reg.reboot_required is False and reg.reboot_required_since is None