fix: honor Komodo health and prefer fresh telemetry across fleet status

This commit is contained in:
2026-09-27 15:34:30 -04:00
parent 46a89ea4b0
commit 8ea696417f
27 changed files with 488 additions and 315 deletions
+52 -75
View File
@@ -257,85 +257,62 @@ export type KomodoHealthResult =
| { ok: true; state: string }
| { ok: false; reason: string; state?: string; snippet?: string };
// Shape-agnostic scan for the container's running-state. Walks the entire
// payload looking for anything that resembles a state field. Covered shapes:
// - Docker inspect: { State: { Status: "running", Running: true, Health: { Status: "healthy" } } }
// - Komodo GetDeployment: top-level or nested `state`, `Status`, `container_state`
// - Komodo GetDeploymentStatus: { state: "Running" }
// - Anything with `Running: true` at any depth
// When it can't find any of these, callers get the raw JSON in the error so
// we can spot which field to add here.
const STATE_KEYS = new Set([
'state',
'State',
'status',
'Status',
'container_state',
'containerState',
'deployment_state',
'deploymentState',
'docker_status'
]);
// Komodo's container summary has { state: "running", container: { status:
// "Up 35 seconds (health: starting)" } }. Inspect responses instead have
// State.Health.Status (or snake/lowercase equivalents). Runtime health wins
// over a coarse running flag. Unknown/configuration strings are not readiness.
export function extractContainerState(payload: unknown): string | null {
const seen = new Set<unknown>();
let found: string | null = null;
const walk = (v: unknown): void => {
if (found || v === null || typeof v !== 'object' || seen.has(v)) return;
seen.add(v);
if (Array.isArray(v)) {
for (const item of v) walk(item);
return;
const known = new Set(['running','healthy','starting','restarting','unhealthy','stopped','exited','created','dead','paused','removing','notdeployed','unknown']);
const stateOf = (value: unknown): string | null => {
if (typeof value !== 'string') return null;
const state = value.toLowerCase().trim();
if (known.has(state)) return state;
if (/^up\b/.test(state)) {
const health = state.match(/\((?:health:\s*)?(starting|unhealthy|healthy)\)/)?.[1];
return health && health !== 'healthy' ? health : 'running';
}
const obj = v as Record<string, unknown>;
// Komodo summaries expose health alongside a flat running state.
const flatState = obj['state'] ?? obj['State'] ?? obj['status'] ?? obj['Status'];
const flatHealth = obj['health'] ?? obj['Health'];
const healthStatus = typeof flatHealth === 'string' ? flatHealth
: flatHealth && typeof flatHealth === 'object'
? ((flatHealth as Record<string, unknown>)['status'] ?? (flatHealth as Record<string, unknown>)['Status']) : null;
if (typeof flatState === 'string' && flatState.toLowerCase() === 'running'
&& typeof healthStatus === 'string' && healthStatus.toLowerCase() !== 'healthy') {
found = healthStatus;
return;
}
// Docker inspect nested: { State: { Status, Running, Health: { Status } } }
const stateObj = obj['State'];
if (stateObj && typeof stateObj === 'object') {
const so = stateObj as Record<string, unknown>;
const status = so['Status'];
const running = so['Running'];
const health = so['Health'];
// A running process can still be starting or unhealthy. Honor an
// explicit Docker health check before accepting its running state.
if ((running === true || status === 'running') && health && typeof health === 'object') {
const hs = (health as Record<string, unknown>)['Status'];
if (typeof hs === 'string' && hs.toLowerCase() !== 'healthy') { found = hs; return; }
}
if (typeof status === 'string') { found = status; return; }
if (running === true) { found = 'running'; return; }
if (health && typeof health === 'object') {
const hs = (health as Record<string, unknown>)['Status'];
if (typeof hs === 'string') { found = hs; return; }
}
}
// Flat string-valued state under any of the recognized keys.
for (const key of STATE_KEYS) {
const val = obj[key];
if (typeof val === 'string' && val.length > 0) {
found = val;
return;
}
}
for (const val of Object.values(obj)) walk(val);
if (/^exited\b/.test(state)) return 'exited';
if (/^restarting\b/.test(state)) return 'restarting';
return null;
};
walk(payload);
return found;
const walk = (value: unknown): string | null => {
if (!value || typeof value !== 'object' || seen.has(value)) return null;
seen.add(value);
if (Array.isArray(value)) {
const states = value.map(walk).filter((state): state is string => !!state);
return states.find(state => !['running','healthy'].includes(state)) ?? states[0] ?? null;
}
const object = value as Record<string, unknown>;
const rawState = object.State ?? object.state;
const state = stateOf(rawState) ?? stateOf(object.container_state ?? object.containerState ?? object.deployment_state ?? object.deploymentState);
// A stopped process is never ready just because its previous health check passed.
if (state && !['running','healthy','unknown'].includes(state)) return state;
if (rawState && typeof rawState === 'object') {
const nested = walk(rawState);
if (nested) return nested;
}
for (const key of ['container','Container','containers','Containers']) {
if (object[key] === null && state === 'running') return 'unknown';
const nested = walk(object[key]);
if (nested) return nested;
}
const status = stateOf(object.Status ?? object.status ?? object.docker_status);
if (status && !['running','healthy'].includes(status)) return status;
const health = object.Health ?? object.health;
const healthState = stateOf(typeof health === 'object' && health ?
(health as Record<string, unknown>).Status ?? (health as Record<string, unknown>).status : health);
if (healthState && healthState !== 'healthy') return healthState;
if (state || status) return state ?? status;
if (object.Running === true || object.running === true) return healthState ?? 'running';
for (const [key, item] of Object.entries(object)) {
if (['config','Config','labels','Labels'].includes(key)) continue;
const nested = walk(item);
if (nested) return nested;
}
return null;
};
return walk(payload);
}
const HEALTHY_STATES = new Set(['running', 'healthy', 'ok', 'started', 'up']);