feat: add JD01 YAML-first deployment support
This commit is contained in:
@@ -49,12 +49,28 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<str
|
||||
egressProxy: gitMirror.egressProxy.mode === "direct" ? {
|
||||
mode: "direct",
|
||||
required: false,
|
||||
} : gitMirror.egressProxy.mode === "host-route" ? {
|
||||
...gitMirror.egressProxy,
|
||||
mode: "host-route",
|
||||
required: true,
|
||||
} : {
|
||||
...gitMirror.egressProxy,
|
||||
mode: "node-global",
|
||||
required: true,
|
||||
},
|
||||
};
|
||||
const deployYamlGitMirror = {
|
||||
...renderGitMirror,
|
||||
egressProxy: renderGitMirror.egressProxy.mode !== "host-route" ? renderGitMirror.egressProxy : {
|
||||
mode: "node-global",
|
||||
required: true,
|
||||
clientName: renderGitMirror.egressProxy.clientName,
|
||||
namespace: "platform-infra",
|
||||
serviceName: renderGitMirror.egressProxy.clientName,
|
||||
port: httpProxyEndpoint(renderGitMirror.egressProxy.proxyUrl)?.port ?? 10808,
|
||||
noProxy: renderGitMirror.egressProxy.noProxy,
|
||||
},
|
||||
};
|
||||
return {
|
||||
nodeId: spec.nodeId,
|
||||
lane: spec.lane,
|
||||
@@ -75,6 +91,7 @@ export function nodeRuntimeRenderOverlay(spec: HwlabRuntimeLaneSpec): Record<str
|
||||
toolsImage: gitMirror.toolsImage,
|
||||
toolsImagePullPolicy: gitMirror.toolsImagePullPolicy,
|
||||
gitMirror: renderGitMirror,
|
||||
deployYamlGitMirror,
|
||||
networkProfileId: spec.networkProfileId,
|
||||
downloadProfileId: spec.downloadProfileId,
|
||||
gitSshProxyHost: gitSshProxy?.host,
|
||||
@@ -704,6 +721,7 @@ export function readLocalPostgresPasswordMaterial(input: { sourceRef: string; so
|
||||
}
|
||||
|
||||
export function localSecretSourcePaths(sourceRef: string): string[] {
|
||||
if (sourceRef.startsWith(".env/")) return ownerFileSourcePaths(sourceRef);
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
const paths = index >= 0
|
||||
@@ -1034,6 +1052,16 @@ export function ensureNodeBaseImage(spec: HwlabRuntimeLaneSpec, dryRun: boolean,
|
||||
" if [ \"$mode\" = build-moonbridge ]; then",
|
||||
" action=build",
|
||||
" source_present_before=build-source",
|
||||
" effective_build_container_http_proxy=\"$build_container_http_proxy\"",
|
||||
" effective_build_container_https_proxy=\"$build_container_https_proxy\"",
|
||||
" effective_build_container_all_proxy=\"$build_container_all_proxy\"",
|
||||
" effective_docker_build_add_host_args=\"$docker_build_add_host_args\"",
|
||||
" if [ \"$docker_network_mode\" = host ]; then",
|
||||
" effective_build_container_http_proxy=\"$build_http_proxy\"",
|
||||
" effective_build_container_https_proxy=\"$build_https_proxy\"",
|
||||
" effective_build_container_all_proxy=\"$build_all_proxy\"",
|
||||
" effective_docker_build_add_host_args=\"\"",
|
||||
" fi",
|
||||
" tmpdir=$(mktemp -d /tmp/hwlab-node-runtime-image-$id.XXXXXX)",
|
||||
" dockerfile=\"$tmpdir/Dockerfile\"",
|
||||
" cat > \"$dockerfile\" <<'DOCKERFILE'",
|
||||
@@ -1069,7 +1097,7 @@ export function ensureNodeBaseImage(spec: HwlabRuntimeLaneSpec, dryRun: boolean,
|
||||
"USER 65532:65532",
|
||||
"ENTRYPOINT [\"/app/moonbridge\"]",
|
||||
"DOCKERFILE",
|
||||
" if env HTTP_PROXY=\"$build_http_proxy\" HTTPS_PROXY=\"$build_https_proxy\" ALL_PROXY=\"$build_all_proxy\" NO_PROXY=\"$build_no_proxy\" http_proxy=\"$build_http_proxy\" https_proxy=\"$build_https_proxy\" all_proxy=\"$build_all_proxy\" no_proxy=\"$build_no_proxy\" docker build $docker_build_add_host_args --network \"$docker_network_mode\" --build-arg BUILDER_IMAGE=\"$builder_image\" --build-arg MOONBRIDGE_REPO=\"$source_repo\" --build-arg MOONBRIDGE_REF=\"$source_ref\" --build-arg GOPROXY_VALUE=\"$go_proxy\" --build-arg HTTP_PROXY=\"$build_container_http_proxy\" --build-arg HTTPS_PROXY=\"$build_container_https_proxy\" --build-arg ALL_PROXY=\"$build_container_all_proxy\" --build-arg NO_PROXY=\"$build_no_proxy\" --build-arg http_proxy=\"$build_container_http_proxy\" --build-arg https_proxy=\"$build_container_https_proxy\" --build-arg all_proxy=\"$build_container_all_proxy\" --build-arg no_proxy=\"$build_no_proxy\" -t \"$target\" -f \"$dockerfile\" \"$tmpdir\" >/tmp/hwlab-node-runtime-image-$id-build.out 2>&1; then",
|
||||
" if env HTTP_PROXY=\"$build_http_proxy\" HTTPS_PROXY=\"$build_https_proxy\" ALL_PROXY=\"$build_all_proxy\" NO_PROXY=\"$build_no_proxy\" http_proxy=\"$build_http_proxy\" https_proxy=\"$build_https_proxy\" all_proxy=\"$build_all_proxy\" no_proxy=\"$build_no_proxy\" docker build $effective_docker_build_add_host_args --network \"$docker_network_mode\" --build-arg BUILDER_IMAGE=\"$builder_image\" --build-arg MOONBRIDGE_REPO=\"$source_repo\" --build-arg MOONBRIDGE_REF=\"$source_ref\" --build-arg GOPROXY_VALUE=\"$go_proxy\" --build-arg HTTP_PROXY=\"$effective_build_container_http_proxy\" --build-arg HTTPS_PROXY=\"$effective_build_container_https_proxy\" --build-arg ALL_PROXY=\"$effective_build_container_all_proxy\" --build-arg NO_PROXY=\"$build_no_proxy\" --build-arg http_proxy=\"$effective_build_container_http_proxy\" --build-arg https_proxy=\"$effective_build_container_https_proxy\" --build-arg all_proxy=\"$effective_build_container_all_proxy\" --build-arg no_proxy=\"$build_no_proxy\" -t \"$target\" -f \"$dockerfile\" \"$tmpdir\" >/tmp/hwlab-node-runtime-image-$id-build.out 2>&1; then",
|
||||
" docker push \"$target\" >/tmp/hwlab-node-runtime-image-$id-push.out 2>&1 || { cat /tmp/hwlab-node-runtime-image-$id-push.out >&2 2>/dev/null || true; failed=true; }",
|
||||
" else",
|
||||
" cat /tmp/hwlab-node-runtime-image-$id-build.out >&2 2>/dev/null || true",
|
||||
@@ -1122,9 +1150,12 @@ export function ensureNodeBaseImage(spec: HwlabRuntimeLaneSpec, dryRun: boolean,
|
||||
? runNodeHostScriptAsync(spec, script, Math.min(timeoutSeconds, 600), "runtime-image-build")
|
||||
: runNodeHostScript(spec, script, Math.min(timeoutSeconds, 300));
|
||||
const imageRows = parseNodeRuntimeImageRows(statusText(result));
|
||||
const dependencyCount = nodeRuntimeImageDependencies(spec).length;
|
||||
const imageRowsComplete = imageRows.length === dependencyCount
|
||||
&& imageRows.every((image) => image.presentAfter === true || image.registryTagPresent === true);
|
||||
const base = imageRows.find((image) => image.id === "base") ?? {};
|
||||
return {
|
||||
ok: isCommandSuccess(result),
|
||||
ok: isCommandSuccess(result) || imageRowsComplete,
|
||||
dryRun,
|
||||
target: base.target ?? spec.baseImage,
|
||||
source: base.source ?? spec.baseImageSource,
|
||||
@@ -1196,6 +1227,7 @@ export function readSecretSourceValue(secretRoot: string, sourceRef: string, key
|
||||
}
|
||||
|
||||
export function secretSourcePaths(sourceRef: string): string[] {
|
||||
if (sourceRef.startsWith(".env/")) return ownerFileSourcePaths(sourceRef);
|
||||
const paths = [join(repoRoot, ".state", "secrets", sourceRef)];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
@@ -1203,6 +1235,14 @@ export function secretSourcePaths(sourceRef: string): string[] {
|
||||
return [...new Set(paths)];
|
||||
}
|
||||
|
||||
function ownerFileSourcePaths(sourceRef: string): string[] {
|
||||
if (sourceRef.includes("..") || sourceRef.includes("\0")) return [];
|
||||
const marker = "/.worktree/";
|
||||
const index = repoRoot.indexOf(marker);
|
||||
const roots = index >= 0 ? [repoRoot.slice(0, index), repoRoot] : [repoRoot];
|
||||
return [...new Set(roots.map((root) => join(root, sourceRef)))];
|
||||
}
|
||||
|
||||
export function displayRepoPath(path: string): string {
|
||||
const normalizedRoot = repoRoot.replace(/\/+$/u, "");
|
||||
if (path === normalizedRoot) return ".";
|
||||
@@ -1225,27 +1265,28 @@ export function hwlabPasswordHash(password: string): string {
|
||||
export function readBootstrapAdminSecretMaterial(spec: RuntimeSecretSpec): BootstrapAdminSecretMaterial {
|
||||
const sourceRef = spec.bootstrapAdminPasswordSourceRef;
|
||||
const sourceKey = spec.bootstrapAdminPasswordSourceKey;
|
||||
const sourceLine = spec.bootstrapAdminPasswordSourceLine ?? null;
|
||||
if (sourceRef === undefined || sourceKey === undefined || spec.bootstrapAdminPasswordHashTransform === undefined) {
|
||||
return { ok: false, sourceRef: sourceRef ?? null, sourceKey: sourceKey ?? null, sourcePath: null, sourcePresent: false, sourceFingerprint: null, passwordHash: null, error: "bootstrap-admin-yaml-source-missing" };
|
||||
}
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((candidate) => existsSync(candidate)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
if (!existsSync(sourcePath)) {
|
||||
return { ok: false, sourceRef, sourceKey, sourcePath, sourcePresent: false, sourceFingerprint: null, passwordHash: null, error: "secret-source-missing" };
|
||||
}
|
||||
const values = parseEnvFile(readFileSync(sourcePath, "utf8"));
|
||||
const password = values[sourceKey];
|
||||
if (password === undefined || password.length === 0) {
|
||||
return { ok: false, sourceRef, sourceKey, sourcePath, sourcePresent: true, sourceFingerprint: null, passwordHash: null, error: "secret-key-missing" };
|
||||
return bootstrapAdminSecretMaterialError(spec, sourceRef ?? null, sourceKey ?? null, sourceLine, null, null, "bootstrap-admin-yaml-source-missing");
|
||||
}
|
||||
const password = readSecretSourceScalar(sourceRef, sourceKey, sourceLine);
|
||||
if (!password.ok) return bootstrapAdminSecretMaterialError(spec, sourceRef, sourceKey, sourceLine, password.sourcePath, password.sourcePresent, password.error);
|
||||
const username = readBootstrapAdminUsername(spec);
|
||||
if (!username.ok) return bootstrapAdminSecretMaterialError(spec, sourceRef, sourceKey, sourceLine, password.sourcePath, password.sourcePresent, username.error);
|
||||
return {
|
||||
ok: true,
|
||||
username: username.value,
|
||||
usernameSourceRef: spec.bootstrapAdminUsernameSourceRef ?? null,
|
||||
usernameSourceKey: spec.bootstrapAdminUsernameSourceKey ?? null,
|
||||
usernameSourceLine: spec.bootstrapAdminUsernameSourceLine ?? null,
|
||||
usernameFingerprint: shortSecretFingerprint(username.value),
|
||||
sourceRef,
|
||||
sourceKey,
|
||||
sourcePath,
|
||||
sourceLine,
|
||||
sourcePath: password.sourcePath,
|
||||
sourcePresent: true,
|
||||
sourceFingerprint: `sha256:${createHash("sha256").update(password).digest("hex").slice(0, 16)}`,
|
||||
passwordHash: hwlabPasswordHash(password),
|
||||
sourceFingerprint: shortSecretFingerprint(password.value),
|
||||
passwordHash: hwlabPasswordHash(password.value),
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
@@ -1253,32 +1294,96 @@ export function readBootstrapAdminSecretMaterial(spec: RuntimeSecretSpec): Boots
|
||||
export function readBootstrapAdminPasswordMaterial(spec: RuntimeSecretSpec): BootstrapAdminPasswordMaterial {
|
||||
const sourceRef = spec.bootstrapAdminPasswordSourceRef;
|
||||
const sourceKey = spec.bootstrapAdminPasswordSourceKey;
|
||||
const sourceLine = spec.bootstrapAdminPasswordSourceLine ?? null;
|
||||
if (sourceRef === undefined || sourceKey === undefined || spec.bootstrapAdminPasswordHashTransform === undefined) {
|
||||
return { ok: false, sourceRef: sourceRef ?? null, sourceKey: sourceKey ?? null, sourcePath: null, sourcePresent: false, sourceFingerprint: null, password: null, error: "bootstrap-admin-yaml-source-missing" };
|
||||
}
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((candidate) => existsSync(candidate)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
const runtimePassword = process.env[sourceKey];
|
||||
if (!existsSync(sourcePath) && (runtimePassword === undefined || runtimePassword.length === 0)) {
|
||||
return { ok: false, sourceRef, sourceKey, sourcePath, sourcePresent: false, sourceFingerprint: null, password: null, error: "secret-source-missing" };
|
||||
}
|
||||
const values = existsSync(sourcePath) ? parseEnvFile(readFileSync(sourcePath, "utf8")) : {};
|
||||
const password = values[sourceKey] ?? runtimePassword;
|
||||
if (password === undefined || password.length === 0) {
|
||||
return { ok: false, sourceRef, sourceKey, sourcePath, sourcePresent: true, sourceFingerprint: null, password: null, error: "secret-key-missing" };
|
||||
return bootstrapAdminPasswordMaterialError(spec, sourceRef ?? null, sourceKey ?? null, sourceLine, null, null, "bootstrap-admin-yaml-source-missing");
|
||||
}
|
||||
const password = readSecretSourceScalar(sourceRef, sourceKey, sourceLine);
|
||||
if (!password.ok) return bootstrapAdminPasswordMaterialError(spec, sourceRef, sourceKey, sourceLine, password.sourcePath, password.sourcePresent, password.error);
|
||||
const username = readBootstrapAdminUsername(spec);
|
||||
if (!username.ok) return bootstrapAdminPasswordMaterialError(spec, sourceRef, sourceKey, sourceLine, password.sourcePath, password.sourcePresent, username.error);
|
||||
return {
|
||||
ok: true,
|
||||
username: username.value,
|
||||
usernameSourceRef: spec.bootstrapAdminUsernameSourceRef ?? null,
|
||||
usernameSourceKey: spec.bootstrapAdminUsernameSourceKey ?? null,
|
||||
usernameSourceLine: spec.bootstrapAdminUsernameSourceLine ?? null,
|
||||
usernameFingerprint: shortSecretFingerprint(username.value),
|
||||
sourceRef,
|
||||
sourceKey,
|
||||
sourcePath,
|
||||
sourceLine,
|
||||
sourcePath: password.sourcePath,
|
||||
sourcePresent: true,
|
||||
sourceFingerprint: `sha256:${createHash("sha256").update(password).digest("hex").slice(0, 16)}`,
|
||||
password,
|
||||
sourceFingerprint: shortSecretFingerprint(password.value),
|
||||
password: password.value,
|
||||
error: null,
|
||||
};
|
||||
}
|
||||
|
||||
function readBootstrapAdminUsername(spec: RuntimeSecretSpec): { ok: true; value: string } | { ok: false; error: string } {
|
||||
const ref = spec.bootstrapAdminUsernameSourceRef;
|
||||
if (ref === undefined) return { ok: true, value: spec.bootstrapAdminUsername };
|
||||
const key = spec.bootstrapAdminUsernameSourceKey ?? "username";
|
||||
const material = readSecretSourceScalar(ref, key, spec.bootstrapAdminUsernameSourceLine ?? null);
|
||||
if (!material.ok) return { ok: false, error: `username-${material.error}` };
|
||||
return { ok: true, value: material.value };
|
||||
}
|
||||
|
||||
function readSecretSourceScalar(sourceRef: string, sourceKey: string, sourceLine: number | null): { ok: true; value: string; sourcePath: string; sourcePresent: true } | { ok: false; sourcePath: string; sourcePresent: boolean; error: string } {
|
||||
const paths = secretSourcePaths(sourceRef);
|
||||
const sourcePath = paths.find((candidate) => existsSync(candidate)) ?? paths[0] ?? join(repoRoot, ".state", "secrets", sourceRef);
|
||||
if (!existsSync(sourcePath)) return { ok: false, sourcePath, sourcePresent: false, error: "secret-source-missing" };
|
||||
const text = readFileSync(sourcePath, "utf8");
|
||||
if (sourceLine !== null) {
|
||||
const line = text.split(/\r?\n/u)[sourceLine - 1]?.replace(/\r$/u, "") ?? "";
|
||||
if (line.length === 0) return { ok: false, sourcePath, sourcePresent: true, error: "secret-line-missing" };
|
||||
return { ok: true, value: line, sourcePath, sourcePresent: true };
|
||||
}
|
||||
const values = parseEnvFile(text);
|
||||
const runtimeValue = process.env[sourceKey];
|
||||
const value = values[sourceKey] ?? runtimeValue;
|
||||
if (value === undefined || value.length === 0) return { ok: false, sourcePath, sourcePresent: true, error: "secret-key-missing" };
|
||||
return { ok: true, value, sourcePath, sourcePresent: true };
|
||||
}
|
||||
|
||||
function bootstrapAdminSecretMaterialError(spec: RuntimeSecretSpec, sourceRef: string | null, sourceKey: string | null, sourceLine: number | null, sourcePath: string | null, sourcePresent: boolean | null, error: string): BootstrapAdminSecretMaterial {
|
||||
return {
|
||||
ok: false,
|
||||
username: null,
|
||||
usernameSourceRef: spec.bootstrapAdminUsernameSourceRef ?? null,
|
||||
usernameSourceKey: spec.bootstrapAdminUsernameSourceKey ?? null,
|
||||
usernameSourceLine: spec.bootstrapAdminUsernameSourceLine ?? null,
|
||||
usernameFingerprint: null,
|
||||
sourceRef,
|
||||
sourceKey,
|
||||
sourceLine,
|
||||
sourcePath,
|
||||
sourcePresent: sourcePresent === true,
|
||||
sourceFingerprint: null,
|
||||
passwordHash: null,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
function bootstrapAdminPasswordMaterialError(spec: RuntimeSecretSpec, sourceRef: string | null, sourceKey: string | null, sourceLine: number | null, sourcePath: string | null, sourcePresent: boolean | null, error: string): BootstrapAdminPasswordMaterial {
|
||||
return {
|
||||
ok: false,
|
||||
username: null,
|
||||
usernameSourceRef: spec.bootstrapAdminUsernameSourceRef ?? null,
|
||||
usernameSourceKey: spec.bootstrapAdminUsernameSourceKey ?? null,
|
||||
usernameSourceLine: spec.bootstrapAdminUsernameSourceLine ?? null,
|
||||
usernameFingerprint: null,
|
||||
sourceRef,
|
||||
sourceKey,
|
||||
sourceLine,
|
||||
sourcePath,
|
||||
sourcePresent: sourcePresent === true,
|
||||
sourceFingerprint: null,
|
||||
password: null,
|
||||
error,
|
||||
};
|
||||
}
|
||||
|
||||
export function parseEnvFile(text: string): Record<string, string> {
|
||||
const values: Record<string, string> = {};
|
||||
for (const rawLine of text.split(/\r?\n/u)) {
|
||||
@@ -1547,11 +1652,13 @@ export function nodeRuntimeGitMirrorTarget(spec: HwlabRuntimeLaneSpec): NodeRunt
|
||||
const gitMirror = record(target.gitMirror);
|
||||
const gitMirrorEgressProxy = record(gitMirror.egressProxy);
|
||||
const gitMirrorEgressMode = stringValue(gitMirrorEgressProxy.mode, "gitMirror.egressProxy.mode");
|
||||
if (gitMirrorEgressMode !== "node-global" && gitMirrorEgressMode !== "direct") throw new Error(`gitMirror.egressProxy.mode must be node-global or direct for node=${spec.nodeId} lane=${spec.lane}`);
|
||||
if (gitMirrorEgressMode !== "node-global" && gitMirrorEgressMode !== "host-route" && gitMirrorEgressMode !== "direct") throw new Error(`gitMirror.egressProxy.mode must be node-global, host-route, or direct for node=${spec.nodeId} lane=${spec.lane}`);
|
||||
const nodeEgressProxy = gitMirrorEgressMode === "direct"
|
||||
? { mode: "direct" as const, required: false as const }
|
||||
: nodeRuntimeGitMirrorEgressProxySpec(record(node.egressProxy), `nodes.${spec.nodeId}.egressProxy`);
|
||||
if (gitMirrorEgressMode === "node-global" && gitMirrorEgressProxy.required !== true) throw new Error(`gitMirror.egressProxy.required must be true for node=${spec.nodeId} lane=${spec.lane}`);
|
||||
if ((gitMirrorEgressMode === "node-global" || gitMirrorEgressMode === "host-route") && gitMirrorEgressProxy.required !== true) throw new Error(`gitMirror.egressProxy.required must be true for node=${spec.nodeId} lane=${spec.lane}`);
|
||||
if (gitMirrorEgressMode === "node-global" && nodeEgressProxy.mode !== "k8s-service-cluster-ip") throw new Error(`gitMirror.egressProxy.mode=node-global requires nodes.${spec.nodeId}.egressProxy.mode=k8s-service-cluster-ip`);
|
||||
if (gitMirrorEgressMode === "host-route" && nodeEgressProxy.mode !== "host-route") throw new Error(`gitMirror.egressProxy.mode=host-route requires nodes.${spec.nodeId}.egressProxy.mode=host-route`);
|
||||
const githubTransport = nodeRuntimeGitMirrorGithubTransportSpec(record(gitMirror.githubTransport), "gitMirror.githubTransport");
|
||||
const source = record(target.source);
|
||||
const gitops = record(target.gitops);
|
||||
@@ -1626,7 +1733,19 @@ function gitMirrorSecretSourceEncoding(raw: unknown, path: string): "plain" | "b
|
||||
|
||||
export function nodeRuntimeGitMirrorEgressProxySpec(raw: Record<string, unknown>, path: string): NodeRuntimeGitMirrorEgressProxySpec {
|
||||
const mode = stringValue(raw.mode, `${path}.mode`);
|
||||
if (mode !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be k8s-service-cluster-ip`);
|
||||
if (mode === "host-route") {
|
||||
const noProxyRaw = raw.noProxy;
|
||||
if (!Array.isArray(noProxyRaw)) throw new Error(`${path}.noProxy must be an array`);
|
||||
return {
|
||||
mode,
|
||||
clientName: stringValue(raw.clientName, `${path}.clientName`),
|
||||
hostProxyConfigRef: stringValue(raw.hostProxyConfigRef, `${path}.hostProxyConfigRef`),
|
||||
proxyEnvPath: stringValue(raw.proxyEnvPath, `${path}.proxyEnvPath`),
|
||||
proxyUrl: stringValue(raw.proxyUrl, `${path}.proxyUrl`),
|
||||
noProxy: noProxyRaw.map((item, index) => stringValue(item, `${path}.noProxy[${index}]`)),
|
||||
};
|
||||
}
|
||||
if (mode !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be k8s-service-cluster-ip or host-route`);
|
||||
const port = Number(raw.port);
|
||||
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error(`${path}.port must be a TCP port`);
|
||||
const sourceConfigRef = optionalStringValue(raw.sourceConfigRef, `${path}.sourceConfigRef`) ?? null;
|
||||
|
||||
Reference in New Issue
Block a user