fix(web-probe): merge sentinel caddy path routes

This commit is contained in:
Codex
2026-06-26 13:21:46 +00:00
parent 0bc341bc62
commit 25c67dcdad
+53 -20
View File
@@ -622,18 +622,21 @@ function runSentinelControlPlaneConfirmed(state: SentinelCicdState, options: Ext
const startedAt = Date.now(); const startedAt = Date.now();
const command = `web-probe sentinel control-plane ${options.action}`; const command = `web-probe sentinel control-plane ${options.action}`;
const applyOnly = options.action === "apply"; const applyOnly = options.action === "apply";
const sourceMirrorSync = applyOnly ? null : runSentinelSourceMirrorSyncJob(state, options.timeoutSeconds); const cicdWaitWarningSeconds = controlPlaneWaitWarningSeconds(state);
const deadline = startedAt + cicdWaitWarningSeconds * 1000;
const remainingCicdWaitSeconds = () => remainingSeconds(deadline, Math.min(options.timeoutSeconds, cicdWaitWarningSeconds));
const sourceMirrorSync = applyOnly ? null : runSentinelSourceMirrorSyncJob(state, remainingCicdWaitSeconds());
const publish = applyOnly const publish = applyOnly
? null ? null
: record(sourceMirrorSync).ok === true : record(sourceMirrorSync).ok === true
? runSentinelPublishJob(state, true, options.timeoutSeconds) ? runSentinelPublishJob(state, true, remainingCicdWaitSeconds())
: sentinelBlockedRemoteResult("source-mirror-sync-blocked", "sentinel source mirror sync failed; publish job was not started"); : sentinelBlockedRemoteResult("source-mirror-sync-blocked", "sentinel source mirror sync failed; publish job was not started");
const flush = !applyOnly && record(publish).ok === true const flush = !applyOnly && record(publish).ok === true
? runChildCli(["hwlab", "nodes", "git-mirror", "flush", "--node", state.spec.nodeId, "--lane", state.spec.lane, "--confirm", "--wait"], options.timeoutSeconds) ? runChildCli(["hwlab", "nodes", "git-mirror", "flush", "--node", state.spec.nodeId, "--lane", state.spec.lane, "--confirm", "--wait"], remainingCicdWaitSeconds())
: null; : null;
const publicExposureApply = applySentinelPublicExposure(state, options.timeoutSeconds); const publicExposureApply = applySentinelPublicExposure(state, remainingCicdWaitSeconds());
const argoApply = applySentinelArgoApplication(state, options.timeoutSeconds); const argoApply = applySentinelArgoApplication(state, remainingCicdWaitSeconds());
const observed = waitForSentinelObservedStatus(state, options.timeoutSeconds); const observed = waitForSentinelObservedStatus(state, remainingCicdWaitSeconds());
const observedReady = sentinelObservedReady(observed); const observedReady = sentinelObservedReady(observed);
const targetValidation = null; const targetValidation = null;
const targetValidationBlocked = false; const targetValidationBlocked = false;
@@ -646,7 +649,6 @@ function runSentinelControlPlaneConfirmed(state: SentinelCicdState, options: Ext
&& record(argoApply).ok === true && record(argoApply).ok === true
&& observedReady; && observedReady;
const elapsedMs = Date.now() - startedAt; const elapsedMs = Date.now() - startedAt;
const cicdWaitWarningSeconds = controlPlaneWaitWarningSeconds(state);
const blocker = ok ? null : { const blocker = ok ? null : {
code: record(sourceMirrorSync).ok === false ? "sentinel-source-mirror-sync-failed" : "sentinel-control-plane-not-ready", code: record(sourceMirrorSync).ok === false ? "sentinel-source-mirror-sync-failed" : "sentinel-control-plane-not-ready",
reason: record(sourceMirrorSync).ok === false reason: record(sourceMirrorSync).ok === false
@@ -753,7 +755,7 @@ function collectSentinelObservedStatus(state: SentinelCicdState, timeoutSeconds:
function waitForSentinelObservedStatus(state: SentinelCicdState, timeoutSeconds: number, expectation?: SentinelObservedExpectation): SentinelObservedStatus { function waitForSentinelObservedStatus(state: SentinelCicdState, timeoutSeconds: number, expectation?: SentinelObservedExpectation): SentinelObservedStatus {
const startedAt = Date.now(); const startedAt = Date.now();
const timeoutMs = Math.max(30_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000)); const timeoutMs = Math.max(5_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000));
let observed = collectSentinelObservedStatus(state, timeoutSeconds, expectation); let observed = collectSentinelObservedStatus(state, timeoutSeconds, expectation);
while (!sentinelObservedReady(observed) && Date.now() - startedAt < timeoutMs) { while (!sentinelObservedReady(observed) && Date.now() - startedAt < timeoutMs) {
runCommand(["sleep", "5"], repoRoot, { timeoutMs: 6_000 }); runCommand(["sleep", "5"], repoRoot, { timeoutMs: 6_000 });
@@ -917,7 +919,7 @@ function runSentinelSourceMirrorSyncJob(state: SentinelCicdState, timeoutSeconds
return { ok: false, phase: "create-job", jobName, payload: { ok: false, status: "create-failed", valuesRedacted: true }, create: compactCommand(created), valuesRedacted: true }; return { ok: false, phase: "create-job", jobName, payload: { ok: false, status: "create-failed", valuesRedacted: true }, create: compactCommand(created), valuesRedacted: true };
} }
const startedAt = Date.now(); const startedAt = Date.now();
const timeoutMs = Math.max(30_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000)); const timeoutMs = Math.max(5_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000));
const warningBudgetMs = Math.max(1, Math.trunc(controlPlaneWaitWarningSeconds(state))) * 1000; const warningBudgetMs = Math.max(1, Math.trunc(controlPlaneWaitWarningSeconds(state))) * 1000;
let slowWarningSent = false; let slowWarningSent = false;
let polls = 0; let polls = 0;
@@ -1160,7 +1162,7 @@ function runSentinelPublishJob(state: SentinelCicdState, publishGitops: boolean,
} }
sentinelProgressEvent("sentinel.publish.progress", { phase: "create-job", status: "succeeded", jobName, publishGitops, node: state.spec.nodeId, lane: state.spec.lane }); sentinelProgressEvent("sentinel.publish.progress", { phase: "create-job", status: "succeeded", jobName, publishGitops, node: state.spec.nodeId, lane: state.spec.lane });
const startedAt = Date.now(); const startedAt = Date.now();
const timeoutMs = Math.max(30_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000)); const timeoutMs = Math.max(5_000, Math.min(timeoutSeconds * 1000, controlPlaneWaitWarningSeconds(state) * 1000));
const warningBudgetMs = Math.max(1, Math.trunc(controlPlaneWaitWarningSeconds(state))) * 1000; const warningBudgetMs = Math.max(1, Math.trunc(controlPlaneWaitWarningSeconds(state))) * 1000;
let slowWarningSent = false; let slowWarningSent = false;
let polls = 0; let polls = 0;
@@ -2267,10 +2269,8 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"", "",
].join("\n") ].join("\n")
: [ : [
`${hostname} {`, `handle_path ${routePrefix}* {`,
` handle_path ${routePrefix}* {`, ...proxyLines.map((line) => ` ${line}`),
...proxyLines.map((line) => ` ${line}`),
" }",
"}", "}",
"", "",
].join("\n"); ].join("\n");
@@ -2281,6 +2281,7 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
`owner=${shellQuote(owner)}`, `owner=${shellQuote(owner)}`,
`config_path=${shellQuote(configPath)}`, `config_path=${shellQuote(configPath)}`,
`service=${shellQuote(serviceName)}`, `service=${shellQuote(serviceName)}`,
`route_prefix=${shellQuote(routePrefix)}`,
`block_b64=${shellQuote(blockB64)}`, `block_b64=${shellQuote(blockB64)}`,
"marker=\"unidesk managed $owner\"", "marker=\"unidesk managed $owner\"",
"tmp=$(mktemp -d)", "tmp=$(mktemp -d)",
@@ -2289,20 +2290,52 @@ function applySentinelCaddyBlock(state: SentinelCicdState, timeoutSeconds: numbe
"next=\"$tmp/Caddyfile\"", "next=\"$tmp/Caddyfile\"",
"printf '%s' \"$block_b64\" | base64 -d >\"$block\"", "printf '%s' \"$block_b64\" | base64 -d >\"$block\"",
"if [ -f \"$config_path\" ]; then cp \"$config_path\" \"$next\"; else : >\"$next\"; fi", "if [ -f \"$config_path\" ]; then cp \"$config_path\" \"$next\"; else : >\"$next\"; fi",
"python3 - \"$next\" \"$block\" \"$marker\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err", "python3 - \"$next\" \"$block\" \"$marker\" \"$hostname\" \"$route_prefix\" <<'PY' >/tmp/web-probe-sentinel-caddy-python.out 2>/tmp/web-probe-sentinel-caddy-python.err",
"import pathlib, re, sys", "import pathlib, re, sys",
"config = pathlib.Path(sys.argv[1])", "config = pathlib.Path(sys.argv[1])",
"block = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8')", "block = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8')",
"marker = sys.argv[3]", "marker = sys.argv[3]",
"hostname = sys.argv[4]",
"route_prefix = sys.argv[5]",
"text = config.read_text(encoding='utf-8') if config.exists() else ''", "text = config.read_text(encoding='utf-8') if config.exists() else ''",
"begin = f'# BEGIN {marker}'", "begin = f'# BEGIN {marker}'",
"end = f'# END {marker}'", "end = f'# END {marker}'",
"managed = f'{begin}\\n{block.rstrip()}\\n# END {marker}\\n'", "pattern = re.compile(rf'(?ms)^[ \\t]*# BEGIN {re.escape(marker)}\\n.*?^[ \\t]*# END {re.escape(marker)}\\n*')",
"pattern = re.compile(rf'(?ms)^# BEGIN {re.escape(marker)}\\n.*?\\n# END {re.escape(marker)}\\n*')", "text = pattern.sub('', text)",
"if pattern.search(text):", "def site_span(src, host):",
" text = pattern.sub(managed, text)", " match = re.search(rf'(?m)^([ \\t]*){re.escape(host)}[ \\t]*\\{{[ \\t]*\\n', src)",
"else:", " if not match:",
" return None",
" depth = 1",
" index = match.end()",
" while index < len(src):",
" char = src[index]",
" if char == '{':",
" depth += 1",
" elif char == '}':",
" depth -= 1",
" if depth == 0:",
" end_index = index + 1",
" if end_index < len(src) and src[end_index] == '\\n':",
" end_index += 1",
" return match.start(), end_index, index, match.end()",
" index += 1",
" raise ValueError(f'unclosed Caddy site block for {host}')",
"if route_prefix == '/':",
" managed = f'{begin}\\n{block.rstrip()}\\n{end}\\n'",
" text = text.rstrip() + '\\n\\n' + managed", " text = text.rstrip() + '\\n\\n' + managed",
"else:",
" handler = '\\n'.join((' ' + line) if line else '' for line in block.rstrip().splitlines())",
" managed = f' {begin}\\n{handler}\\n {end}\\n'",
" span = site_span(text, hostname)",
" if span is None:",
" text = text.rstrip() + '\\n\\n' + f'{hostname} {{\\n{managed}}}\\n'",
" else:",
" start, stop, close_index, open_end = span",
" site = text[start:stop]",
" relative_open = open_end - start",
" replacement = site[:relative_open] + managed + site[relative_open:]",
" text = text[:start] + replacement + text[stop:]",
"config.write_text(text, encoding='utf-8')", "config.write_text(text, encoding='utf-8')",
"PY", "PY",
"python_rc=$?", "python_rc=$?",