diff --git a/src/mgr/session-pvc.ts b/src/mgr/session-pvc.ts index 2063d79..8433231 100644 --- a/src/mgr/session-pvc.ts +++ b/src/mgr/session-pvc.ts @@ -1,4 +1,5 @@ import { spawn } from "node:child_process"; +import { createHash } from "node:crypto"; import type { AgentRunStore } from "./store.js"; import type { JsonRecord } from "../common/types.js"; import { AgentRunError } from "../common/errors.js"; @@ -37,6 +38,7 @@ const defaultStorageClassName = "local-path"; const defaultPvcSize = "1Gi"; const defaultSubdir = "sessions"; const defaultNamespace = "agentrun-v01"; +const maxKubernetesLabelValueLength = 63; export function sessionPvcNameFor(sessionId: string): string { const sanitized = sanitizeSessionIdForPvc(sessionId); @@ -47,6 +49,15 @@ export function sanitizeSessionIdForPvc(sessionId: string): string { return sessionId.toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, ""); } +export function sessionPvcLabelValueFor(sessionId: string): string { + const sanitized = sanitizeSessionIdForPvc(sessionId) || "default"; + if (sanitized.length <= maxKubernetesLabelValueLength) return sanitized; + const hash = createHash("sha256").update(sessionId).digest("hex").slice(0, 12); + const prefixLength = maxKubernetesLabelValueLength - hash.length - 1; + const prefix = sanitized.slice(0, prefixLength).replace(/-+$/gu, "") || "session"; + return `${prefix}-${hash}`; +} + function runtimeNamespace(): string { const value = process.env.AGENTRUN_RUNTIME_NAMESPACE?.trim(); return value && value.length > 0 ? value : defaultNamespace; @@ -96,7 +107,12 @@ export async function createSessionPvc(input: { store: AgentRunStore; sessionId: const manifest: JsonRecord = { apiVersion: "v1", kind: "PersistentVolumeClaim", - metadata: { name: spec.pvcName, namespace: spec.namespace, labels: { "agentrun.pikastech.local/session-id": input.sessionId, "agentrun.pikastech.local/lane": runtimeLane() } }, + metadata: { + name: spec.pvcName, + namespace: spec.namespace, + labels: { "agentrun.pikastech.local/session-id": sessionPvcLabelValueFor(input.sessionId), "agentrun.pikastech.local/lane": runtimeLane() }, + annotations: { "agentrun.pikastech.local/session-id": input.sessionId }, + }, spec: { accessModes: ["ReadWriteOnce"], storageClassName: spec.storageClassName, resources: { requests: { storage: spec.size } } }, }; const handler = resolveHandler(input.options); diff --git a/src/selftest/cases/10-mgr-session-pvc.ts b/src/selftest/cases/10-mgr-session-pvc.ts index 28e2142..35bd55f 100644 --- a/src/selftest/cases/10-mgr-session-pvc.ts +++ b/src/selftest/cases/10-mgr-session-pvc.ts @@ -2,7 +2,7 @@ import assert from "node:assert/strict"; import { startManagerServer } from "../../mgr/server.js"; import { MemoryAgentRunStore } from "../../mgr/store.js"; import { ManagerClient } from "../../mgr/client.js"; -import { buildSessionPvcSpec, createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc, sessionPvcNameFor, sanitizeSessionIdForPvc } from "../../mgr/session-pvc.js"; +import { buildSessionPvcSpec, createSessionPvc, deleteSessionPvc, getSessionPvcSummary, refreshSessionPvcSummary, runSessionStorageGc, sessionPvcLabelValueFor, sessionPvcNameFor, sanitizeSessionIdForPvc } from "../../mgr/session-pvc.js"; import type { KubectlHandler, SessionPvcOptions } from "../../mgr/session-pvc.js"; import type { SelfTestCase } from "../harness.js"; @@ -35,6 +35,11 @@ const selfTest: SelfTestCase = async () => { assert.equal(sessionPvcNameFor("sess_with_underscores_001"), "agentrun-v01-session-sess-with-underscores-001"); assert.equal(sessionPvcNameFor("Sess.UPPER.001"), "agentrun-v01-session-sess-upper-001"); assert.equal(sessionPvcNameFor("---"), "agentrun-v01-session-default"); + const longSessionId = "ses_agentrun_dsflash_go_0606efb7_60fd_4f38_9532_398b46e54808-reset-trcmqlrsnc2r"; + const longSessionLabel = sessionPvcLabelValueFor(longSessionId); + assert.ok(longSessionLabel.length <= 63); + assert.notEqual(longSessionLabel, longSessionId); + assert.match(longSessionLabel, /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/u); const previousRuntimeNamespace = process.env.AGENTRUN_RUNTIME_NAMESPACE; try { delete process.env.AGENTRUN_RUNTIME_NAMESPACE;