Fix sunbeam check: group by namespace, never crash on network errors
Output now mirrors sunbeam status (namespace headers, checks indented below). Any uncaught exception from a check is caught in cmd_check and displayed as a failed check instead of crashing. Also fix _http_get: TimeoutError and other raw OSError/SSL errors that Python 3.13 doesn't always wrap in URLError are now normalized to URLError before re-raising, so each check function's URLError handler reliably catches all network failures.
This commit is contained in:
@@ -62,13 +62,23 @@ def _opener(ssl_ctx: ssl.SSLContext) -> urllib.request.OpenerDirector:
|
|||||||
|
|
||||||
def _http_get(url: str, opener: urllib.request.OpenerDirector, *,
|
def _http_get(url: str, opener: urllib.request.OpenerDirector, *,
|
||||||
headers: dict | None = None, timeout: int = 10) -> tuple[int, bytes]:
|
headers: dict | None = None, timeout: int = 10) -> tuple[int, bytes]:
|
||||||
"""Return (status_code, body). Redirects are not followed."""
|
"""Return (status_code, body). Redirects are not followed.
|
||||||
|
|
||||||
|
Any network/SSL error (including TimeoutError) is re-raised as URLError
|
||||||
|
so callers only need to catch urllib.error.URLError.
|
||||||
|
"""
|
||||||
req = urllib.request.Request(url, headers=headers or {})
|
req = urllib.request.Request(url, headers=headers or {})
|
||||||
try:
|
try:
|
||||||
with opener.open(req, timeout=timeout) as resp:
|
with opener.open(req, timeout=timeout) as resp:
|
||||||
return resp.status, resp.read()
|
return resp.status, resp.read()
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
return e.code, b""
|
return e.code, b""
|
||||||
|
except urllib.error.URLError:
|
||||||
|
raise
|
||||||
|
except OSError as e:
|
||||||
|
# TimeoutError and other socket/SSL errors don't always get wrapped
|
||||||
|
# in URLError by Python's urllib — normalize them here.
|
||||||
|
raise urllib.error.URLError(e) from e
|
||||||
|
|
||||||
|
|
||||||
# ── Individual checks ─────────────────────────────────────────────────────────
|
# ── Individual checks ─────────────────────────────────────────────────────────
|
||||||
@@ -243,23 +253,35 @@ def cmd_check(target: str | None) -> None:
|
|||||||
op = _opener(ssl_ctx)
|
op = _opener(ssl_ctx)
|
||||||
|
|
||||||
ns_filter, svc_filter = parse_target(target) if target else (None, None)
|
ns_filter, svc_filter = parse_target(target) if target else (None, None)
|
||||||
fns = [
|
selected = [
|
||||||
fn for fn, ns, svc in CHECKS
|
(fn, ns, svc) for fn, ns, svc in CHECKS
|
||||||
if (ns_filter is None or ns == ns_filter)
|
if (ns_filter is None or ns == ns_filter)
|
||||||
and (svc_filter is None or svc == svc_filter)
|
and (svc_filter is None or svc == svc_filter)
|
||||||
]
|
]
|
||||||
|
|
||||||
if not fns:
|
if not selected:
|
||||||
warn(f"No checks match target: {target}")
|
warn(f"No checks match target: {target}")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Run all checks; catch any unexpected exception so we never crash.
|
||||||
results = []
|
results = []
|
||||||
for fn in fns:
|
for fn, ns, svc in selected:
|
||||||
r = fn(domain, op)
|
try:
|
||||||
|
r = fn(domain, op)
|
||||||
|
except Exception as e:
|
||||||
|
r = CheckResult(fn.__name__.replace("check_", ""), ns, svc, False, str(e)[:80])
|
||||||
results.append(r)
|
results.append(r)
|
||||||
|
|
||||||
|
# Print grouped by namespace (mirrors sunbeam status layout).
|
||||||
|
name_w = max(len(r.name) for r in results)
|
||||||
|
cur_ns = None
|
||||||
|
for r in results:
|
||||||
|
if r.ns != cur_ns:
|
||||||
|
print(f" {r.ns}:")
|
||||||
|
cur_ns = r.ns
|
||||||
icon = "\u2713" if r.passed else "\u2717"
|
icon = "\u2713" if r.passed else "\u2717"
|
||||||
detail = f" ({r.detail})" if r.detail else ""
|
detail = f" {r.detail}" if r.detail else ""
|
||||||
print(f" {icon} {r.ns}/{r.svc} [{r.name}]{detail}")
|
print(f" {icon} {r.name:<{name_w}}{detail}")
|
||||||
|
|
||||||
print()
|
print()
|
||||||
failed = [r for r in results if not r.passed]
|
failed = [r for r in results if not r.passed]
|
||||||
|
|||||||
Reference in New Issue
Block a user