|
|
|
@@ -1503,6 +1503,11 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
"function isEnvReuseContainer(container) { return envValue(container, 'HWLAB_RUNTIME_MODE') === 'env-reuse-git-mirror-checkout' || envValue(container, 'HWLAB_BOOT_SH') !== undefined || envValue(container, 'HWLAB_BOOT_COMMIT') !== undefined; }",
|
|
|
|
|
"function workloadName(item) { return item && item.metadata && item.metadata.labels && item.metadata.labels['app.kubernetes.io/name'] ? String(item.metadata.labels['app.kubernetes.io/name']) : String(item && item.metadata && item.metadata.name || ''); }",
|
|
|
|
|
"function expectedPublicEndpoint(item) { return workloadName(item) === 'hwlab-cloud-web' ? overlay.publicWebUrl : overlay.publicApiUrl; }",
|
|
|
|
|
"function publicExposureCloudWebEnvEntries() {",
|
|
|
|
|
" const exposure = overlay.publicExposure;",
|
|
|
|
|
" if (!exposure || !Array.isArray(exposure.extraProxies)) return [];",
|
|
|
|
|
" return exposure.extraProxies.filter((proxy) => proxy && proxy.cloudWebEnvName && proxy.publicBaseUrl).map((proxy) => ({ name: String(proxy.cloudWebEnvName), value: String(proxy.publicBaseUrl) }));",
|
|
|
|
|
"}",
|
|
|
|
|
"function startupProbeFrom(probe) {",
|
|
|
|
|
" const next = JSON.parse(JSON.stringify(probe));",
|
|
|
|
|
" next.periodSeconds = 10;",
|
|
|
|
@@ -1561,17 +1566,22 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" return changed;",
|
|
|
|
|
"}",
|
|
|
|
|
"function patchRuntimeEnv(item, podSpec) {",
|
|
|
|
|
" if (!isObject(podSpec)) return { publicEndpointChanged: false, dbSslModeChanged: false, codeAgentRuntimeChanged: false };",
|
|
|
|
|
" if (!isObject(podSpec)) return { publicEndpointChanged: false, dbSslModeChanged: false, codeAgentRuntimeChanged: false, cloudWebRuntimeChanged: false };",
|
|
|
|
|
" let publicEndpointChanged = false;",
|
|
|
|
|
" let dbSslModeChanged = false;",
|
|
|
|
|
" let codeAgentRuntimeChanged = false;",
|
|
|
|
|
" let cloudWebRuntimeChanged = false;",
|
|
|
|
|
" const pg = overlay.externalPostgres;",
|
|
|
|
|
" const codeAgentRuntime = overlay.codeAgentRuntime;",
|
|
|
|
|
" const cloudWebEnvEntries = publicExposureCloudWebEnvEntries();",
|
|
|
|
|
" for (const group of ['containers', 'initContainers']) {",
|
|
|
|
|
" for (const container of Array.isArray(podSpec[group]) ? podSpec[group] : []) {",
|
|
|
|
|
" if (!isObject(container)) continue;",
|
|
|
|
|
" if (envValue(container, 'HWLAB_PUBLIC_ENDPOINT') !== undefined) publicEndpointChanged = setEnvValue(container, 'HWLAB_PUBLIC_ENDPOINT', expectedPublicEndpoint(item)) || publicEndpointChanged;",
|
|
|
|
|
" if (pg && pg.sslmode && envValue(container, 'HWLAB_CLOUD_DB_SSL_MODE') !== undefined) dbSslModeChanged = setEnvValue(container, 'HWLAB_CLOUD_DB_SSL_MODE', pg.sslmode) || dbSslModeChanged;",
|
|
|
|
|
" if (workloadName(item) === 'hwlab-cloud-web' && container.name === 'hwlab-cloud-web') {",
|
|
|
|
|
" for (const env of cloudWebEnvEntries) cloudWebRuntimeChanged = setEnvValue(container, env.name, env.value) || cloudWebRuntimeChanged;",
|
|
|
|
|
" }",
|
|
|
|
|
" if (codeAgentRuntime && codeAgentRuntime.enabled && workloadName(item) === 'hwlab-cloud-api' && container.name === 'hwlab-cloud-api') {",
|
|
|
|
|
" codeAgentRuntimeChanged = setEnvValue(container, 'HWLAB_CODE_AGENT_ADAPTER', String(codeAgentRuntime.adapter)) || codeAgentRuntimeChanged;",
|
|
|
|
|
" codeAgentRuntimeChanged = setEnvValue(container, 'AGENTRUN_MGR_URL', String(codeAgentRuntime.managerUrl)) || codeAgentRuntimeChanged;",
|
|
|
|
@@ -1593,7 +1603,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" }",
|
|
|
|
|
" }",
|
|
|
|
|
" }",
|
|
|
|
|
" return { publicEndpointChanged, dbSslModeChanged, codeAgentRuntimeChanged };",
|
|
|
|
|
" return { publicEndpointChanged, dbSslModeChanged, codeAgentRuntimeChanged, cloudWebRuntimeChanged };",
|
|
|
|
|
"}",
|
|
|
|
|
"function patchRuntimeWorkloads() {",
|
|
|
|
|
" let observabilityChanged = false;",
|
|
|
|
@@ -1628,15 +1638,16 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" changed = gitUrlChanged || changed;",
|
|
|
|
|
" gitReadUrlChanged = gitReadUrlChanged || gitUrlChanged;",
|
|
|
|
|
" const envChanged = patchRuntimeEnv(item, podSpecFor(item));",
|
|
|
|
|
" changed = envChanged.publicEndpointChanged || envChanged.dbSslModeChanged || envChanged.codeAgentRuntimeChanged || changed;",
|
|
|
|
|
" changed = envChanged.publicEndpointChanged || envChanged.dbSslModeChanged || envChanged.codeAgentRuntimeChanged || envChanged.cloudWebRuntimeChanged || changed;",
|
|
|
|
|
" publicEndpointChanged = publicEndpointChanged || envChanged.publicEndpointChanged;",
|
|
|
|
|
" dbSslModeChanged = dbSslModeChanged || envChanged.dbSslModeChanged;",
|
|
|
|
|
" codeAgentRuntimeChanged = codeAgentRuntimeChanged || envChanged.codeAgentRuntimeChanged;",
|
|
|
|
|
" cloudWebRuntimeChanged = cloudWebRuntimeChanged || envChanged.cloudWebRuntimeChanged;",
|
|
|
|
|
" }",
|
|
|
|
|
" }",
|
|
|
|
|
" if (changed) writeYamlDocuments(file, docs);",
|
|
|
|
|
" }",
|
|
|
|
|
" return { observabilityChanged, startupProbeChanged, imageRewriteChanged, gitReadUrlChanged, publicEndpointChanged, dbSslModeChanged, codeAgentRuntimeChanged };",
|
|
|
|
|
" return { observabilityChanged, startupProbeChanged, imageRewriteChanged, gitReadUrlChanged, publicEndpointChanged, dbSslModeChanged, codeAgentRuntimeChanged, cloudWebRuntimeChanged };",
|
|
|
|
|
"}",
|
|
|
|
|
"function patchKustomization() {",
|
|
|
|
|
" const file = path.join(runtimePath, 'kustomization.yaml');",
|
|
|
|
@@ -1722,6 +1733,18 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" if (changed) writeYaml(file, normalizeList(items));",
|
|
|
|
|
" return changed;",
|
|
|
|
|
"}",
|
|
|
|
|
"function publicExposureFrpcProxies(exposure) { return [exposure.webProxy, exposure.apiProxy, ...(Array.isArray(exposure.extraProxies) ? exposure.extraProxies : [])].filter(Boolean); }",
|
|
|
|
|
"function renderPublicExposureFrpcProxyToml(proxy) {",
|
|
|
|
|
" return [",
|
|
|
|
|
" '[[proxies]]',",
|
|
|
|
|
" 'name = ' + JSON.stringify(String(proxy.name)),",
|
|
|
|
|
" 'type = \"tcp\"',",
|
|
|
|
|
" 'localIP = ' + JSON.stringify(String(proxy.localIP)),",
|
|
|
|
|
" 'localPort = ' + Number(proxy.localPort),",
|
|
|
|
|
" 'remotePort = ' + Number(proxy.remotePort),",
|
|
|
|
|
" '',",
|
|
|
|
|
" ];",
|
|
|
|
|
"}",
|
|
|
|
|
"function renderPublicExposureFrpcToml(exposure) {",
|
|
|
|
|
" return [",
|
|
|
|
|
" 'serverAddr = ' + JSON.stringify(String(exposure.serverAddr)),",
|
|
|
|
@@ -1729,20 +1752,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" 'loginFailExit = true',",
|
|
|
|
|
" 'auth.token = \"{{ .Envs.HWLAB_FRP_TOKEN }}\"',",
|
|
|
|
|
" '',",
|
|
|
|
|
" '[[proxies]]',",
|
|
|
|
|
" 'name = ' + JSON.stringify(String(exposure.webProxy.name)),",
|
|
|
|
|
" 'type = \"tcp\"',",
|
|
|
|
|
" 'localIP = ' + JSON.stringify(String(exposure.webProxy.localIP)),",
|
|
|
|
|
" 'localPort = ' + Number(exposure.webProxy.localPort),",
|
|
|
|
|
" 'remotePort = ' + Number(exposure.webProxy.remotePort),",
|
|
|
|
|
" '',",
|
|
|
|
|
" '[[proxies]]',",
|
|
|
|
|
" 'name = ' + JSON.stringify(String(exposure.apiProxy.name)),",
|
|
|
|
|
" 'type = \"tcp\"',",
|
|
|
|
|
" 'localIP = ' + JSON.stringify(String(exposure.apiProxy.localIP)),",
|
|
|
|
|
" 'localPort = ' + Number(exposure.apiProxy.localPort),",
|
|
|
|
|
" 'remotePort = ' + Number(exposure.apiProxy.remotePort),",
|
|
|
|
|
" '',",
|
|
|
|
|
" ...publicExposureFrpcProxies(exposure).flatMap(renderPublicExposureFrpcProxyToml),",
|
|
|
|
|
" ].join('\\\\n');",
|
|
|
|
|
"}",
|
|
|
|
|
"function setEnvFromSecret(container, name, secretName, secretKey) {",
|
|
|
|
@@ -1800,7 +1810,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" process.exit(50);",
|
|
|
|
|
" }",
|
|
|
|
|
" if (changed) writeYamlDocuments(file, docs);",
|
|
|
|
|
" console.error(JSON.stringify({ event: 'unidesk-public-exposure-postprocess', ok: true, applied: true, changed, filePath: file, hostname: exposure.hostname, serverAddr: exposure.serverAddr, serverPort: exposure.serverPort, webProxy: exposure.webProxy.name, apiProxy: exposure.apiProxy.name, configSha256: crypto.createHash('sha256').update(toml).digest('hex') }));",
|
|
|
|
|
" console.error(JSON.stringify({ event: 'unidesk-public-exposure-postprocess', ok: true, applied: true, changed, filePath: file, hostname: exposure.hostname, serverAddr: exposure.serverAddr, serverPort: exposure.serverPort, webProxy: exposure.webProxy.name, apiProxy: exposure.apiProxy.name, extraProxyCount: Array.isArray(exposure.extraProxies) ? exposure.extraProxies.length : 0, configSha256: crypto.createHash('sha256').update(toml).digest('hex') }));",
|
|
|
|
|
" return { configured: true, changed, foundConfigMap, foundDeployment };",
|
|
|
|
|
"}",
|
|
|
|
|
"const kustomizationChanged = patchKustomization();",
|
|
|
|
@@ -1808,7 +1818,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
"const externalPostgresChanged = patchExternalPostgres();",
|
|
|
|
|
"const healthContractChanged = patchHealthContract();",
|
|
|
|
|
"const publicExposureChanged = patchPublicExposure();",
|
|
|
|
|
"console.error(JSON.stringify({ event: 'unidesk-runtime-gitops-postprocess', ok: true, runtimePath, sourcePath, pathRelocated: sourcePath !== runtimePath, observabilityPrometheusOperator: overlay.observability ? overlay.observability.prometheusOperator : null, runtimeImageRewriteCount: (overlay.runtimeImageRewrites || []).length, kustomizationChanged, observabilityWorkloadsChanged: runtimeWorkloadsChanged.observabilityChanged, startupProbeChanged: runtimeWorkloadsChanged.startupProbeChanged, runtimeImageRewriteChanged: runtimeWorkloadsChanged.imageRewriteChanged, gitReadUrlChanged: runtimeWorkloadsChanged.gitReadUrlChanged, publicEndpointChanged: runtimeWorkloadsChanged.publicEndpointChanged, dbSslModeChanged: runtimeWorkloadsChanged.dbSslModeChanged, codeAgentRuntimeChanged: runtimeWorkloadsChanged.codeAgentRuntimeChanged, externalPostgresChanged, healthContractChanged, publicExposureChanged }));",
|
|
|
|
|
"console.error(JSON.stringify({ event: 'unidesk-runtime-gitops-postprocess', ok: true, runtimePath, sourcePath, pathRelocated: sourcePath !== runtimePath, observabilityPrometheusOperator: overlay.observability ? overlay.observability.prometheusOperator : null, runtimeImageRewriteCount: (overlay.runtimeImageRewrites || []).length, kustomizationChanged, observabilityWorkloadsChanged: runtimeWorkloadsChanged.observabilityChanged, startupProbeChanged: runtimeWorkloadsChanged.startupProbeChanged, runtimeImageRewriteChanged: runtimeWorkloadsChanged.imageRewriteChanged, gitReadUrlChanged: runtimeWorkloadsChanged.gitReadUrlChanged, publicEndpointChanged: runtimeWorkloadsChanged.publicEndpointChanged, dbSslModeChanged: runtimeWorkloadsChanged.dbSslModeChanged, codeAgentRuntimeChanged: runtimeWorkloadsChanged.codeAgentRuntimeChanged, cloudWebRuntimeChanged: runtimeWorkloadsChanged.cloudWebRuntimeChanged, externalPostgresChanged, healthContractChanged, publicExposureChanged }));",
|
|
|
|
|
"NODE_UNIDESK_RUNTIME_GITOPS_POSTPROCESS`;",
|
|
|
|
|
"}",
|
|
|
|
|
"function runtimeGitopsVerifyScript() {",
|
|
|
|
@@ -1870,6 +1880,11 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
"function isEnvReuseContainer(container) { return envValue(container, 'HWLAB_RUNTIME_MODE') === 'env-reuse-git-mirror-checkout' || envValue(container, 'HWLAB_BOOT_SH') !== undefined || envValue(container, 'HWLAB_BOOT_COMMIT') !== undefined; }",
|
|
|
|
|
"function workloadName(item) { return item && item.metadata && item.metadata.labels && item.metadata.labels['app.kubernetes.io/name'] ? String(item.metadata.labels['app.kubernetes.io/name']) : String(item && item.metadata && item.metadata.name || ''); }",
|
|
|
|
|
"function expectedPublicEndpoint(item) { return workloadName(item) === 'hwlab-cloud-web' ? overlay.publicWebUrl : overlay.publicApiUrl; }",
|
|
|
|
|
"function publicExposureCloudWebEnvEntries() {",
|
|
|
|
|
" const exposure = overlay.publicExposure;",
|
|
|
|
|
" if (!exposure || !Array.isArray(exposure.extraProxies)) return [];",
|
|
|
|
|
" return exposure.extraProxies.filter((proxy) => proxy && proxy.cloudWebEnvName && proxy.publicBaseUrl).map((proxy) => ({ name: String(proxy.cloudWebEnvName), value: String(proxy.publicBaseUrl) }));",
|
|
|
|
|
"}",
|
|
|
|
|
"function workloadRef(item, file, container) { return { file, kind: item && item.kind, name: item && item.metadata && item.metadata.name, container: container && container.name }; }",
|
|
|
|
|
"function workloadChecks() {",
|
|
|
|
|
" const metricsRefs = [];",
|
|
|
|
@@ -1879,8 +1894,14 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" const wrongPublicEndpoints = [];",
|
|
|
|
|
" const wrongDbSslModes = [];",
|
|
|
|
|
" const wrongCodeAgentRuntimeEnvs = [];",
|
|
|
|
|
" const wrongCloudWebRuntimeEnvs = [];",
|
|
|
|
|
" const rewriteSources = new Set((overlay.runtimeImageRewrites || []).map((item) => item && item.source).filter(Boolean));",
|
|
|
|
|
" const codeAgentRuntime = overlay.codeAgentRuntime;",
|
|
|
|
|
" const cloudWebEnvEntries = publicExposureCloudWebEnvEntries();",
|
|
|
|
|
" function checkCloudWebRuntimeValue(item, file, container, envName, expected) {",
|
|
|
|
|
" const value = envValue(container, envName);",
|
|
|
|
|
" if (value !== expected) wrongCloudWebRuntimeEnvs.push({ ...workloadRef(item, file, container), envName, expected, value: value ?? null });",
|
|
|
|
|
" }",
|
|
|
|
|
" function checkCodeAgentRuntimeValue(item, file, container, envName, expected) {",
|
|
|
|
|
" const value = envValue(container, envName);",
|
|
|
|
|
" if (value !== expected) wrongCodeAgentRuntimeEnvs.push({ ...workloadRef(item, file, container), envName, kind: 'value', expected, value: value ?? null });",
|
|
|
|
@@ -1905,6 +1926,9 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" if (publicEndpoint !== undefined && publicEndpoint !== expectedPublicEndpoint(item)) wrongPublicEndpoints.push({ ...workloadRef(item, file, container), value: publicEndpoint, expected: expectedPublicEndpoint(item) });",
|
|
|
|
|
" const dbSslMode = envValue(container, 'HWLAB_CLOUD_DB_SSL_MODE');",
|
|
|
|
|
" if (overlay.externalPostgres && overlay.externalPostgres.sslmode && dbSslMode !== undefined && dbSslMode !== overlay.externalPostgres.sslmode) wrongDbSslModes.push({ ...workloadRef(item, file, container), value: dbSslMode, expected: overlay.externalPostgres.sslmode });",
|
|
|
|
|
" if (workloadName(item) === 'hwlab-cloud-web' && container.name === 'hwlab-cloud-web') {",
|
|
|
|
|
" for (const env of cloudWebEnvEntries) checkCloudWebRuntimeValue(item, file, container, env.name, env.value);",
|
|
|
|
|
" }",
|
|
|
|
|
" if (codeAgentRuntime && codeAgentRuntime.enabled && workloadName(item) === 'hwlab-cloud-api' && container.name === 'hwlab-cloud-api') {",
|
|
|
|
|
" checkCodeAgentRuntimeValue(item, file, container, 'HWLAB_CODE_AGENT_ADAPTER', String(codeAgentRuntime.adapter));",
|
|
|
|
|
" checkCodeAgentRuntimeValue(item, file, container, 'AGENTRUN_MGR_URL', String(codeAgentRuntime.managerUrl));",
|
|
|
|
@@ -1929,7 +1953,7 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" }",
|
|
|
|
|
" }",
|
|
|
|
|
" }",
|
|
|
|
|
" return { metricsRefs, missingStartupProbes, publicRuntimeImages, staleGitReadUrls, wrongPublicEndpoints, wrongDbSslModes, wrongCodeAgentRuntimeEnvs };",
|
|
|
|
|
" return { metricsRefs, missingStartupProbes, publicRuntimeImages, staleGitReadUrls, wrongPublicEndpoints, wrongDbSslModes, wrongCodeAgentRuntimeEnvs, wrongCloudWebRuntimeEnvs };",
|
|
|
|
|
"}",
|
|
|
|
|
"const checks = [];",
|
|
|
|
|
"const workloadCheck = workloadChecks();",
|
|
|
|
@@ -1953,6 +1977,8 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
"if (overlay.externalPostgres && overlay.externalPostgres.sslmode) checks.push('runtime-db-ssl-mode');",
|
|
|
|
|
"if (workloadCheck.wrongCodeAgentRuntimeEnvs.length > 0) fail('code-agent-runtime-env-mismatch', { refs: workloadCheck.wrongCodeAgentRuntimeEnvs.slice(0, 12), count: workloadCheck.wrongCodeAgentRuntimeEnvs.length });",
|
|
|
|
|
"if (overlay.codeAgentRuntime && overlay.codeAgentRuntime.enabled) checks.push('code-agent-runtime-env');",
|
|
|
|
|
"if (workloadCheck.wrongCloudWebRuntimeEnvs.length > 0) fail('cloud-web-runtime-env-mismatch', { refs: workloadCheck.wrongCloudWebRuntimeEnvs.slice(0, 12), count: workloadCheck.wrongCloudWebRuntimeEnvs.length });",
|
|
|
|
|
"if (publicExposureCloudWebEnvEntries().length > 0) checks.push('cloud-web-runtime-env');",
|
|
|
|
|
"const pg = overlay.externalPostgres;",
|
|
|
|
|
"if (pg && pg.serviceName) {",
|
|
|
|
|
" const access = pg.runtimeAccess || { endpointAddress: pg.endpointAddress, port: pg.port };",
|
|
|
|
@@ -1994,7 +2020,8 @@ export function nodeRuntimePipelinePostprocessScript(): string[] {
|
|
|
|
|
" if (!deployment) fail('public-exposure-frpc-deployment-missing', { expected: deploymentName });",
|
|
|
|
|
" const toml = configMap.data && configMap.data[configKey];",
|
|
|
|
|
" if (typeof toml !== 'string') fail('public-exposure-frpc-config-missing', { expectedKey: configKey });",
|
|
|
|
|
" for (const expected of [String(exposure.serverAddr), String(exposure.serverPort), String(exposure.webProxy.name), String(exposure.webProxy.remotePort), String(exposure.apiProxy.name), String(exposure.apiProxy.remotePort), 'HWLAB_FRP_TOKEN']) {",
|
|
|
|
|
" const expectedProxyTokens = [exposure.webProxy, exposure.apiProxy, ...(Array.isArray(exposure.extraProxies) ? exposure.extraProxies : [])].flatMap((proxy) => [String(proxy.name), String(proxy.remotePort)]);",
|
|
|
|
|
" for (const expected of [String(exposure.serverAddr), String(exposure.serverPort), ...expectedProxyTokens, 'HWLAB_FRP_TOKEN']) {",
|
|
|
|
|
" if (!toml.includes(expected)) fail('public-exposure-frpc-config-mismatch', { expected });",
|
|
|
|
|
" }",
|
|
|
|
|
" const podSpec = podSpecFor(deployment);",
|
|
|
|
|