Address review on the egress recovery job (#3937)

- The egress job runs through WorkerHelper.getEgressRecoveryJob(), like
  the cleanup jobs, instead of a direct service call from the queue
- Toggling egressCheckEnabled resets the egress state and removes any
  pending recovery job
- Probes resolve and pass the proxy, so http targets honour the global
  proxy as monitors do
- PingProvider keeps IPv6 addresses intact instead of truncating at the
  first colon
- Egress targets are parsed by one function shared by the validator and
  the probe; malformed addresses such as unbalanced brackets are rejected
- Notification id pattern drops the /i flag, which produced an invalid
  OpenAPI pattern
- EgressState findSingleton reads before it writes, so a read no longer
  bumps updatedAt
- "disabled" is a distinct assessment result and is cached like any other
- Concurrent failures share an in-flight assessment for as long as it
  runs, then reuse the settled result for 5 s
- The recovery job row is only removed if nobody rescheduled it since
  this run claimed it, so a concurrent new episode keeps its job
- Deleting a notification removes it from egressNotifications

🔧
This commit is contained in:
Craig Lawson committed 2026-09-15 08:42:09 +01:00
1 parent 46eb0b4063
commit 08f104e283
26 files changed
+600 -157

No files matched your search

+48
View File
@@ -0,0 +1,48 @@
import { z } from "zod";
import { dnsHostnameRegex } from "@/api/validation/shared.js";
// A reliability target for the egress self-check, in one of three shapes. Parsed here so the
// settings validator and the probe agree on exactly what is accepted.
export type EgressTarget =
| { kind: "http"; url: string } // http(s):// URL, fetched
| { kind: "port"; host: string; port: number } // host:port or [ipv6]:port, TCP connect
| { kind: "ping"; host: string }; // bare hostname or IP address, ICMP
// "[ipv6]:port" or "host:port" where host has no colons, so a bare IPv6 address is never read as host:port.
const HOST_PORT_PATTERN = /^(?:\[([^\]]+)\]|([^:/\s[\]]+)):(\d{1,5})$/;
const BRACKETED_PATTERN = /^\[([^\]]+)\]$/;
const isIpv6 = (value: string): boolean => z.ipv6().safeParse(value).success;
const isHostOrIpv4 = (value: string): boolean => value === "localhost" || z.ipv4().safeParse(value).success || dnsHostnameRegex.test(value);
const parseHttp = (target: string): EgressTarget | null => {
try {
const { protocol, hostname } = new URL(target);
return (protocol === "http:" || protocol === "https:") && hostname.length > 0 ? { kind: "http", url: target } : null;
} catch {
return null;
}
};
export const parseEgressTarget = (raw: string): EgressTarget | null => {
const target = raw.trim();
if (target.length === 0) return null;
if (/^https?:\/\//i.test(target)) return parseHttp(target);
const hostPort = HOST_PORT_PATTERN.exec(target);
if (hostPort) {
const port = Number(hostPort[3]);
if (port < 1 || port > 65535) return null;
const bracketedHost = hostPort[1];
if (bracketedHost !== undefined) return isIpv6(bracketedHost) ? { kind: "port", host: bracketedHost, port } : null;
const host = hostPort[2]!;
return isHostOrIpv4(host) ? { kind: "port", host, port } : null;
}
const bracketed = BRACKETED_PATTERN.exec(target);
if (bracketed) return isIpv6(bracketed[1]!) ? { kind: "ping", host: bracketed[1]! } : null;
if (isIpv6(target) || isHostOrIpv4(target)) return { kind: "ping", host: target };
return null;
};