The product mapping was hand-written and never checked against the actual catalogue, so a quarter of it was wrong. Every EOL run asked for ms-office, exchange-server, java, visual-cpp, adobe-acrobat, apache and iis, and got 404 back each time — visible in the log as "not in catalog", invisible everywhere else. The scan simply found no lifecycle data and moved on, which is why Microsoft Exchange Server 2016 came out looking supported. Checked against the live list (462 products) rather than guessed: exchange-server -> msexchange (the reported Exchange 2016 case) ms-office -> office apache -> apache-http-server java -> oracle-jdk The other three do not exist there at all and never will: endoflife.date has no record for Adobe Acrobat, the Visual C++ redistributables, or IIS. Their 18 mapping entries are removed rather than left to 404 on every run — IIS ships with Windows Server and is covered by that entry, the other two depend on the Microsoft export or a vendor page. Same reasoning as the existing Edge note. tools/check_eol_slugs.py verifies the whole mapping against the live catalogue and exits non-zero when a slug disappears, so this cannot rot again unnoticed.
59 lines
2.2 KiB
Python
59 lines
2.2 KiB
Python
"""Do our endoflife.date slugs still exist in their catalogue?
|
|
|
|
The mapping was hand-written and never checked against the real product list,
|
|
so seven of 33 slugs pointed at nothing: every EOL run asked for ms-office,
|
|
exchange-server, java, visual-cpp and adobe-acrobat and got a 404 back. The
|
|
misses were invisible in the GUI — the scan just found no lifecycle data and
|
|
moved on, so Exchange Server 2016 looked supported.
|
|
|
|
docker compose exec backend python tools/check_eol_slugs.py
|
|
|
|
Exits non-zero when a slug has disappeared, so it can be run after an upgrade
|
|
or on a schedule. Catalogue changes are a fact of life; silently 404ing is not.
|
|
"""
|
|
import sys
|
|
|
|
sys.path.insert(0, "/app")
|
|
|
|
|
|
def main():
|
|
import httpx
|
|
from app.services.eol_service import _PRODUCT_SLUGS, _API_BASE
|
|
|
|
catalogue_url = _API_BASE.rstrip("/") + "/"
|
|
try:
|
|
r = httpx.get(catalogue_url, timeout=30.0)
|
|
r.raise_for_status()
|
|
payload = r.json()
|
|
except Exception as e:
|
|
print(f"Could not fetch the catalogue ({catalogue_url}): {e}")
|
|
return 2
|
|
|
|
entries = payload.get("result") or payload
|
|
catalogue = {p.get("name") if isinstance(p, dict) else p for p in entries}
|
|
print(f"endoflife.date lists {len(catalogue)} products.\n")
|
|
|
|
used = sorted({s for s in _PRODUCT_SLUGS.values() if s})
|
|
missing = [s for s in used if s not in catalogue]
|
|
for slug in used:
|
|
names = sorted(k for k, v in _PRODUCT_SLUGS.items() if v == slug)
|
|
mark = "GONE" if slug in missing else " ok "
|
|
print(f" [{mark}] {slug:24} ({len(names)} name(s) map here)")
|
|
|
|
print()
|
|
if missing:
|
|
import difflib
|
|
print(f"{len(missing)} slug(s) no longer exist — every lookup 404s:")
|
|
for slug in missing:
|
|
near = difflib.get_close_matches(slug, catalogue, n=3, cutoff=0.55)
|
|
print(f" {slug:24} closest in catalogue: {near or 'nothing similar'}")
|
|
return 1
|
|
print(f"All {len(used)} slugs resolve. Products deliberately left unmapped "
|
|
f"(Acrobat, VC++ redistributables, IIS, Edge) are not listed here — "
|
|
f"endoflife.date has no record for them.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|