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
+25
View File
@@ -235,3 +235,28 @@ async def test_the_agent_card_says_what_happened_while_the_host_was_away(app):
assert page.index("Rebooted") < page.index("Lost contact")
assert "going on, 5m" in page and "1h" in page
assert "Downtime, last 7 days" not in quiet
async def test_the_agent_card_leads_with_a_needed_reboot_then_the_updates(app):
"""#5765: what the host has waiting, from the agent's hourly check."""
from datetime import datetime, timedelta, timezone
from tests._templates import render_plugin_template
now = datetime.now(timezone.utc)
host = SimpleNamespace(id="h1", name="box", address="10.0.0.5")
waiting = SimpleNamespace(
agent_version="1.16.0", last_seen_at=now, updates_manager="apt", updates_pending=12,
updates_security=3, updates_checked_at=now - timedelta(minutes=20), updates_error=None,
reboot_required=True, reboot_required_since=now - timedelta(hours=5),
reboot_packages="libc6, linux-image-6.8.0-146-generic")
current = SimpleNamespace(**{**vars(waiting), "updates_pending": 0, "updates_security": 0,
"reboot_required": False, "reboot_required_since": None,
"reboot_packages": None})
kw = dict(host=host, reporting=True, stale=False, target=None, ansible_available=False,
managed_key_set=False, downtime=[])
async with app.test_request_context("/plugins/host_agent/panel/h1"):
session["user_role"] = "viewer"
page = await render_plugin_template(app, "host_agent", "panel.html", reg=waiting, **kw)
quiet = await render_plugin_template(app, "host_agent", "panel.html", reg=current, **kw)
assert page.index("Reboot required") < page.index("12 updates waiting (3 security)")
assert "libc6" in page
assert "Up to date" in quiet and "Reboot required" not in quiet