mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-09-03 06:07:58 +00:00
79914fe750
* fix: recognize clean one-shot completions in health gate and drift Treat exit 0 with restart policy no/absent as successful completion so init and migration jobs no longer fail post-update observation or show as service-missing, while long-running restart policies still fail closed. * fix: ignore residual health on clean one-shots and honor deploy.restart_policy Completed exit-0 jobs with no-restart intent no longer fail the health gate on leftover starting/unhealthy state, and Drift treats deploy.restart_policy with Compose precedence so any/on-failure services are not mistaken for one-shots. * fix: require explicit Compose restart no for one-shot recognition Docker inspect reports restart no for both intentional jobs and bare services that omit restart, so Health Gate and Drift now require declared restart:""no"" (or deploy.restart_policy condition none) and load Compose intent once per gate.
78 lines
2.9 KiB
TypeScript
78 lines
2.9 KiB
TypeScript
/**
|
|
* Helpers for clean one-shot completion and Compose restart-intent normalization.
|
|
* Used by Health Gate and Drift service-presence only; does not change bulk
|
|
* status, Auto-Heal, or atomic-deploy helpers.
|
|
*/
|
|
|
|
/**
|
|
* True only for an explicit Compose `restart: "no"` (after normalization).
|
|
* Absent / null / empty do not qualify: Docker reports HostConfig restart
|
|
* "no" for both intentional one-shots and bare long-running services that
|
|
* omit `restart:`, so consumers must pass declared Compose intent, not inspect.
|
|
*/
|
|
export function isNoRestartPolicy(policy: string | null | undefined): boolean {
|
|
return policy === 'no';
|
|
}
|
|
|
|
/**
|
|
* Normalize Compose restart intent for Drift / declared-model consumers.
|
|
*
|
|
* Compose precedence: when `deploy.restart_policy` is set, its `condition`
|
|
* wins; otherwise the service-level `restart` field is used. Conditions map to
|
|
* Docker-like policy names so {@link isNoRestartPolicy} stays the single gate:
|
|
* `none` → `no`, `any` → `always`, `on-failure` → `on-failure`. Missing
|
|
* condition defaults to `any` (Compose default). Unknown shapes fail closed.
|
|
* Absent service restart (no deploy policy) stays null and is not one-shot eligible.
|
|
*/
|
|
export function normalizeComposeRestartIntent(
|
|
serviceRestart: string | null | undefined,
|
|
deploy?: Record<string, unknown> | null,
|
|
): string | null {
|
|
if (deploy && Object.prototype.hasOwnProperty.call(deploy, 'restart_policy')) {
|
|
const policy = deploy['restart_policy'];
|
|
if (policy === null || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
return 'always';
|
|
}
|
|
const condition = (policy as Record<string, unknown>).condition;
|
|
// Missing, empty, or non-string → Compose default `any` → always.
|
|
if (typeof condition !== 'string' || condition === '') {
|
|
return 'always';
|
|
}
|
|
switch (condition.toLowerCase()) {
|
|
case 'none':
|
|
return 'no';
|
|
case 'on-failure':
|
|
return 'on-failure';
|
|
case 'any':
|
|
return 'always';
|
|
default:
|
|
return 'always';
|
|
}
|
|
}
|
|
if (serviceRestart == null || serviceRestart === '') return null;
|
|
return serviceRestart;
|
|
}
|
|
|
|
export interface OneShotCompletionInput {
|
|
state: string;
|
|
/** Exact Docker exit code; null means unknown (fail closed). */
|
|
exitCode: number | null;
|
|
/**
|
|
* Declared Compose restart intent after normalization (`"no"` for explicit
|
|
* one-shots / `deploy.restart_policy.condition: none`). Do not pass Docker
|
|
* inspect HostConfig values here.
|
|
*/
|
|
restartPolicy: string | null | undefined;
|
|
}
|
|
|
|
/**
|
|
* True only for an exited container with exit code exactly 0 and explicit
|
|
* declared restart `"no"`. Null/absent restart, null exit codes, and restarting
|
|
* policies never qualify.
|
|
*/
|
|
export function isCleanOneShotCompletion(input: OneShotCompletionInput): boolean {
|
|
return input.state === 'exited'
|
|
&& input.exitCode === 0
|
|
&& isNoRestartPolicy(input.restartPolicy);
|
|
}
|