fix: persist code agent workspace
This commit is contained in:
@@ -10,6 +10,8 @@ export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({
|
||||
model: "gpt-5.5",
|
||||
requiredEnv: Object.freeze([
|
||||
"OPENAI_API_KEY",
|
||||
"HWLAB_CODE_AGENT_WORKSPACE",
|
||||
"HWLAB_CODE_AGENT_CODEX_WORKSPACE",
|
||||
"HWLAB_CODE_AGENT_DEFAULT_PROVIDER_PROFILE",
|
||||
"HWLAB_CODE_AGENT_OPENAI_BASE_URL",
|
||||
"HWLAB_CODE_AGENT_DEEPSEEK_MODEL",
|
||||
@@ -63,6 +65,19 @@ export const DEV_CODE_AGENT_PROVIDER_CONTRACT = Object.freeze({
|
||||
authKey: "auth.json",
|
||||
mountContract: "writable emptyDir CODEX_HOME with read-only config.toml/auth.json file mounts"
|
||||
}),
|
||||
workspace: Object.freeze({
|
||||
path: "/workspace/hwlab",
|
||||
mountPath: "/workspace",
|
||||
sourcePath: "/app",
|
||||
volumeName: "hwlab-code-agent-workspace",
|
||||
claimName: "hwlab-code-agent-workspace",
|
||||
accessMode: "ReadWriteOnce",
|
||||
storage: "8Gi",
|
||||
initContainerName: "hwlab-code-agent-workspace-init",
|
||||
initMarker: ".hwlab-workspace-initialized",
|
||||
rolloutStrategy: "Recreate",
|
||||
mountContract: "persistent RWO PVC mounted at /workspace with copy-once /app bootstrap into /workspace/hwlab"
|
||||
}),
|
||||
noProxyRequired: Object.freeze([
|
||||
"hyueapi.com",
|
||||
".hyueapi.com",
|
||||
@@ -102,6 +117,8 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
|
||||
[contract.forwarder.upstreamEnv]: contract.forwarder.upstreamBaseUrl,
|
||||
[contract.forwarder.portEnv]: String(contract.forwarder.port),
|
||||
[contract.egress.env]: contract.egress.defaultBaseUrl,
|
||||
HWLAB_CODE_AGENT_WORKSPACE: contract.workspace.path,
|
||||
HWLAB_CODE_AGENT_CODEX_WORKSPACE: contract.workspace.path,
|
||||
CODEX_HOME: contract.codexHome.path,
|
||||
NO_PROXY: contract.noProxyRequired.join(","),
|
||||
no_proxy: contract.noProxyRequired.join(","),
|
||||
@@ -146,6 +163,26 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
|
||||
if (workloadInspection.codexHome.authSecretPresent !== true) {
|
||||
missingCodexHomeContract.push(`${contract.codexHome.secretName}/${contract.codexHome.authKey}`);
|
||||
}
|
||||
const missingWorkspaceContract = [];
|
||||
if (
|
||||
deployEnv?.HWLAB_CODE_AGENT_WORKSPACE !== contract.workspace.path ||
|
||||
deployEnv?.HWLAB_CODE_AGENT_CODEX_WORKSPACE !== contract.workspace.path ||
|
||||
workloadInspection.workspace.present !== true
|
||||
) {
|
||||
missingWorkspaceContract.push(`Code Agent workspace must be ${contract.workspace.path}`);
|
||||
}
|
||||
if (workloadInspection.workspace.writableMountPresent !== true) {
|
||||
missingWorkspaceContract.push(`${contract.workspace.mountPath} must be a writable volumeMount`);
|
||||
}
|
||||
if (workloadInspection.workspace.persistentVolumeClaimPresent !== true) {
|
||||
missingWorkspaceContract.push(`${contract.workspace.volumeName} must use PVC ${contract.workspace.claimName}`);
|
||||
}
|
||||
if (workloadInspection.workspace.initContainerPresent !== true) {
|
||||
missingWorkspaceContract.push(`${contract.workspace.initContainerName} must bootstrap ${contract.workspace.path} from ${contract.workspace.sourcePath}`);
|
||||
}
|
||||
if (workloadInspection.workspace.recreateStrategy !== true) {
|
||||
missingWorkspaceContract.push(`hwlab-cloud-api rollout strategy must be ${contract.workspace.rolloutStrategy}`);
|
||||
}
|
||||
const missingNoProxyContract = [...new Set([
|
||||
...noProxyMissing(deployEnv?.NO_PROXY, contract.noProxyRequired),
|
||||
...noProxyMissing(deployEnv?.no_proxy, contract.noProxyRequired),
|
||||
@@ -160,6 +197,7 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
|
||||
missingSecretRefs.length === 0 &&
|
||||
missingEgressContract.length === 0 &&
|
||||
missingCodexHomeContract.length === 0 &&
|
||||
missingWorkspaceContract.length === 0 &&
|
||||
missingNoProxyContract.length === 0 &&
|
||||
missingForwarderContract.length === 0;
|
||||
|
||||
@@ -213,9 +251,11 @@ export function inspectCodeAgentProviderManifestRefs({ deployEnv = {}, workloadE
|
||||
missingSecretRefs,
|
||||
missingEgressContract,
|
||||
missingCodexHomeContract,
|
||||
missingWorkspaceContract,
|
||||
missingNoProxyContract,
|
||||
missingForwarderContract,
|
||||
codexHome: workloadInspection.codexHome,
|
||||
workspace: workloadInspection.workspace,
|
||||
forwarder: workloadInspection.forwarder,
|
||||
noProxy: workloadInspection.noProxy,
|
||||
secretMaterialRead: false,
|
||||
@@ -240,6 +280,8 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
|
||||
[contract.forwarder.upstreamEnv]: contract.forwarder.upstreamBaseUrl,
|
||||
[contract.forwarder.portEnv]: String(contract.forwarder.port),
|
||||
[contract.egress.env]: contract.egress.defaultBaseUrl,
|
||||
HWLAB_CODE_AGENT_WORKSPACE: contract.workspace.path,
|
||||
HWLAB_CODE_AGENT_CODEX_WORKSPACE: contract.workspace.path,
|
||||
CODEX_HOME: contract.codexHome.path,
|
||||
NO_PROXY: contract.noProxyRequired.join(","),
|
||||
no_proxy: contract.noProxyRequired.join(",")
|
||||
@@ -276,11 +318,15 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
|
||||
const matchesDevProxy = baseUrl === contract.egress.defaultBaseUrl;
|
||||
const upstreamMatchesDirectHyueapi = upstreamBaseUrl === contract.egress.upstreamBaseUrl;
|
||||
const codexHomePresent = envEntryValue(getEnvEntry(workloadEnv, "CODEX_HOME")) === contract.codexHome.path;
|
||||
const workspacePathPresent =
|
||||
envEntryValue(getEnvEntry(workloadEnv, "HWLAB_CODE_AGENT_WORKSPACE")) === contract.workspace.path &&
|
||||
envEntryValue(getEnvEntry(workloadEnv, "HWLAB_CODE_AGENT_CODEX_WORKSPACE")) === contract.workspace.path;
|
||||
const noProxyMissingEntries = [...new Set([
|
||||
...noProxyMissing(envEntryValue(getEnvEntry(workloadEnv, "NO_PROXY")), contract.noProxyRequired),
|
||||
...noProxyMissing(envEntryValue(getEnvEntry(workloadEnv, "no_proxy")), contract.noProxyRequired)
|
||||
])];
|
||||
const codexHome = inspectCodexHomeWorkloadMounts(workloadEnv);
|
||||
const workspace = inspectCodeAgentWorkspaceWorkloadMounts(workloadEnv);
|
||||
const forwarder = inspectCodexApiForwarderWorkload(workloadEnv);
|
||||
const ready =
|
||||
missingEnv.length === 0 &&
|
||||
@@ -292,6 +338,8 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
|
||||
codexHome.present === true &&
|
||||
codexHome.configMapPresent === true &&
|
||||
codexHome.authSecretPresent === true &&
|
||||
workspacePathPresent &&
|
||||
workspace.present === true &&
|
||||
noProxyMissingEntries.length === 0 &&
|
||||
forwarder.present === true;
|
||||
|
||||
@@ -327,6 +375,10 @@ export function inspectCodeAgentProviderWorkloadEnv(workloadEnv = {}) {
|
||||
...codexHome,
|
||||
present: codexHomePresent && codexHome.present === true
|
||||
},
|
||||
workspace: {
|
||||
...workspace,
|
||||
present: workspacePathPresent && workspace.present === true
|
||||
},
|
||||
forwarder,
|
||||
noProxy: {
|
||||
required: [...contract.noProxyRequired],
|
||||
@@ -357,6 +409,7 @@ export function buildCodeAgentProviderManifestPlaceholder() {
|
||||
command: [...contract.forwarder.command]
|
||||
},
|
||||
codexHome: { ...contract.codexHome },
|
||||
workspace: { ...contract.workspace },
|
||||
noProxyRequired: [...contract.noProxyRequired],
|
||||
fixtureEvidence: {
|
||||
providerConnected: false,
|
||||
@@ -384,7 +437,9 @@ export function inspectCodeAgentProviderEnv(env = {}) {
|
||||
const noProxyReady = noProxyMissing(env.NO_PROXY, contract.noProxyRequired).length === 0 &&
|
||||
noProxyMissing(env.no_proxy, contract.noProxyRequired).length === 0;
|
||||
const codexHomeReady = firstNonEmpty(env.CODEX_HOME) === contract.codexHome.path;
|
||||
const ready = missingEnv.length === 0 && egressReady && codexHomeReady && noProxyReady;
|
||||
const workspaceReady = firstNonEmpty(env.HWLAB_CODE_AGENT_WORKSPACE) === contract.workspace.path &&
|
||||
firstNonEmpty(env.HWLAB_CODE_AGENT_CODEX_WORKSPACE) === contract.workspace.path;
|
||||
const ready = missingEnv.length === 0 && egressReady && codexHomeReady && workspaceReady && noProxyReady;
|
||||
|
||||
return {
|
||||
contractVersion: contract.contractVersion,
|
||||
@@ -443,6 +498,15 @@ export function inspectCodeAgentProviderEnv(env = {}) {
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true
|
||||
},
|
||||
workspace: {
|
||||
path: contract.workspace.path,
|
||||
mountPath: contract.workspace.mountPath,
|
||||
claimName: contract.workspace.claimName,
|
||||
initContainerName: contract.workspace.initContainerName,
|
||||
rolloutStrategy: contract.workspace.rolloutStrategy,
|
||||
present: workspaceReady,
|
||||
persistentVolumeClaimPresent: "not_observed_by_runtime"
|
||||
},
|
||||
noProxy: {
|
||||
required: [...contract.noProxyRequired],
|
||||
missing: [...new Set([
|
||||
@@ -562,6 +626,56 @@ function inspectCodexHomeWorkloadMounts(workloadEnv = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
function inspectCodeAgentWorkspaceWorkloadMounts(workloadEnv = {}) {
|
||||
const contract = DEV_CODE_AGENT_PROVIDER_CONTRACT;
|
||||
const volumeMounts = Array.isArray(workloadEnv.__volumeMounts) ? workloadEnv.__volumeMounts : [];
|
||||
const volumes = Array.isArray(workloadEnv.__volumes) ? workloadEnv.__volumes : [];
|
||||
const initContainers = Array.isArray(workloadEnv.__initContainers) ? workloadEnv.__initContainers : [];
|
||||
const deploymentStrategy = workloadEnv.__deploymentStrategy;
|
||||
const workspaceMount = volumeMounts.find((entry) =>
|
||||
entry?.name === contract.workspace.volumeName &&
|
||||
entry?.mountPath === contract.workspace.mountPath
|
||||
);
|
||||
const writableMountPresent = Boolean(workspaceMount && workspaceMount.readOnly !== true);
|
||||
const workspaceVolume = volumes.find((entry) =>
|
||||
entry?.name === contract.workspace.volumeName &&
|
||||
entry?.persistentVolumeClaim?.claimName === contract.workspace.claimName
|
||||
);
|
||||
const initContainer = initContainers.find((entry) => entry?.name === contract.workspace.initContainerName);
|
||||
const initContainerMountPresent = (initContainer?.volumeMounts ?? []).some((entry) =>
|
||||
entry?.name === contract.workspace.volumeName &&
|
||||
entry?.mountPath === contract.workspace.mountPath
|
||||
);
|
||||
const initCommand = [...(initContainer?.command ?? []), ...(initContainer?.args ?? [])].join("\n");
|
||||
const copyOnceCommandPresent = initCommand.includes(contract.workspace.sourcePath) &&
|
||||
initCommand.includes(contract.workspace.path) &&
|
||||
initCommand.includes(contract.workspace.initMarker);
|
||||
const recreateStrategy = deploymentStrategy?.type === contract.workspace.rolloutStrategy;
|
||||
const persistentVolumeClaimPresent = Boolean(workspaceVolume);
|
||||
const initContainerPresent = Boolean(initContainer && initContainerMountPresent && copyOnceCommandPresent);
|
||||
return {
|
||||
path: contract.workspace.path,
|
||||
mountPath: contract.workspace.mountPath,
|
||||
volumeName: contract.workspace.volumeName,
|
||||
claimName: contract.workspace.claimName,
|
||||
accessMode: contract.workspace.accessMode,
|
||||
storage: contract.workspace.storage,
|
||||
initContainerName: contract.workspace.initContainerName,
|
||||
initMarker: contract.workspace.initMarker,
|
||||
rolloutStrategy: contract.workspace.rolloutStrategy,
|
||||
present: Boolean(writableMountPresent && persistentVolumeClaimPresent && initContainerPresent && recreateStrategy),
|
||||
writableMountPresent,
|
||||
persistentVolumeClaimPresent,
|
||||
initContainerPresent,
|
||||
initContainerMountPresent,
|
||||
copyOnceCommandPresent,
|
||||
recreateStrategy,
|
||||
mountContract: contract.workspace.mountContract,
|
||||
secretMaterialRead: false,
|
||||
valuesRedacted: true
|
||||
};
|
||||
}
|
||||
|
||||
function noProxyMissing(value, required) {
|
||||
const entries = new Set(String(value ?? "")
|
||||
.split(/[,;\s]+/u)
|
||||
|
||||
Reference in New Issue
Block a user