v0.5.0: TrueNAS alert when an update is available
Raises a real alert in the TrueNAS UI bell -- not a log line nobody reads. On by default, checked once a day. install.sh --no-update-alerts turns it off. It does not nag --------------- A release whose CHANGELOG contains only a "### Docs" section changed no code and raises nothing. Anything else raises INFO; a "### Security" section raises WARNING. The CHANGELOG's own section headings are the signal, and a security fix anywhere in the range escalates the whole span -- a docs-only release sitting on top of a security fix still reports as security rather than hiding it. Why an AlertSource and not midclt ---------------------------------- TrueNAS cannot raise an alert from the CLI. midclt exposes only alert.dismiss, alert.list, alert.list_categories, alert.list_policies and alert.restore -- alert CREATION is internal to middlewared, and none of its ~60 one-shot classes is generic enough to reuse. Registering an AlertSource is the only way. It is also the least invasive thing this patch does. The providers and nested modules both APPEND CODE TO STOCK middleware files; the alert source ADDS ONE FILE and modifies none. It is the native mechanism -- the same one every built-in TrueNAS alert uses -- and TrueNAS polls it itself, so there is no cron job and no systemd timer. - Fail-safe: every error path returns None; it cannot take middlewared down. - Read-only: `git ls-remote` plus an HTTPS fetch of the CHANGELOG. It never writes to .git, so it cannot leave root-owned objects behind the way a `git fetch` from middlewared (running as root) would. - Removed by uninstall.sh (mw_patch.revert_all). - It only tells you; it never updates anything. Verified against the real repo and remote, with middlewared stubbed: on v0.4.1, only a README-only v0.4.2 available -> NO ALERT on v0.4.0, v0.4.1 fixed real bugs -> INFO on v0.3.2, v0.3.3 was the password fix -> SECURITY / WARNING 139 tests, ruff and shellcheck -S style clean.
This commit is contained in:
@@ -88,6 +88,60 @@ def extract_notes(text: str, version: str) -> str:
|
||||
return "\n".join(lines[start:end]).strip()
|
||||
|
||||
|
||||
# ── significance ──────────────────────────────────────────────────────────────
|
||||
# Used by the TrueNAS update alert to decide whether a release is worth bothering
|
||||
# anyone about. The CHANGELOG's own section headings are the signal: a release that
|
||||
# only has "### Docs" changed no code, and nobody should get an alert for a README.
|
||||
|
||||
_SECTION_RE = re.compile(r"^###\s+(.+?)\s*$", re.M)
|
||||
|
||||
#: Headings that mean "nothing about the running system changed".
|
||||
QUIET_SECTIONS = {"docs", "documentation"}
|
||||
|
||||
|
||||
def version_tuple(v: str) -> tuple:
|
||||
"""Sortable version. Pre-release suffixes are dropped, not ranked."""
|
||||
return tuple(int(x) for x in normalise(v).split("-")[0].split("."))
|
||||
|
||||
|
||||
def section_headings(body: str) -> list[str]:
|
||||
"""The `### ...` headings inside one version's body, lowercased."""
|
||||
return [h.strip().lower() for h in _SECTION_RE.findall(body)]
|
||||
|
||||
|
||||
def significance(text: str, current: str, latest: str):
|
||||
"""How much does upgrading `current` -> `latest` actually matter?
|
||||
|
||||
Returns ``(level, versions, headings)`` where level is one of:
|
||||
|
||||
"security" a release in the range has a Security section -> alert loudly
|
||||
"notable" something about the system changed -> alert quietly
|
||||
"docs" only documentation changed -> DO NOT alert
|
||||
|
||||
Considers every release in the range, not just the newest: a docs-only v0.4.2
|
||||
on top of a security-fixing v0.4.1 must still be reported as security.
|
||||
"""
|
||||
cur, lat = version_tuple(current), version_tuple(latest)
|
||||
|
||||
versions = [
|
||||
v for v in changelog_versions(text)
|
||||
if cur < version_tuple(v) <= lat
|
||||
]
|
||||
|
||||
headings = []
|
||||
for v in versions:
|
||||
try:
|
||||
headings.extend(section_headings(extract_notes(text, v)))
|
||||
except KeyError:
|
||||
continue
|
||||
|
||||
if any(h.startswith("security") for h in headings):
|
||||
return "security", versions, headings
|
||||
if [h for h in headings if h not in QUIET_SECTIONS]:
|
||||
return "notable", versions, headings
|
||||
return "docs", versions, headings
|
||||
|
||||
|
||||
def check(version: str, root: str = ROOT) -> list[str]:
|
||||
"""Every reason this version is not releasable. Empty list means it is."""
|
||||
want = normalise(version)
|
||||
|
||||
Reference in New Issue
Block a user