/** * Bounded reaper for throwaway containers (T13436), behind * `cleo doctor system --repair` (dry run) and `--repair --apply`. * * The convention: a throwaway container is started with `docker run --rm` or * labelled `cleo.task=` and `cleo.ttl=` (`4h`, `2d`). The reaper * removes exactly two things: * - STOPPED containers carrying `cleo.ttl` created longer ago than it (opted in * by the label); * - anonymous volumes no container uses (64-hex names: the ones * `docker run` without `--rm` leaves behind, from any container). * * A RUNNING labelled container is never removed: one past its ttl since it * last STARTED (a restart resets the clock, creation does not) is only * reported, because a reused container may be mid-test (T13451). It never * touches an unlabelled container or a named volume. * * @task T13436 * @epic T13434 */ /** The label that opts a container into reaping, with its time to live. */ export declare const CLEO_TTL_LABEL = "cleo.ttl"; /** The label naming the task a container was started for. */ export declare const CLEO_TASK_LABEL = "cleo.task"; /** `docker ps` columns {@link planContainerReap} reads; labels last (they may hold anything but tabs). */ export declare const LABELLED_FORMAT = "{{.ID}}\t{{.Names}}\t{{.Image}}\t{{.CreatedAt}}\t{{.State}}\t{{.Labels}}"; /** A labelled container past its ttl. */ export interface ReapContainer { readonly id: string; readonly name: string; readonly image: string; readonly task: string | null; readonly ttl: string; /** Since creation for a stopped container, since its last start for a running one. */ readonly ageSec: number; } /** One removal and its outcome. */ export interface ReapOutcome { readonly target: string; readonly kind: 'container' | 'volume'; readonly ok: boolean; readonly error?: string; } /** What the reaper found, and (with `--apply`) what it removed. */ export interface ContainerReapPlan { readonly mode: 'dry-run' | 'apply'; /** False when docker is absent or did not answer: nothing was assessed. */ readonly dockerAvailable: boolean; /** Stopped containers past their ttl: removed by `--apply`. */ readonly containers: readonly ReapContainer[]; /** Running containers past their ttl since their last start: reported, never removed. */ readonly runningExpired: readonly ReapContainer[]; readonly volumes: readonly string[]; /** Labelled containers whose `cleo.ttl` could not be parsed (left alone). */ readonly invalidTtl: readonly string[]; readonly applied: readonly ReapOutcome[]; } /** Raw docker output for {@link planContainerReap}. */ export interface ContainerReapInput { /** `docker ps -a --filter label=cleo.ttl --format` {@link LABELLED_FORMAT}. */ readonly labelledContainers: string | null; /** Full container id → `State.StartedAt` (RFC 3339), for the running candidates. */ readonly startedAt: Readonly>; /** * Full container id → `State.FinishedAt`, for the stopped candidates: a stopped * container's ttl runs from when it last stopped, so one reused across runs * is not removed between them. Docker's zero time (never started) falls back * to `CreatedAt`. */ readonly finishedAt?: Readonly>; /** `docker volume ls -q -f dangling=true`. */ readonly danglingVolumes: string | null; readonly nowMs: number; } /** `90s`, `30m`, `4h`, `2d` to seconds; `null` when not a duration. */ export declare function parseTtl(ttl: string): number | null; /** * Decide what to reap. Pure. * * @example * ```ts * planContainerReap({ labelledContainers, danglingVolumes, nowMs: Date.now() }).containers; * ``` * * @task T13436 */ export declare function planContainerReap(input: ContainerReapInput): ContainerReapPlan; /** Runs `docker `; resolves to the error text, or `null` on success. */ export type DockerRunFn = (args: readonly string[]) => Promise; /** * Read docker for {@link planContainerReap}. Never throws; an absent docker * gives `null` outputs and a plan with `dockerAvailable: false`. * * @task T13436 */ export declare function collectContainerReapInput(): Promise; /** * Remove exactly what `plan` lists: each expired STOPPED labelled container * with `docker rm -v`, never `-f`, so one started since the plan is refused by * docker and reported (its anonymous volumes go with it, named ones never do), * then each anonymous dangling volume. A volume that gained a user since the * plan is refused by docker and reported, not forced. * * @task T13436 */ export declare function applyContainerReap(plan: ContainerReapPlan, docker?: DockerRunFn): Promise; //# sourceMappingURL=container-reaper.d.ts.map