feat: add maintenance toggle command
This commit is contained in:
+115
@@ -11,6 +11,13 @@ import {
|
||||
} from "./build-kubernetes";
|
||||
import { FilesystemCas } from "./cas";
|
||||
import { KubernetesAuthStore } from "./kubernetes-store";
|
||||
import {
|
||||
MAINTENANCE_ROUTE_NAME,
|
||||
MAINTENANCE_STATE_NAME,
|
||||
MAINTENANCE_STATE_NAMESPACE,
|
||||
MaintenanceService,
|
||||
} from "./maintenance";
|
||||
import { PatchStrategy, type KubernetesObject } from "@kubernetes/client-node";
|
||||
import {
|
||||
createKubernetesClients,
|
||||
createKubernetesLeaseObjects,
|
||||
@@ -112,6 +119,107 @@ const leases = new KubernetesWorkspaceLeaseProvider(
|
||||
createKubernetesLeaseObjects(clients.coordination),
|
||||
namespace,
|
||||
);
|
||||
const maintenance = new MaintenanceService(
|
||||
{
|
||||
async readState() {
|
||||
try {
|
||||
const value = (await clients.objects.read({
|
||||
apiVersion: "v1",
|
||||
kind: "ConfigMap",
|
||||
metadata: {
|
||||
name: MAINTENANCE_STATE_NAME,
|
||||
namespace: MAINTENANCE_STATE_NAMESPACE,
|
||||
},
|
||||
})) as { data?: Record<string, string> };
|
||||
const hosts = JSON.parse(value.data?.hosts ?? "[]");
|
||||
return Array.isArray(hosts) &&
|
||||
hosts.every((host) => typeof host === "string")
|
||||
? { hosts }
|
||||
: { hosts: [] };
|
||||
} catch (error) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
(("code" in error && error.code === 404) ||
|
||||
("statusCode" in error && error.statusCode === 404))
|
||||
)
|
||||
return;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async writeState({ hosts }) {
|
||||
await clients.objects.patch(
|
||||
{
|
||||
apiVersion: "v1",
|
||||
kind: "ConfigMap",
|
||||
metadata: {
|
||||
name: MAINTENANCE_STATE_NAME,
|
||||
namespace: MAINTENANCE_STATE_NAMESPACE,
|
||||
labels: { "kuber.astrxl.dev/type": "maintenance" },
|
||||
},
|
||||
data: { hosts: JSON.stringify(hosts) },
|
||||
} as KubernetesObject,
|
||||
undefined,
|
||||
undefined,
|
||||
"kuber-server",
|
||||
true,
|
||||
PatchStrategy.ServerSideApply,
|
||||
);
|
||||
},
|
||||
async routeExists() {
|
||||
try {
|
||||
await clients.objects.read({
|
||||
apiVersion: "traefik.io/v1alpha1",
|
||||
kind: "IngressRoute",
|
||||
metadata: { name: MAINTENANCE_ROUTE_NAME, namespace: "routing" },
|
||||
});
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
(("code" in error && error.code === 404) ||
|
||||
("statusCode" in error && error.statusCode === 404))
|
||||
)
|
||||
return false;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async apply(resource) {
|
||||
await clients.objects.patch(
|
||||
resource as KubernetesObject,
|
||||
undefined,
|
||||
undefined,
|
||||
"kuber-server",
|
||||
true,
|
||||
PatchStrategy.ServerSideApply,
|
||||
);
|
||||
},
|
||||
async deleteRoute() {
|
||||
try {
|
||||
await clients.objects.delete({
|
||||
apiVersion: "traefik.io/v1alpha1",
|
||||
kind: "IngressRoute",
|
||||
metadata: { name: MAINTENANCE_ROUTE_NAME, namespace: "routing" },
|
||||
});
|
||||
} catch (error) {
|
||||
if (
|
||||
!(
|
||||
error &&
|
||||
typeof error === "object" &&
|
||||
(("code" in error && error.code === 404) ||
|
||||
("statusCode" in error && error.statusCode === 404))
|
||||
)
|
||||
)
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
},
|
||||
new KubernetesWorkspaceLeaseProvider(
|
||||
createKubernetesLeaseObjects(clients.coordination),
|
||||
MAINTENANCE_STATE_NAMESPACE,
|
||||
),
|
||||
);
|
||||
const builds = new BuildController({
|
||||
cas: new FilesystemCas(`${dataRoot}/cas`),
|
||||
store: buildStore,
|
||||
@@ -177,6 +285,12 @@ try {
|
||||
console.error("Startup operation recovery failed", error);
|
||||
}
|
||||
|
||||
try {
|
||||
await maintenance.reconcile();
|
||||
} catch (error) {
|
||||
console.error("Startup maintenance reconciliation failed", error);
|
||||
}
|
||||
|
||||
const sessionCleanupIntervalMs = Number(
|
||||
process.env.KUBER_SESSION_CLEANUP_MS ?? 10 * 60 * 1000,
|
||||
);
|
||||
@@ -218,6 +332,7 @@ const app = createApp({
|
||||
builds,
|
||||
logs,
|
||||
execService,
|
||||
maintenance,
|
||||
leases,
|
||||
resolveImage: async (project, service) => {
|
||||
const image = buildImageName(registry, project, service);
|
||||
|
||||
Reference in New Issue
Block a user