diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index 806d4cf9..ab67c4ed 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -7,6 +7,8 @@ error while a layer decodes is a NOTE, never a pass. - **`security_watch.py`:** a `RUNTIME read=N (S s) from-cache=M not-checked=K zstd-decoder=…` line and one `RUNTIME-READ ok|NOT CHECKED` line per image read tonight. + The decoder is named in words (`zstd_decoder_how()`: „zstandard 0.25.0 (pinned wheel)" / „the zstd program" / none); + the first CI run printed the module's repr. - Tests: `test_image_runtime.py` 22 checks (zstd above gzip with no `zstd` program, a damaged and a cut-off zstd layer, no decoder, the wheel loader's hash refusals) — the zstd read seen failing on the 2026-10-27 module first; `test_security_watch.py` W10 counts (59 checks). diff --git a/scripts/image_runtime.py b/scripts/image_runtime.py index 1b02b37e..ebc58feb 100644 --- a/scripts/image_runtime.py +++ b/scripts/image_runtime.py @@ -138,18 +138,24 @@ def zstandard_module(fetch=None, wheels=None, cache=None, try_import=True): def zstd_decoder(): - """How this process decodes a zstd layer: ('module', mod) | ('program', 'zstd') | (None, why). Decided once.""" + """How this process decodes a zstd layer: ('module', mod) | ('program', 'zstd') | (None, why). Decided once; + zstd_decoder_how() says it in words for the log.""" if "d" not in _ZSTANDARD: mod, how = zstandard_module() if mod: - _ZSTANDARD["d"] = ("module", mod) + _ZSTANDARD["d"], _ZSTANDARD["how"] = ("module", mod), how elif shutil.which("zstd"): - _ZSTANDARD["d"] = ("program", "zstd") + _ZSTANDARD["d"], _ZSTANDARD["how"] = ("program", "zstd"), "the zstd program" else: - _ZSTANDARD["d"] = (None, how) + _ZSTANDARD["d"], _ZSTANDARD["how"] = (None, how), "NONE — " + how return _ZSTANDARD["d"] +def zstd_decoder_how(): + zstd_decoder() + return _ZSTANDARD["how"] + + def split_ref(ref): """'rommapp/romm:5.4.1' -> (registry, path, tag). lscr.io is Docker Hub's mirror (the watch's rule).""" tag = "latest" diff --git a/scripts/security_watch.py b/scripts/security_watch.py index 92684a73..f710d3b1 100644 --- a/scripts/security_watch.py +++ b/scripts/security_watch.py @@ -884,7 +884,7 @@ def main(argv): st = rreader.stats print("RUNTIME read=%d (%.0f s) from-cache=%d not-checked=%d zstd-decoder=%s" % ( st["read"], st["read_s"], st["cached"], st["not_checked"], - image_runtime.zstd_decoder()[1] if st["read"] else "not needed")) + image_runtime.zstd_decoder_how() if st["read"] else "not needed")) print("REQUESTS " + " ".join("%s=%d" % kv for kv in sorted(net.requests.items()))) state_path = opts.get("state") state, missing = {}, True diff --git a/scripts/test_image_runtime.py b/scripts/test_image_runtime.py index c667589b..a8b8441c 100644 --- a/scripts/test_image_runtime.py +++ b/scripts/test_image_runtime.py @@ -89,6 +89,11 @@ def zstd_checks(): found.get("pydist")) found, notes = ir.scan_layers(reg, [{"digest": "sha256:zst", "mediaType": ZSTD_MT, "size": 9}], decoder=(None, "no zstd decoder: test")) + ir._ZSTANDARD.clear() + how = ir.zstd_decoder_how() + check("zstd: the log names the decoder in words", isinstance(how, str) and ("zstandard" in how or "zstd program" in how + or how.startswith("NONE")), how) + ir._ZSTANDARD.clear() check("zstd: no decoder is a NOTE (NOT CHECKED), never a silent pass", notes and "no zstd decoder" in notes[0] and not found, (found, notes)) mod, _how = ir.zstandard_module()