fix: add JD01 GC retention controls
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import { execFileSync, spawnSync } from "node:child_process";
|
||||
|
||||
function runJson(args) {
|
||||
return JSON.parse(execFileSync("kubectl", args, { encoding: "utf8", maxBuffer: 32 * 1024 * 1024 }));
|
||||
}
|
||||
|
||||
function duBytes(path) {
|
||||
if (!path || !path.startsWith("/var/lib/rancher/k3s/storage/")) return null;
|
||||
const result = spawnSync("du", ["-sb", path], { encoding: "utf8", timeout: 8000 });
|
||||
if (result.status !== 0) return null;
|
||||
const value = Number(result.stdout.trim().split(/\s+/u)[0]);
|
||||
return Number.isFinite(value) ? value : null;
|
||||
}
|
||||
|
||||
const namespace = process.env.NAMESPACE;
|
||||
const confirm = process.env.CONFIRM === "true";
|
||||
const enabled = process.env.ENABLED === "true";
|
||||
const limit = Math.max(1, Math.min(Number(process.env.LIMIT || "100"), 1000));
|
||||
const prefixes = JSON.parse(Buffer.from(process.env.PREFIXES_JSON_B64 || "W10=", "base64").toString("utf8"));
|
||||
|
||||
if (!enabled) {
|
||||
console.log(JSON.stringify({ ok: false, error: "session-pvc-retention-disabled", selectedPvcCount: 0, mutation: false }));
|
||||
process.exit(0);
|
||||
}
|
||||
if (!namespace || !Array.isArray(prefixes) || prefixes.length === 0) throw new Error("session PVC cleanup requires namespace and YAML prefixes");
|
||||
|
||||
const pvData = runJson(["get", "pv", "-o", "json"]);
|
||||
const pvcData = runJson(["-n", namespace, "get", "pvc", "-o", "json"]);
|
||||
const podData = runJson(["-n", namespace, "get", "pod", "-o", "json"]);
|
||||
const pvs = new Map((pvData.items || []).map((pv) => [pv.metadata?.name, pv]));
|
||||
const activeClaims = new Map();
|
||||
for (const pod of podData.items || []) {
|
||||
const phase = pod.status?.phase;
|
||||
if (phase === "Succeeded" || phase === "Failed") continue;
|
||||
for (const volume of pod.spec?.volumes || []) {
|
||||
const claim = volume.persistentVolumeClaim?.claimName;
|
||||
if (!claim) continue;
|
||||
const list = activeClaims.get(claim) || [];
|
||||
list.push(pod.metadata?.name);
|
||||
activeClaims.set(claim, list);
|
||||
}
|
||||
}
|
||||
|
||||
const candidates = [];
|
||||
const protectedRows = [];
|
||||
for (const pvc of pvcData.items || []) {
|
||||
const name = pvc.metadata?.name || "";
|
||||
const matchedPrefix = prefixes.find((prefix) => name.startsWith(prefix));
|
||||
if (!matchedPrefix) continue;
|
||||
const activeMountPods = activeClaims.get(name) || [];
|
||||
const pv = pvs.get(pvc.spec?.volumeName);
|
||||
const storageClass = pvc.spec?.storageClassName || pv?.spec?.storageClassName || null;
|
||||
const reclaimPolicy = pv?.spec?.persistentVolumeReclaimPolicy || null;
|
||||
const hostPath = pv?.spec?.hostPath?.path || pv?.spec?.local?.path || null;
|
||||
const row = {
|
||||
namespace,
|
||||
pvc: name,
|
||||
volume: pvc.spec?.volumeName || null,
|
||||
matchedPrefix,
|
||||
phase: pvc.status?.phase || null,
|
||||
pvPhase: pv?.status?.phase || null,
|
||||
storageClass,
|
||||
reclaimPolicy,
|
||||
activeMountCount: activeMountPods.length,
|
||||
activeMountPods: activeMountPods.slice(0, 5),
|
||||
estimatedBytes: duBytes(hostPath),
|
||||
};
|
||||
if (activeMountPods.length > 0 || storageClass !== "local-path" || reclaimPolicy !== "Delete") {
|
||||
protectedRows.push({ ...row, reason: activeMountPods.length > 0 ? "active-mount" : "not-local-path-delete" });
|
||||
} else {
|
||||
candidates.push(row);
|
||||
}
|
||||
}
|
||||
|
||||
candidates.sort((a, b) => (b.estimatedBytes || 0) - (a.estimatedBytes || 0));
|
||||
const selected = candidates.slice(0, limit);
|
||||
const result = {
|
||||
ok: true,
|
||||
planKind: "agentrun-session-pvc-retention",
|
||||
namespace,
|
||||
dryRun: !confirm,
|
||||
mutation: confirm,
|
||||
criteria: { prefixes, storageClass: "local-path", reclaimPolicy: "Delete", requireNoActiveMount: true, limit },
|
||||
candidatePvcCount: candidates.length,
|
||||
selectedPvcCount: selected.length,
|
||||
protectedPvcCount: protectedRows.length,
|
||||
estimatedReclaimBytes: selected.reduce((sum, item) => sum + (item.estimatedBytes || 0), 0),
|
||||
selectedPreview: selected.slice(0, 12),
|
||||
protectedPreview: protectedRows.slice(0, 12),
|
||||
deletedPvcCount: 0,
|
||||
valuesPrinted: false,
|
||||
};
|
||||
|
||||
if (confirm && selected.length > 0) {
|
||||
for (let index = 0; index < selected.length; index += 50) {
|
||||
execFileSync("kubectl", ["-n", namespace, "delete", "pvc", "--wait=false", ...selected.slice(index, index + 50).map((item) => item.pvc)], { encoding: "utf8", maxBuffer: 1024 * 1024 });
|
||||
}
|
||||
result.deletedPvcCount = selected.length;
|
||||
result.deleteMode = "submit-only-wait-false";
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(result));
|
||||
Reference in New Issue
Block a user