b1c81ba518
- template docker/pull-check.sh.tpl (pull sans up, inspect before/after) - dockerPull: parseDockerPullCheck + buildDockerPullResult (TDD) — compare image id/digest/label OCI → services up_to_date|updates_available|error, changes operation=pulled ; erreurs registry nettoyées (URL/token/password) - dockerDedupKey (digests prioritaires, fallback image ids) + DockerImageChange.dedupKey - pullCheckStack: SSH + upsert docker_stack_services, refuse stack non enabled, refresh Docker séparé (hors refreshMachine, pas de pull auto) - execute: runAction(opts.stackId), branche docker_pull_check, injection stackDir (corrige docker_inspect_current) ; route: allowlist Docker passifs + pull_check, destructives toujours hors API jusqu'à action_requests (SJ-6) Pas de migration (schéma SJ-4 suffisant). tsc 0 erreur · 85 tests · build OK. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.9 KiB
TypeScript
50 lines
1.9 KiB
TypeScript
// server/routes/actions.ts
|
|
import { Hono } from "hono";
|
|
import { readFileSync } from "node:fs";
|
|
import { runAction, listExecutions, getExecution } from "../services/execute.js";
|
|
import type { ActionType } from "@shared/types.js";
|
|
|
|
export const actionsRoutes = new Hono();
|
|
|
|
// Actions autorisées par l'API. Les actions destructives Docker
|
|
// (docker_compose_apply/down, docker_prune_images agressif) restent hors API
|
|
// jusqu'au socle de validation (action_requests, SJ-6).
|
|
const ALLOWED_ACTIONS: ActionType[] = [
|
|
"apt_full_upgrade",
|
|
"reboot",
|
|
// Docker passifs / non-applicatifs (SJ-4/SJ-5).
|
|
"docker_scan",
|
|
"docker_inspect_current",
|
|
"docker_pull_check",
|
|
];
|
|
// Actions Docker ciblant un stack précis : stackId obligatoire.
|
|
const NEED_STACK: ActionType[] = ["docker_inspect_current", "docker_pull_check"];
|
|
|
|
actionsRoutes.post("/:id/actions", async (c) => {
|
|
const { action, stackId } = (await c.req.json()) as { action: ActionType; stackId?: string };
|
|
if (!ALLOWED_ACTIONS.includes(action)) {
|
|
return c.json({ error: "Action non autorisée" }, 400);
|
|
}
|
|
if (NEED_STACK.includes(action) && !stackId) {
|
|
return c.json({ error: "stackId requis pour cette action" }, 400);
|
|
}
|
|
// Exécution lancée en arrière-plan; le suivi se fait via WebSocket.
|
|
runAction(c.req.param("id"), action, stackId ? { stackId } : undefined).catch((err) =>
|
|
console.error("[action]", (err as Error).message),
|
|
);
|
|
return c.json({ ok: true, action }, 202);
|
|
});
|
|
|
|
actionsRoutes.get("/:id/executions", (c) => c.json(listExecutions(c.req.param("id"))));
|
|
|
|
actionsRoutes.get("/:id/executions/:execId", (c) => {
|
|
const e = getExecution(c.req.param("execId"));
|
|
return e ? c.json(e) : c.json({ error: "Exécution introuvable" }, 404);
|
|
});
|
|
|
|
actionsRoutes.get("/:id/executions/:execId/report", (c) => {
|
|
const e = getExecution(c.req.param("execId"));
|
|
if (!e?.reportPath) return c.json({ error: "Rapport introuvable" }, 404);
|
|
return c.text(readFileSync(e.reportPath, "utf8"));
|
|
});
|