fix: migrate HWLAB v03 development public entry
This commit is contained in:
@@ -82,6 +82,7 @@ lanes:
|
||||
deployment: hwlab-cloud-api
|
||||
targets:
|
||||
NC01:
|
||||
environment: development
|
||||
node: NC01
|
||||
workspace: /root/hwlab-v03
|
||||
sourceWorkspace:
|
||||
@@ -158,8 +159,8 @@ lanes:
|
||||
extends: templates.hwlabV03.webProbeWorkbench
|
||||
playwrightBrowsersPath: "0"
|
||||
public:
|
||||
webUrl: https://hwlab.pikapython.com
|
||||
apiUrl: https://hwlab.pikapython.com
|
||||
webUrl: http://152.53.229.148:32009
|
||||
apiUrl: http://152.53.229.148:32009
|
||||
observability:
|
||||
prometheusOperator: false
|
||||
webProbe:
|
||||
@@ -237,12 +238,16 @@ lanes:
|
||||
transactionalProjectorConsumerGroupId: hwlab-v03-agentrun-event-projector
|
||||
hwlabEventConsumerGroupId: hwlab-v03-workbench-live-sse
|
||||
publicExposure:
|
||||
extends: templates.hwlabV03.publicExposurePk01
|
||||
frpc:
|
||||
webProxy:
|
||||
remotePort: 22082
|
||||
apiProxy:
|
||||
remotePort: 22083
|
||||
mode: node-port-http
|
||||
publicBaseUrl: http://152.53.229.148:32009
|
||||
publicAddress: 152.53.229.148
|
||||
service:
|
||||
name: hwlab-cloud-web-public
|
||||
selector:
|
||||
app.kubernetes.io/name: hwlab-cloud-web
|
||||
port: 80
|
||||
targetPort: 8080
|
||||
nodePort: 32009
|
||||
externalPostgres:
|
||||
extends: templates.hwlabV03.externalPostgresConsumer
|
||||
provider: NC01
|
||||
|
||||
@@ -487,7 +487,7 @@ export interface HwlabRuntimePublicExposureFrpcExtraProxySpec extends HwlabRunti
|
||||
readonly cloudWebEnvName?: string;
|
||||
}
|
||||
|
||||
export interface HwlabRuntimePublicExposureSpec {
|
||||
export interface HwlabRuntimePk01PublicExposureSpec {
|
||||
readonly enabled: boolean;
|
||||
readonly mode: "pk01-caddy-frp";
|
||||
readonly publicBaseUrl: string;
|
||||
@@ -511,6 +511,20 @@ export interface HwlabRuntimePublicExposureSpec {
|
||||
readonly extraProxies: readonly HwlabRuntimePublicExposureFrpcExtraProxySpec[];
|
||||
}
|
||||
|
||||
export interface HwlabRuntimeNodePortPublicExposureSpec {
|
||||
readonly enabled: boolean;
|
||||
readonly mode: "node-port-http";
|
||||
readonly publicBaseUrl: string;
|
||||
readonly publicAddress: string;
|
||||
readonly serviceName: string;
|
||||
readonly selector: Record<string, string>;
|
||||
readonly port: number;
|
||||
readonly targetPort: number;
|
||||
readonly nodePort: number;
|
||||
}
|
||||
|
||||
export type HwlabRuntimePublicExposureSpec = HwlabRuntimePk01PublicExposureSpec | HwlabRuntimeNodePortPublicExposureSpec;
|
||||
|
||||
export interface HwlabRuntimeBootstrapAdminSpec {
|
||||
readonly username: string;
|
||||
readonly displayName: string;
|
||||
@@ -639,6 +653,7 @@ export interface HwlabRuntimePipelineProvenanceSpec {
|
||||
|
||||
export interface HwlabRuntimeLaneSpec {
|
||||
readonly lane: HwlabRuntimeLane;
|
||||
readonly environment?: "development" | "production";
|
||||
readonly nodeId: string;
|
||||
readonly nodeRoute: string;
|
||||
readonly nodeKubeRoute: string;
|
||||
@@ -711,6 +726,7 @@ export const HWLAB_NODE_LANE_CONFIG_PATH = "config/hwlab-node-lanes.yaml";
|
||||
|
||||
interface HwlabLaneConfig {
|
||||
readonly id: HwlabRuntimeLane;
|
||||
readonly environment?: "development" | "production";
|
||||
readonly node: string;
|
||||
readonly activeTarget?: string;
|
||||
readonly minor: number;
|
||||
@@ -956,6 +972,7 @@ function laneConfig(id: HwlabRuntimeLane, raw: Record<string, unknown>): HwlabLa
|
||||
if (minor >= 3 && raw.sourceSnapshot === undefined) throw new Error(`lanes.${id}.sourceSnapshot is required for HWLAB runtime v0.3+`);
|
||||
return {
|
||||
id,
|
||||
environment: raw.environment === undefined ? undefined : enumStringField(raw, "environment", `lanes.${id}`, ["development", "production"]),
|
||||
node: stringField(raw, "node", `lanes.${id}`),
|
||||
activeTarget: optionalStringField(raw, "activeTarget", `lanes.${id}`),
|
||||
minor,
|
||||
@@ -1952,7 +1969,27 @@ function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicE
|
||||
if (value === undefined) return null;
|
||||
const raw = asRecord(value, path);
|
||||
const mode = stringField(raw, "mode", path);
|
||||
if (mode !== "pk01-caddy-frp") throw new Error(`${path}.mode must be pk01-caddy-frp`);
|
||||
const publicBaseUrl = stringField(raw, "publicBaseUrl", path).replace(/\/+$/u, "");
|
||||
if (mode === "node-port-http") {
|
||||
const service = asRecord(raw.service, `${path}.service`);
|
||||
const selector = optionalStringRecord(service.selector, `${path}.service.selector`);
|
||||
if (Object.keys(selector).length === 0) throw new Error(`${path}.service.selector must declare at least one label`);
|
||||
const publicAddress = stringField(raw, "publicAddress", path);
|
||||
const nodePort = numberField(service, "nodePort", `${path}.service`);
|
||||
validateNodePortPublicExposureOrigin(publicBaseUrl, publicAddress, nodePort, `${path}.publicBaseUrl`);
|
||||
return {
|
||||
enabled: true,
|
||||
mode,
|
||||
publicBaseUrl,
|
||||
publicAddress,
|
||||
serviceName: stringField(service, "name", `${path}.service`),
|
||||
selector,
|
||||
port: numberField(service, "port", `${path}.service`),
|
||||
targetPort: numberField(service, "targetPort", `${path}.service`),
|
||||
nodePort,
|
||||
};
|
||||
}
|
||||
if (mode !== "pk01-caddy-frp") throw new Error(`${path}.mode must be pk01-caddy-frp or node-port-http`);
|
||||
const frpc = asRecord(raw.frpc, `${path}.frpc`);
|
||||
const caddy = asRecord(raw.caddy, `${path}.caddy`);
|
||||
const caddyTls = optionalStringField(caddy, "tls", `${path}.caddy`) ?? "auto";
|
||||
@@ -1960,7 +1997,7 @@ function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicE
|
||||
return {
|
||||
enabled: true,
|
||||
mode,
|
||||
publicBaseUrl: stringField(raw, "publicBaseUrl", path).replace(/\/+$/u, ""),
|
||||
publicBaseUrl,
|
||||
hostname: stringField(raw, "hostname", path),
|
||||
expectedA: stringField(raw, "expectedA", path),
|
||||
serverAddr: stringField(frpc, "serverAddr", `${path}.frpc`),
|
||||
@@ -1982,6 +2019,19 @@ function publicExposureConfig(value: unknown, path: string): HwlabRuntimePublicE
|
||||
};
|
||||
}
|
||||
|
||||
function validateNodePortPublicExposureOrigin(publicBaseUrl: string, publicAddress: string, nodePort: number, path: string): void {
|
||||
try {
|
||||
const parsed = new URL(publicBaseUrl);
|
||||
if (parsed.protocol !== "http:") throw new Error("must use http");
|
||||
if (parsed.hostname !== publicAddress) throw new Error(`hostname must match publicAddress ${publicAddress}`);
|
||||
if (Number(parsed.port) !== nodePort) throw new Error(`port must match service.nodePort ${nodePort}`);
|
||||
if (parsed.pathname !== "/" || parsed.search.length > 0 || parsed.hash.length > 0) throw new Error("must point to the public origin root");
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
throw new Error(`${path} must be an http NodePort origin root URL: ${message}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validatePublicExposureOrigin(publicBaseUrl: string, path: string): void {
|
||||
try {
|
||||
const parsed = new URL(publicBaseUrl);
|
||||
@@ -2297,8 +2347,15 @@ function buildRuntimeLaneSpec(config: HwlabLaneConfig): HwlabRuntimeLaneSpec {
|
||||
const node = HWLAB_NODE_LANE_CONFIG.nodes[config.node];
|
||||
const networkProfile = HWLAB_NODE_LANE_CONFIG.networkProfiles[node.networkProfileId];
|
||||
const downloadProfile = HWLAB_NODE_LANE_CONFIG.downloadProfiles[node.downloadProfileId];
|
||||
if (config.publicExposure?.mode === "node-port-http") {
|
||||
if (config.environment === undefined) throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.lanes.${config.id}.environment is required for node-port-http public exposure`);
|
||||
if (config.public.webUrl !== config.publicExposure.publicBaseUrl || config.public.apiUrl !== config.publicExposure.publicBaseUrl) {
|
||||
throw new Error(`${HWLAB_NODE_LANE_CONFIG_PATH}.lanes.${config.id}.public webUrl/apiUrl must match publicExposure.publicBaseUrl for node-port-http`);
|
||||
}
|
||||
}
|
||||
return {
|
||||
lane: config.id,
|
||||
...(config.environment === undefined ? {} : { environment: config.environment }),
|
||||
nodeId: node.id,
|
||||
nodeRoute: node.route,
|
||||
nodeKubeRoute: node.kubeRoute,
|
||||
|
||||
@@ -13,7 +13,7 @@ import { runCommand, type CommandResult } from "../command";
|
||||
import { startJob } from "../jobs";
|
||||
import { classifySshTcpPoolFailure } from "../ssh";
|
||||
import { HWLAB_NODE_CONTROL_PLANE_CONFIG_PATH, hwlabNodeControlPlaneInfraHelp, runHwlabNodeControlPlaneInfra } from "../hwlab-node-control-plane";
|
||||
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
|
||||
import { hwlabRuntimeLaneConfigPath, hwlabRuntimeLaneIds, hwlabRuntimeLaneSpec, hwlabRuntimeLaneSpecForNode, hwlabRuntimeNodeIds, isHwlabRuntimeLane, type HwlabRuntimeLane, type HwlabRuntimeLaneSpec, type HwlabRuntimeObservabilityRecordingRuleSpec, type HwlabRuntimeObservabilitySpec, type HwlabRuntimeObservabilityWarningAlertSpec, type HwlabRuntimePk01PublicExposureSpec, type HwlabRuntimePublicExposureSpec, type HwlabRuntimeWebProbeAlertThresholdsSpec, type HwlabRuntimeWebProbeProjectManagementSpec } from "../hwlab-node-lanes";
|
||||
import { nodeWebProbeScriptRunnerSource } from "../hwlab-node-web-probe-runner-source";
|
||||
import { nodeWebObserveAnalyzerSource } from "../hwlab-node-web-observe-analyzer-source";
|
||||
import { nodeWebObserveRunnerSource } from "../hwlab-node-web-observe-runner-source";
|
||||
@@ -440,6 +440,20 @@ export function runExternalPostgresSecretEnsure(options: NodeSecretOptions, spec
|
||||
}
|
||||
|
||||
export function publicExposureSummary(exposure: HwlabRuntimePublicExposureSpec): Record<string, unknown> {
|
||||
if (exposure.mode === "node-port-http") {
|
||||
return {
|
||||
mode: exposure.mode,
|
||||
publicBaseUrl: exposure.publicBaseUrl,
|
||||
publicAddress: exposure.publicAddress,
|
||||
service: {
|
||||
name: exposure.serviceName,
|
||||
selector: exposure.selector,
|
||||
port: exposure.port,
|
||||
targetPort: exposure.targetPort,
|
||||
nodePort: exposure.nodePort,
|
||||
},
|
||||
};
|
||||
}
|
||||
return {
|
||||
mode: exposure.mode,
|
||||
publicBaseUrl: exposure.publicBaseUrl,
|
||||
@@ -480,6 +494,23 @@ export function runNodePublicExposure(options: NodePublicExposureOptions): Recor
|
||||
mutation: false,
|
||||
};
|
||||
}
|
||||
if (exposure.mode === "node-port-http") {
|
||||
return {
|
||||
ok: true,
|
||||
command: "hwlab nodes control-plane public-exposure",
|
||||
node: options.node,
|
||||
lane: options.lane,
|
||||
mode: "gitops-managed-node-port",
|
||||
mutation: false,
|
||||
configPath: hwlabRuntimeLaneConfigPath(),
|
||||
publicExposure: publicExposureSummary(exposure),
|
||||
valuesRedacted: true,
|
||||
next: {
|
||||
delivery: "merge the source PR and observe the automatic PaC/GitOps/Argo chain",
|
||||
status: `bun scripts/cli.ts hwlab nodes control-plane status --node ${options.node} --lane ${options.lane}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
const source = readPublicExposureTokenSource(exposure);
|
||||
if (!source.ok) {
|
||||
return {
|
||||
@@ -531,7 +562,7 @@ export function runNodePublicExposure(options: NodePublicExposureOptions): Recor
|
||||
};
|
||||
}
|
||||
|
||||
function publicExposureSecretDryRunStatus(options: NodePublicExposureOptions, exposure: HwlabRuntimePublicExposureSpec, tokenBytes: number): Record<string, unknown> {
|
||||
function publicExposureSecretDryRunStatus(options: NodePublicExposureOptions, exposure: HwlabRuntimePk01PublicExposureSpec, tokenBytes: number): Record<string, unknown> {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
@@ -566,7 +597,7 @@ function publicExposureSecretDryRunStatus(options: NodePublicExposureOptions, ex
|
||||
};
|
||||
}
|
||||
|
||||
function publicExposureSecretApplyStatus(options: NodePublicExposureOptions, exposure: HwlabRuntimePublicExposureSpec, token: string): Record<string, unknown> {
|
||||
function publicExposureSecretApplyStatus(options: NodePublicExposureOptions, exposure: HwlabRuntimePk01PublicExposureSpec, token: string): Record<string, unknown> {
|
||||
const secretApplyResult = runTransScript(options.node, publicExposureSecretScript(options, exposure), token, options.timeoutSeconds);
|
||||
let secretFields = keyValueLinesFromText(statusText(secretApplyResult));
|
||||
let secretResult = secretApplyResult;
|
||||
@@ -578,7 +609,7 @@ function publicExposureSecretApplyStatus(options: NodePublicExposureOptions, exp
|
||||
return publicExposureSecretStatus(secretFields, secretResult);
|
||||
}
|
||||
|
||||
export function readPublicExposureTokenSource(exposure: HwlabRuntimePublicExposureSpec): { ok: boolean; path: string; checkedPaths: string[]; key: string; value: string | null; fingerprint: string | null; error?: string } {
|
||||
export function readPublicExposureTokenSource(exposure: HwlabRuntimePk01PublicExposureSpec): { ok: boolean; path: string; checkedPaths: string[]; key: string; value: string | null; fingerprint: string | null; error?: string } {
|
||||
const checkedPaths = publicExposureTokenSourcePaths(exposure);
|
||||
const path = checkedPaths.find((candidate) => existsSync(candidate)) ?? checkedPaths[0] ?? rootPath(".state", "secrets", exposure.tokenSourceRef);
|
||||
if (!existsSync(path)) return { ok: false, path, checkedPaths, key: exposure.tokenSourceKey, value: null, fingerprint: null, error: "secret-source-missing" };
|
||||
@@ -595,7 +626,7 @@ export function readPublicExposureTokenSource(exposure: HwlabRuntimePublicExposu
|
||||
};
|
||||
}
|
||||
|
||||
export function publicExposureTokenSourcePaths(exposure: HwlabRuntimePublicExposureSpec): string[] {
|
||||
export function publicExposureTokenSourcePaths(exposure: HwlabRuntimePk01PublicExposureSpec): string[] {
|
||||
return [rootPath(".state", "secrets", exposure.tokenSourceRef)];
|
||||
}
|
||||
|
||||
@@ -671,7 +702,7 @@ export function publicExposureCaddyStatus(fields: Record<string, string>, result
|
||||
};
|
||||
}
|
||||
|
||||
export function publicExposureSecretScript(options: NodePublicExposureOptions, exposure: HwlabRuntimePublicExposureSpec): string {
|
||||
export function publicExposureSecretScript(options: NodePublicExposureOptions, exposure: HwlabRuntimePk01PublicExposureSpec): string {
|
||||
const deployment = `${options.spec.runtimeNamespace}-frpc`;
|
||||
return [
|
||||
"set +e",
|
||||
@@ -859,7 +890,7 @@ export function combinePublicExposureCommandResults(first: CommandResult, second
|
||||
};
|
||||
}
|
||||
|
||||
export function publicExposureCaddyScript(options: NodePublicExposureOptions, exposure: HwlabRuntimePublicExposureSpec): string {
|
||||
export function publicExposureCaddyScript(options: NodePublicExposureOptions, exposure: HwlabRuntimePk01PublicExposureSpec): string {
|
||||
const blockB64 = Buffer.from(publicExposureCaddyBlock(exposure), "utf8").toString("base64");
|
||||
const marker = `unidesk managed ${exposure.hostname}`;
|
||||
return [
|
||||
@@ -976,7 +1007,7 @@ export function publicExposureCaddyScript(options: NodePublicExposureOptions, ex
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function publicExposureCaddyBlock(exposure: HwlabRuntimePublicExposureSpec): string {
|
||||
export function publicExposureCaddyBlock(exposure: HwlabRuntimePk01PublicExposureSpec): string {
|
||||
const tlsLines = exposure.caddyTls === "internal" ? " tls internal\n" : "";
|
||||
const mainBlock = `${exposure.hostname} {
|
||||
${tlsLines} @api path /health* /v1* /json-rpc* /openapi* /docs* /swagger*
|
||||
|
||||
@@ -1626,6 +1626,7 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
" runtimePath: overlay.runtimePath,",
|
||||
" runtimeRenderDir: overlay.runtimeRenderDir,",
|
||||
" runtimeNamespace: overlay.runtimeNamespace,",
|
||||
" environment: overlay.environment,",
|
||||
" catalogPath: overlay.catalogPath,",
|
||||
" gitUrl: overlay.gitUrl,",
|
||||
" publicWebUrl: overlay.publicWebUrl,",
|
||||
@@ -1723,6 +1724,7 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
" runtimePath: overlay.runtimePath,",
|
||||
" runtimeRenderDir: overlay.runtimeRenderDir,",
|
||||
" runtimeNamespace: overlay.runtimeNamespace,",
|
||||
" environment: overlay.environment,",
|
||||
" externalPostgres: overlay.externalPostgres,",
|
||||
" codeAgentRuntime: overlay.codeAgentRuntime,",
|
||||
" publicExposure: overlay.publicExposure,",
|
||||
@@ -2049,15 +2051,17 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
" if (!fs.existsSync(file)) return false;",
|
||||
" const doc = readYaml(file) || {};",
|
||||
" const resources = Array.isArray(doc.resources) ? doc.resources : [];",
|
||||
" const publicExposureEnabled = overlay.publicExposure && overlay.publicExposure.enabled;",
|
||||
" const publicExposureMode = overlay.publicExposure && overlay.publicExposure.enabled ? overlay.publicExposure.mode : null;",
|
||||
" const next = resources.filter((item) => {",
|
||||
" const resource = String(item);",
|
||||
" if (!publicExposureEnabled && resource === 'node-frpc.yaml') return false;",
|
||||
" if (publicExposureMode !== 'pk01-caddy-frp' && resource === 'node-frpc.yaml') return false;",
|
||||
" if (publicExposureMode !== 'node-port-http' && resource === 'node-public-service.yaml') return false;",
|
||||
" if (overlay.observability && overlay.observability.prometheusOperator === false && resource === 'observability.yaml') return false;",
|
||||
" return !(/\\.ya?ml$/u.test(resource) && !fs.existsSync(path.join(runtimePath, resource)));",
|
||||
" });",
|
||||
" let changed = false;",
|
||||
" if (next.length !== resources.length) { doc.resources = next; writeYaml(file, doc); changed = true; }",
|
||||
" if (publicExposureMode === 'node-port-http' && !next.includes('node-public-service.yaml')) next.push('node-public-service.yaml');",
|
||||
" if (JSON.stringify(next) !== JSON.stringify(resources)) { doc.resources = next; writeYaml(file, doc); changed = true; }",
|
||||
" const observabilityFile = path.join(runtimePath, 'observability.yaml');",
|
||||
" if (overlay.observability && overlay.observability.prometheusOperator === false && fs.existsSync(observabilityFile)) { fs.rmSync(observabilityFile, { force: true }); changed = true; }",
|
||||
" return changed;",
|
||||
@@ -2171,12 +2175,31 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
"}",
|
||||
"function patchPublicExposure() {",
|
||||
" const exposure = overlay.publicExposure;",
|
||||
" const file = path.join(runtimePath, 'node-frpc.yaml');",
|
||||
" const frpcFile = path.join(runtimePath, 'node-frpc.yaml');",
|
||||
" const nodePortFile = path.join(runtimePath, 'node-public-service.yaml');",
|
||||
" if (!exposure || !exposure.enabled) {",
|
||||
" const changed = fs.existsSync(file);",
|
||||
" if (changed) fs.rmSync(file, { force: true });",
|
||||
" const changed = fs.existsSync(frpcFile) || fs.existsSync(nodePortFile);",
|
||||
" fs.rmSync(frpcFile, { force: true });",
|
||||
" fs.rmSync(nodePortFile, { force: true });",
|
||||
" return { configured: false, changed };",
|
||||
" }",
|
||||
" if (exposure.mode === 'node-port-http') {",
|
||||
" if (typeof overlay.environment !== 'string' || overlay.environment.length === 0) throw new Error('node-port-http public exposure requires owning YAML environment');",
|
||||
" const service = {",
|
||||
" apiVersion: 'v1',",
|
||||
" kind: 'Service',",
|
||||
" metadata: { name: String(exposure.serviceName), namespace: String(overlay.runtimeNamespace), labels: { 'app.kubernetes.io/name': String(exposure.serviceName), 'app.kubernetes.io/part-of': 'hwlab', 'hwlab.pikastech.local/environment': String(overlay.environment) } },",
|
||||
" spec: { type: 'NodePort', selector: exposure.selector, ports: [{ name: 'http', protocol: 'TCP', port: Number(exposure.port), targetPort: Number(exposure.targetPort), nodePort: Number(exposure.nodePort) }] },",
|
||||
" };",
|
||||
" const rendered = YAML.stringify(service).trimEnd() + '\\n';",
|
||||
" const before = fs.existsSync(nodePortFile) ? fs.readFileSync(nodePortFile, 'utf8') : '';",
|
||||
" if (before !== rendered) fs.writeFileSync(nodePortFile, rendered);",
|
||||
" const frpcRemoved = fs.existsSync(frpcFile);",
|
||||
" fs.rmSync(frpcFile, { force: true });",
|
||||
" return { configured: true, mode: exposure.mode, changed: before !== rendered || frpcRemoved, filePath: nodePortFile, serviceName: exposure.serviceName, nodePort: exposure.nodePort };",
|
||||
" }",
|
||||
" const file = frpcFile;",
|
||||
" fs.rmSync(nodePortFile, { force: true });",
|
||||
" if (!fs.existsSync(file)) {",
|
||||
" console.error(JSON.stringify({ event: 'unidesk-public-exposure-postprocess', ok: false, reason: 'node-frpc-yaml-missing', filePath: file, hostname: exposure.hostname }));",
|
||||
" process.exit(49);",
|
||||
@@ -2238,6 +2261,7 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
" const runtimeOverlay = JSON.stringify({",
|
||||
" runtimePath: overlay.runtimePath,",
|
||||
" runtimeNamespace: overlay.runtimeNamespace,",
|
||||
" environment: overlay.environment,",
|
||||
" externalPostgres: overlay.externalPostgres,",
|
||||
" codeAgentRuntime: overlay.codeAgentRuntime,",
|
||||
" publicExposure: overlay.publicExposure,",
|
||||
@@ -2450,7 +2474,18 @@ export function nodeRuntimePipelinePostprocessScript(featureConfigSchemaStage: s
|
||||
" }",
|
||||
"}",
|
||||
"const exposure = overlay.publicExposure;",
|
||||
"if (exposure && exposure.enabled) {",
|
||||
"if (exposure && exposure.enabled && exposure.mode === 'node-port-http') {",
|
||||
" const file = path.join(runtimePath, 'node-public-service.yaml');",
|
||||
" if (!fs.existsSync(file)) fail('public-exposure-node-port-service-missing');",
|
||||
" const service = allItemsFromFile(file).find((item) => item && item.kind === 'Service' && item.metadata && item.metadata.name === exposure.serviceName);",
|
||||
" if (!service) fail('public-exposure-node-port-service-object-missing', { expected: exposure.serviceName });",
|
||||
" const port = service.spec && Array.isArray(service.spec.ports) ? service.spec.ports.find((item) => item && Number(item.nodePort) === Number(exposure.nodePort)) : null;",
|
||||
" if (!service.spec || service.spec.type !== 'NodePort') fail('public-exposure-node-port-service-type-mismatch', { expected: 'NodePort', actual: service.spec && service.spec.type });",
|
||||
" if (!port || Number(port.port) !== Number(exposure.port) || Number(port.targetPort) !== Number(exposure.targetPort)) fail('public-exposure-node-port-service-port-mismatch', { expectedPort: exposure.port, expectedTargetPort: exposure.targetPort, expectedNodePort: exposure.nodePort });",
|
||||
" if (JSON.stringify(service.spec.selector || {}) !== JSON.stringify(exposure.selector || {})) fail('public-exposure-node-port-service-selector-mismatch', { expected: exposure.selector, actual: service.spec.selector || {} });",
|
||||
" checks.push('public-exposure-node-port');",
|
||||
"}",
|
||||
"if (exposure && exposure.enabled && exposure.mode === 'pk01-caddy-frp') {",
|
||||
" const file = path.join(runtimePath, 'node-frpc.yaml');",
|
||||
" if (!fs.existsSync(file)) fail('public-exposure-frpc-missing');",
|
||||
" const items = allItemsFromFile(file);",
|
||||
|
||||
@@ -78,6 +78,7 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<str
|
||||
pipelineProvenanceAnnotations: hwlabRuntimePipelineProvenanceAnnotations(spec),
|
||||
nodeId: spec.nodeId,
|
||||
lane: spec.lane,
|
||||
environment: spec.environment,
|
||||
sourceBranch: spec.sourceBranch,
|
||||
gitopsBranch: spec.gitopsBranch,
|
||||
gitopsRoot: nodeRuntimeGitopsRoot(spec),
|
||||
@@ -139,7 +140,17 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<str
|
||||
buildkitSidecarImage: spec.buildkit?.sidecarImage,
|
||||
publicWebUrl: spec.publicWebUrl,
|
||||
publicApiUrl: spec.publicApiUrl,
|
||||
publicExposure: spec.publicExposure === null ? undefined : {
|
||||
publicExposure: spec.publicExposure === null ? undefined : spec.publicExposure.mode === "node-port-http" ? {
|
||||
enabled: spec.publicExposure.enabled,
|
||||
mode: spec.publicExposure.mode,
|
||||
publicBaseUrl: spec.publicExposure.publicBaseUrl,
|
||||
publicAddress: spec.publicExposure.publicAddress,
|
||||
serviceName: spec.publicExposure.serviceName,
|
||||
selector: spec.publicExposure.selector,
|
||||
port: spec.publicExposure.port,
|
||||
targetPort: spec.publicExposure.targetPort,
|
||||
nodePort: spec.publicExposure.nodePort,
|
||||
} : {
|
||||
enabled: spec.publicExposure.enabled,
|
||||
mode: spec.publicExposure.mode,
|
||||
publicBaseUrl: spec.publicExposure.publicBaseUrl,
|
||||
|
||||
Reference in New Issue
Block a user