feat: add maintenance toggle command
This commit is contained in:
+66
-2
@@ -72,6 +72,11 @@ import {
|
||||
safeLog,
|
||||
type ProcessLogEntry,
|
||||
} from "../lib/request-log";
|
||||
import {
|
||||
MaintenanceBusyError,
|
||||
normalizeMaintenanceHost,
|
||||
type MaintenanceService,
|
||||
} from "./maintenance";
|
||||
|
||||
const API_PREFIX = "/api/v2";
|
||||
const RUNTIME_SESSION_MS = 24 * 60 * 60 * 1000;
|
||||
@@ -103,6 +108,7 @@ export type AppOptions = {
|
||||
builds?: BuildController;
|
||||
logs?: LogService;
|
||||
execService?: ExecService;
|
||||
maintenance?: MaintenanceService;
|
||||
resolveImage?: (
|
||||
project: string,
|
||||
service: string,
|
||||
@@ -1792,6 +1798,57 @@ export function createApp(
|
||||
return response(adopted);
|
||||
}
|
||||
|
||||
match = new RegExp(`^${API_PREFIX}/maintenance/([^/]+)$`).exec(path);
|
||||
if (match) {
|
||||
const host = pathPart(match[1]!);
|
||||
try {
|
||||
normalizeMaintenanceHost(host);
|
||||
} catch (error) {
|
||||
throw new HttpError(
|
||||
400,
|
||||
"Invalid hostname",
|
||||
"MAINTENANCE_HOST_INVALID",
|
||||
error instanceof Error ? error.message : "Invalid hostname",
|
||||
);
|
||||
}
|
||||
if (!options.maintenance)
|
||||
throw new HttpError(
|
||||
503,
|
||||
"Service unavailable",
|
||||
"MAINTENANCE_UNAVAILABLE",
|
||||
"Maintenance service is not configured",
|
||||
);
|
||||
if (identity.apiKey?.workspace) {
|
||||
await audit(identity, request, "authorization.workspace", "denied", {
|
||||
workspace: identity.apiKey.workspace,
|
||||
});
|
||||
throw new HttpError(
|
||||
403,
|
||||
"Forbidden",
|
||||
"MAINTENANCE_GLOBAL_SCOPE_REQUIRED",
|
||||
"Maintenance requires an unscoped API key or an operator session",
|
||||
);
|
||||
}
|
||||
if (request.method === "GET") {
|
||||
await requireCapability(identity, request, "kubernetes:write");
|
||||
return response(await options.maintenance.status(host));
|
||||
}
|
||||
if (request.method === "POST") {
|
||||
await requireCapability(identity, request, "kubernetes:write");
|
||||
const body = await readJson(request);
|
||||
if (typeof body.enabled !== "boolean")
|
||||
throw new HttpError(
|
||||
400,
|
||||
"Invalid request",
|
||||
"MAINTENANCE_ENABLED_REQUIRED",
|
||||
"enabled must be a boolean",
|
||||
);
|
||||
const result = await options.maintenance.set(host, body.enabled);
|
||||
await audit(identity, request, "maintenance.toggle", "success", result);
|
||||
return response(result);
|
||||
}
|
||||
}
|
||||
|
||||
match = new RegExp(`^${API_PREFIX}/workspaces/([^/]+)(?:/(.*))?$`).exec(
|
||||
path,
|
||||
);
|
||||
@@ -2326,9 +2383,16 @@ export function createApp(
|
||||
error instanceof WorkspaceConflictError ||
|
||||
error instanceof OperationConflictError ||
|
||||
error instanceof BuildConflictError ||
|
||||
error instanceof WorkspaceAdoptionError
|
||||
error instanceof WorkspaceAdoptionError ||
|
||||
error instanceof MaintenanceBusyError
|
||||
)
|
||||
return new HttpError(409, "Conflict", error.code, error.message);
|
||||
return new HttpError(
|
||||
409,
|
||||
"Conflict",
|
||||
error.code,
|
||||
error.message,
|
||||
error instanceof MaintenanceBusyError ? { "retry-after": "1" } : undefined,
|
||||
);
|
||||
if (
|
||||
error instanceof WorkspaceValidationError ||
|
||||
error instanceof OperationValidationError ||
|
||||
|
||||
Reference in New Issue
Block a user