Merge pull request #1154 from pikasTech/fix/issue-1148-d518-gitmirror-ssh
Fix D518 git-mirror SSH secret sourceRef
This commit is contained in:
@@ -186,6 +186,14 @@ targets:
|
|||||||
required: true
|
required: true
|
||||||
githubTransport:
|
githubTransport:
|
||||||
mode: ssh
|
mode: ssh
|
||||||
|
privateKeySecretKey: ssh-privatekey
|
||||||
|
privateKeySourceRef: github/hwlab-git-mirror-ssh.env
|
||||||
|
privateKeySourceKey: GITHUB_SSH_PRIVATE_KEY_B64
|
||||||
|
privateKeySourceEncoding: base64
|
||||||
|
knownHostsSecretKey: known_hosts
|
||||||
|
knownHostsSourceRef: github/hwlab-git-mirror-ssh.env
|
||||||
|
knownHostsSourceKey: GITHUB_KNOWN_HOSTS_B64
|
||||||
|
knownHostsSourceEncoding: base64
|
||||||
tekton:
|
tekton:
|
||||||
pipelineName: hwlab-d601-v03-ci-image-publish
|
pipelineName: hwlab-d601-v03-ci-image-publish
|
||||||
serviceAccountName: hwlab-d601-v03-tekton-runner
|
serviceAccountName: hwlab-d601-v03-tekton-runner
|
||||||
@@ -329,6 +337,14 @@ targets:
|
|||||||
required: true
|
required: true
|
||||||
githubTransport:
|
githubTransport:
|
||||||
mode: ssh
|
mode: ssh
|
||||||
|
privateKeySecretKey: ssh-privatekey
|
||||||
|
privateKeySourceRef: github/hwlab-git-mirror-ssh.env
|
||||||
|
privateKeySourceKey: GITHUB_SSH_PRIVATE_KEY_B64
|
||||||
|
privateKeySourceEncoding: base64
|
||||||
|
knownHostsSecretKey: known_hosts
|
||||||
|
knownHostsSourceRef: github/hwlab-git-mirror-ssh.env
|
||||||
|
knownHostsSourceKey: GITHUB_KNOWN_HOSTS_B64
|
||||||
|
knownHostsSourceEncoding: base64
|
||||||
tekton:
|
tekton:
|
||||||
pipelineName: hwlab-d518-v03-ci-image-publish
|
pipelineName: hwlab-d518-v03-ci-image-publish
|
||||||
serviceAccountName: hwlab-d518-v03-tekton-runner
|
serviceAccountName: hwlab-d518-v03-tekton-runner
|
||||||
|
|||||||
@@ -112,7 +112,17 @@ interface ControlPlaneGitMirrorEgressProxySpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ControlPlaneGitMirrorGithubTransportSpec =
|
type ControlPlaneGitMirrorGithubTransportSpec =
|
||||||
| { mode: "ssh" }
|
| {
|
||||||
|
mode: "ssh";
|
||||||
|
privateKeySecretKey: string;
|
||||||
|
privateKeySourceRef: string;
|
||||||
|
privateKeySourceKey: string;
|
||||||
|
privateKeySourceEncoding: "plain" | "base64";
|
||||||
|
knownHostsSecretKey: string | null;
|
||||||
|
knownHostsSourceRef: string | null;
|
||||||
|
knownHostsSourceKey: string | null;
|
||||||
|
knownHostsSourceEncoding: "plain" | "base64" | null;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
mode: "https";
|
mode: "https";
|
||||||
username: string;
|
username: string;
|
||||||
@@ -1451,7 +1461,38 @@ function gitMirrorEgressProxySpec(raw: Record<string, unknown>, path: string): C
|
|||||||
|
|
||||||
function gitMirrorGithubTransportSpec(raw: Record<string, unknown>, path: string): ControlPlaneGitMirrorGithubTransportSpec {
|
function gitMirrorGithubTransportSpec(raw: Record<string, unknown>, path: string): ControlPlaneGitMirrorGithubTransportSpec {
|
||||||
const mode = stringField(raw, "mode", path);
|
const mode = stringField(raw, "mode", path);
|
||||||
if (mode === "ssh") return { mode };
|
if (mode === "ssh") {
|
||||||
|
const privateKeySecretKey = stringField(raw, "privateKeySecretKey", path);
|
||||||
|
const privateKeySourceRef = stringField(raw, "privateKeySourceRef", path);
|
||||||
|
const privateKeySourceKey = stringField(raw, "privateKeySourceKey", path);
|
||||||
|
const privateKeySourceEncoding = secretSourceEncodingField(raw, "privateKeySourceEncoding", path);
|
||||||
|
const knownHostsSecretKey = optionalStringField(raw, "knownHostsSecretKey", path) ?? null;
|
||||||
|
const knownHostsSourceRef = optionalStringField(raw, "knownHostsSourceRef", path) ?? null;
|
||||||
|
const knownHostsSourceKey = optionalStringField(raw, "knownHostsSourceKey", path) ?? null;
|
||||||
|
const knownHostsSourceEncoding = raw.knownHostsSourceEncoding === undefined ? null : secretSourceEncodingField(raw, "knownHostsSourceEncoding", path);
|
||||||
|
const knownHostsFields = [knownHostsSecretKey, knownHostsSourceRef, knownHostsSourceKey, knownHostsSourceEncoding];
|
||||||
|
if (knownHostsFields.some((value) => value !== null) && knownHostsFields.some((value) => value === null)) {
|
||||||
|
throw new Error(`${path}.knownHostsSecretKey/sourceRef/sourceKey/sourceEncoding must be declared together`);
|
||||||
|
}
|
||||||
|
validateSecretKey(privateKeySecretKey, `${path}.privateKeySecretKey`);
|
||||||
|
if (privateKeySecretKey !== "ssh-privatekey") throw new Error(`${path}.privateKeySecretKey must be ssh-privatekey for kubernetes.io/ssh-auth`);
|
||||||
|
validateSourceRef(privateKeySourceRef, `${path}.privateKeySourceRef`);
|
||||||
|
validateEnvKey(privateKeySourceKey, `${path}.privateKeySourceKey`);
|
||||||
|
if (knownHostsSecretKey !== null) validateSecretKey(knownHostsSecretKey, `${path}.knownHostsSecretKey`);
|
||||||
|
if (knownHostsSourceRef !== null) validateSourceRef(knownHostsSourceRef, `${path}.knownHostsSourceRef`);
|
||||||
|
if (knownHostsSourceKey !== null) validateEnvKey(knownHostsSourceKey, `${path}.knownHostsSourceKey`);
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
privateKeySecretKey,
|
||||||
|
privateKeySourceRef,
|
||||||
|
privateKeySourceKey,
|
||||||
|
privateKeySourceEncoding,
|
||||||
|
knownHostsSecretKey,
|
||||||
|
knownHostsSourceRef,
|
||||||
|
knownHostsSourceKey,
|
||||||
|
knownHostsSourceEncoding,
|
||||||
|
};
|
||||||
|
}
|
||||||
if (mode !== "https") throw new Error(`${path}.mode must be ssh or https`);
|
if (mode !== "https") throw new Error(`${path}.mode must be ssh or https`);
|
||||||
const tokenSecretName = stringField(raw, "tokenSecretName", path);
|
const tokenSecretName = stringField(raw, "tokenSecretName", path);
|
||||||
const tokenSecretKey = stringField(raw, "tokenSecretKey", path);
|
const tokenSecretKey = stringField(raw, "tokenSecretKey", path);
|
||||||
@@ -1471,6 +1512,12 @@ function gitMirrorGithubTransportSpec(raw: Record<string, unknown>, path: string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function secretSourceEncodingField(raw: Record<string, unknown>, key: string, path: string): "plain" | "base64" {
|
||||||
|
const value = stringField(raw, key, path);
|
||||||
|
if (value !== "plain" && value !== "base64") throw new Error(`${path}.${key} must be plain or base64`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
function targetSpec(raw: Record<string, unknown>, index: number): ControlPlaneTargetSpec {
|
function targetSpec(raw: Record<string, unknown>, index: number): ControlPlaneTargetSpec {
|
||||||
const path = `targets[${index}]`;
|
const path = `targets[${index}]`;
|
||||||
const source = asRecord(raw.source, `${path}.source`);
|
const source = asRecord(raw.source, `${path}.source`);
|
||||||
@@ -1560,6 +1607,8 @@ function renderInfraManifest(_node: ControlPlaneNodeSpec, target: ControlPlaneTa
|
|||||||
);
|
);
|
||||||
const githubTokenSecret = gitMirrorGithubTokenSecret(target, labels);
|
const githubTokenSecret = gitMirrorGithubTokenSecret(target, labels);
|
||||||
if (githubTokenSecret !== null) manifests.push(githubTokenSecret);
|
if (githubTokenSecret !== null) manifests.push(githubTokenSecret);
|
||||||
|
const githubSshSecret = gitMirrorGithubSshSecret(target, labels);
|
||||||
|
if (githubSshSecret !== null) manifests.push(githubSshSecret);
|
||||||
if (target.gitMirror.cacheHostPath === null) {
|
if (target.gitMirror.cacheHostPath === null) {
|
||||||
manifests.push({
|
manifests.push({
|
||||||
apiVersion: "v1",
|
apiVersion: "v1",
|
||||||
@@ -1601,6 +1650,81 @@ function renderInfraManifest(_node: ControlPlaneNodeSpec, target: ControlPlaneTa
|
|||||||
return manifests;
|
return manifests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gitMirrorGithubSshSecret(target: ControlPlaneTargetSpec, labels: Record<string, string>): Record<string, unknown> | null {
|
||||||
|
const transport = target.gitMirror.githubTransport;
|
||||||
|
if (transport.mode !== "ssh") return null;
|
||||||
|
const material = gitMirrorGithubSshMaterial(transport);
|
||||||
|
return {
|
||||||
|
apiVersion: "v1",
|
||||||
|
kind: "Secret",
|
||||||
|
metadata: {
|
||||||
|
name: target.gitMirror.secretName,
|
||||||
|
namespace: target.gitMirror.namespace,
|
||||||
|
labels: { ...labels, "app.kubernetes.io/name": "git-mirror" },
|
||||||
|
annotations: {
|
||||||
|
"unidesk.ai/private-key-source-ref": transport.privateKeySourceRef,
|
||||||
|
"unidesk.ai/private-key-source-key": transport.privateKeySourceKey,
|
||||||
|
"unidesk.ai/private-key-target-key": transport.privateKeySecretKey,
|
||||||
|
"unidesk.ai/private-key-fingerprint": material.privateKeyFingerprint,
|
||||||
|
...(transport.knownHostsSecretKey === null ? {} : {
|
||||||
|
"unidesk.ai/known-hosts-source-ref": transport.knownHostsSourceRef ?? "",
|
||||||
|
"unidesk.ai/known-hosts-source-key": transport.knownHostsSourceKey ?? "",
|
||||||
|
"unidesk.ai/known-hosts-target-key": transport.knownHostsSecretKey,
|
||||||
|
"unidesk.ai/known-hosts-fingerprint": material.knownHostsFingerprint ?? "",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: "kubernetes.io/ssh-auth",
|
||||||
|
stringData: {
|
||||||
|
[transport.privateKeySecretKey]: material.privateKey,
|
||||||
|
...(transport.knownHostsSecretKey === null || material.knownHosts === null ? {} : { [transport.knownHostsSecretKey]: material.knownHosts }),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function gitMirrorGithubSshMaterial(transport: Extract<ControlPlaneGitMirrorGithubTransportSpec, { mode: "ssh" }>): { privateKey: string; knownHosts: string | null; privateKeyFingerprint: string; knownHostsFingerprint: string | null } {
|
||||||
|
const privateSource = readControlPlaneSecretSource(transport.privateKeySourceRef, `gitMirror.githubTransport private key source ${transport.privateKeySourceRef} is missing; create the YAML-declared sourceRef with ${transport.privateKeySourceKey} before applying the control plane`);
|
||||||
|
const privateValue = requiredEnvValue(privateSource.values, transport.privateKeySourceKey, transport.privateKeySourceRef);
|
||||||
|
const privateKey = decodeSecretSourceValue(privateValue, transport.privateKeySourceEncoding, "gitMirror.githubTransport.privateKeySourceKey");
|
||||||
|
if (!/-----BEGIN [A-Z ]*PRIVATE KEY-----/u.test(privateKey)) throw new Error(`${transport.privateKeySourceRef}.${transport.privateKeySourceKey} does not contain private key material`);
|
||||||
|
let knownHosts: string | null = null;
|
||||||
|
let knownHostsFingerprint: string | null = null;
|
||||||
|
if (transport.knownHostsSourceRef !== null && transport.knownHostsSourceKey !== null && transport.knownHostsSourceEncoding !== null) {
|
||||||
|
const knownHostsSource = readControlPlaneSecretSource(transport.knownHostsSourceRef, `gitMirror.githubTransport known_hosts source ${transport.knownHostsSourceRef} is missing; create the YAML-declared sourceRef with ${transport.knownHostsSourceKey} before applying the control plane`);
|
||||||
|
const knownHostsValue = requiredEnvValue(knownHostsSource.values, transport.knownHostsSourceKey, transport.knownHostsSourceRef);
|
||||||
|
knownHosts = decodeSecretSourceValue(knownHostsValue, transport.knownHostsSourceEncoding, "gitMirror.githubTransport.knownHostsSourceKey");
|
||||||
|
if (!/(^|\n)(github\.com|\[github\.com\]:22)\s+(ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp256)\s+/u.test(knownHosts)) {
|
||||||
|
throw new Error(`${transport.knownHostsSourceRef}.${transport.knownHostsSourceKey} must contain github.com known_hosts rows`);
|
||||||
|
}
|
||||||
|
knownHostsFingerprint = fingerprintSecretValues({ knownHosts }, ["knownHosts"]);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
privateKey: privateKey.endsWith("\n") ? privateKey : `${privateKey}\n`,
|
||||||
|
knownHosts: knownHosts === null ? null : (knownHosts.endsWith("\n") ? knownHosts : `${knownHosts}\n`),
|
||||||
|
privateKeyFingerprint: fingerprintSecretValues({ privateKey }, ["privateKey"]),
|
||||||
|
knownHostsFingerprint,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function readControlPlaneSecretSource(sourceRef: string, missingMessage: string): ReturnType<typeof readEnvSourceFile> {
|
||||||
|
return readEnvSourceFile({
|
||||||
|
root: rootPath(".state", "secrets"),
|
||||||
|
sourceRef,
|
||||||
|
missingMessage: () => missingMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeSecretSourceValue(value: string, encoding: "plain" | "base64", path: string): string {
|
||||||
|
if (encoding === "plain") return value;
|
||||||
|
try {
|
||||||
|
const compact = value.replace(/\s+/gu, "");
|
||||||
|
if (compact.length === 0) throw new Error("empty base64 value");
|
||||||
|
return Buffer.from(compact, "base64").toString("utf8");
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`${path} must be valid base64: ${error instanceof Error ? error.message : String(error)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function gitMirrorGithubTokenSecret(target: ControlPlaneTargetSpec, labels: Record<string, string>): Record<string, unknown> | null {
|
function gitMirrorGithubTokenSecret(target: ControlPlaneTargetSpec, labels: Record<string, string>): Record<string, unknown> | null {
|
||||||
const transport = target.gitMirror.githubTransport;
|
const transport = target.gitMirror.githubTransport;
|
||||||
if (transport.mode !== "https") return null;
|
if (transport.mode !== "https") return null;
|
||||||
@@ -1975,10 +2099,15 @@ function gitMirrorProxyPrelude(node: ControlPlaneNodeSpec, target: ControlPlaneT
|
|||||||
"remote=\"https://github.com/${repository}.git\"",
|
"remote=\"https://github.com/${repository}.git\"",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
const privateKeyPath = transport.mode === "ssh" ? `/git-ssh/${transport.privateKeySecretKey}` : "/git-ssh/ssh-privatekey";
|
||||||
|
const knownHostsCopy = transport.mode === "ssh" && transport.knownHostsSecretKey !== null
|
||||||
|
? [`cp ${shQuote(`/git-ssh/${transport.knownHostsSecretKey}`)} /root/.ssh/known_hosts`, "chmod 0600 /root/.ssh/known_hosts"]
|
||||||
|
: [];
|
||||||
return [
|
return [
|
||||||
"mkdir -p /root/.ssh",
|
"mkdir -p /root/.ssh",
|
||||||
"cp /git-ssh/ssh-privatekey /root/.ssh/id_rsa",
|
`cp ${shQuote(privateKeyPath)} /root/.ssh/id_rsa`,
|
||||||
"chmod 0400 /root/.ssh/id_rsa",
|
"chmod 0400 /root/.ssh/id_rsa",
|
||||||
|
...knownHostsCopy,
|
||||||
...common,
|
...common,
|
||||||
...proxyConnectBlock,
|
...proxyConnectBlock,
|
||||||
"cat > /tmp/hwlab-git-ssh-proxy.sh <<'SH_PROXY'",
|
"cat > /tmp/hwlab-git-ssh-proxy.sh <<'SH_PROXY'",
|
||||||
@@ -2294,7 +2423,20 @@ function gitMirrorEffectiveEgressProxySummary(node: ControlPlaneNodeSpec, target
|
|||||||
}
|
}
|
||||||
|
|
||||||
function gitMirrorGithubTransportSummary(transport: ControlPlaneGitMirrorGithubTransportSpec): Record<string, unknown> {
|
function gitMirrorGithubTransportSummary(transport: ControlPlaneGitMirrorGithubTransportSpec): Record<string, unknown> {
|
||||||
if (transport.mode === "ssh") return { mode: "ssh", valuesPrinted: false };
|
if (transport.mode === "ssh") {
|
||||||
|
return {
|
||||||
|
mode: "ssh",
|
||||||
|
privateKeySecretKey: transport.privateKeySecretKey,
|
||||||
|
privateKeySourceRef: transport.privateKeySourceRef,
|
||||||
|
privateKeySourceKey: transport.privateKeySourceKey,
|
||||||
|
privateKeySourceEncoding: transport.privateKeySourceEncoding,
|
||||||
|
knownHostsSecretKey: transport.knownHostsSecretKey,
|
||||||
|
knownHostsSourceRef: transport.knownHostsSourceRef,
|
||||||
|
knownHostsSourceKey: transport.knownHostsSourceKey,
|
||||||
|
knownHostsSourceEncoding: transport.knownHostsSourceEncoding,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
mode: "https",
|
mode: "https",
|
||||||
username: transport.username,
|
username: transport.username,
|
||||||
@@ -2332,6 +2474,13 @@ write_svc=${shQuote(target.gitMirror.serviceWriteName)}
|
|||||||
cache_pvc=${shQuote(target.gitMirror.cachePvcName)}
|
cache_pvc=${shQuote(target.gitMirror.cachePvcName)}
|
||||||
cache_host_path=${shQuote(target.gitMirror.cacheHostPath ?? "")}
|
cache_host_path=${shQuote(target.gitMirror.cacheHostPath ?? "")}
|
||||||
github_transport_mode=${shQuote(target.gitMirror.githubTransport.mode)}
|
github_transport_mode=${shQuote(target.gitMirror.githubTransport.mode)}
|
||||||
|
github_ssh_secret=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.secretName : "")}
|
||||||
|
github_ssh_private_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySecretKey : "")}
|
||||||
|
github_ssh_private_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceRef : "")}
|
||||||
|
github_ssh_private_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.privateKeySourceKey : "")}
|
||||||
|
github_ssh_known_hosts_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSecretKey ?? "" : "")}
|
||||||
|
github_ssh_known_hosts_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceRef ?? "" : "")}
|
||||||
|
github_ssh_known_hosts_source_key=${shQuote(target.gitMirror.githubTransport.mode === "ssh" ? target.gitMirror.githubTransport.knownHostsSourceKey ?? "" : "")}
|
||||||
github_token_secret=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretName : "")}
|
github_token_secret=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretName : "")}
|
||||||
github_token_key=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretKey : "")}
|
github_token_key=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSecretKey : "")}
|
||||||
github_token_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSourceRef : "")}
|
github_token_source_ref=${shQuote(target.gitMirror.githubTransport.mode === "https" ? target.gitMirror.githubTransport.tokenSourceRef : "")}
|
||||||
@@ -2358,24 +2507,55 @@ exists_res() { kubectl -n "$1" get "$2" "$3" >/dev/null 2>&1 && printf true || p
|
|||||||
deploy_ready() { desired=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; }
|
deploy_ready() { desired=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get deploy "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; }
|
||||||
sts_ready() { desired=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; }
|
sts_ready() { desired=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n "$1" get statefulset "$2" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n "$desired" ] && [ "$desired" -gt 0 ] 2>/dev/null && [ "\${ready:-0}" = "$desired" ] && printf true || printf false; }
|
||||||
endpoint_ready() { endpoints=$(kubectl -n "$1" get endpoints "$2" -o 'jsonpath={.subsets[*].addresses[*].ip}' 2>/dev/null || true); [ -n "$endpoints" ] && printf true || printf false; }
|
endpoint_ready() { endpoints=$(kubectl -n "$1" get endpoints "$2" -o 'jsonpath={.subsets[*].addresses[*].ip}' 2>/dev/null || true); [ -n "$endpoints" ] && printf true || printf false; }
|
||||||
github_transport_json=$(python3 - "$github_transport_mode" "$gitmirror_ns" "$github_token_secret" "$github_token_key" "$github_token_source_ref" "$github_token_source_key" <<'PY'
|
github_transport_json=$(python3 - "$github_transport_mode" "$gitmirror_ns" "$github_ssh_secret" "$github_ssh_private_key" "$github_ssh_private_source_ref" "$github_ssh_private_source_key" "$github_ssh_known_hosts_key" "$github_ssh_known_hosts_source_ref" "$github_ssh_known_hosts_source_key" "$github_token_secret" "$github_token_key" "$github_token_source_ref" "$github_token_source_key" <<'PY'
|
||||||
import hashlib, json, subprocess, sys
|
import hashlib, json, subprocess, sys
|
||||||
mode, namespace, secret_name, secret_key, source_ref, source_key = sys.argv[1:7]
|
mode, namespace, ssh_secret, ssh_private_key, ssh_private_source_ref, ssh_private_source_key, ssh_known_hosts_key, ssh_known_hosts_source_ref, ssh_known_hosts_source_key, token_secret, token_key, token_source_ref, token_source_key = sys.argv[1:14]
|
||||||
if mode != "https":
|
def read_secret(name):
|
||||||
print(json.dumps({"mode": mode, "required": False, "ready": True, "valuesPrinted": False}))
|
proc = subprocess.run(["kubectl", "-n", namespace, "get", "secret", name, "-o", "json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
||||||
raise SystemExit(0)
|
if proc.returncode != 0:
|
||||||
proc = subprocess.run(["kubectl", "-n", namespace, "get", "secret", secret_name, "-o", "json"], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
return False, {}, {}
|
||||||
exists = proc.returncode == 0
|
|
||||||
data = {}
|
|
||||||
if exists:
|
|
||||||
try:
|
try:
|
||||||
data = json.loads(proc.stdout).get("data") or {}
|
obj = json.loads(proc.stdout)
|
||||||
except Exception:
|
except Exception:
|
||||||
data = {}
|
obj = {}
|
||||||
encoded = data.get(secret_key) if isinstance(data, dict) else None
|
return True, obj.get("data") or {}, obj.get("metadata", {}).get("annotations") or {}
|
||||||
|
def fingerprint(value):
|
||||||
|
return "sha256:" + hashlib.sha256(value.encode()).hexdigest()[:16] if value else None
|
||||||
|
if mode == "ssh":
|
||||||
|
exists, data, annotations = read_secret(ssh_secret)
|
||||||
|
private_encoded = data.get(ssh_private_key) if isinstance(data, dict) else None
|
||||||
|
known_hosts_encoded = data.get(ssh_known_hosts_key) if ssh_known_hosts_key and isinstance(data, dict) else None
|
||||||
|
private_present = isinstance(private_encoded, str) and len(private_encoded) > 0
|
||||||
|
known_hosts_expected = bool(ssh_known_hosts_key)
|
||||||
|
known_hosts_present = isinstance(known_hosts_encoded, str) and len(known_hosts_encoded) > 0
|
||||||
|
print(json.dumps({
|
||||||
|
"mode": mode,
|
||||||
|
"required": True,
|
||||||
|
"ready": exists and private_present and (not known_hosts_expected or known_hosts_present),
|
||||||
|
"secretName": ssh_secret,
|
||||||
|
"privateKeySecretKey": ssh_private_key,
|
||||||
|
"privateKeySourceRef": ssh_private_source_ref,
|
||||||
|
"privateKeySourceKey": ssh_private_source_key,
|
||||||
|
"privateKeySecretExists": exists,
|
||||||
|
"privateKeyPresent": private_present,
|
||||||
|
"privateKeyBytes": len(private_encoded) if private_present else 0,
|
||||||
|
"privateKeyFingerprint": annotations.get("unidesk.ai/private-key-fingerprint") or fingerprint(private_encoded),
|
||||||
|
"knownHostsSecretKey": ssh_known_hosts_key or None,
|
||||||
|
"knownHostsSourceRef": ssh_known_hosts_source_ref or None,
|
||||||
|
"knownHostsSourceKey": ssh_known_hosts_source_key or None,
|
||||||
|
"knownHostsPresent": (known_hosts_present if known_hosts_expected else None),
|
||||||
|
"knownHostsBytes": (len(known_hosts_encoded) if known_hosts_present else 0) if known_hosts_expected else None,
|
||||||
|
"knownHostsFingerprint": annotations.get("unidesk.ai/known-hosts-fingerprint") or fingerprint(known_hosts_encoded),
|
||||||
|
"valuesPrinted": False,
|
||||||
|
}))
|
||||||
|
raise SystemExit(0)
|
||||||
|
if mode != "https":
|
||||||
|
print(json.dumps({"mode": mode, "required": True, "ready": False, "valuesPrinted": False}))
|
||||||
|
raise SystemExit(0)
|
||||||
|
exists, data, _ = read_secret(token_secret)
|
||||||
|
encoded = data.get(token_key) if isinstance(data, dict) else None
|
||||||
present = isinstance(encoded, str) and len(encoded) > 0
|
present = isinstance(encoded, str) and len(encoded) > 0
|
||||||
fingerprint = "sha256:" + hashlib.sha256(encoded.encode()).hexdigest()[:16] if present else None
|
print(json.dumps({"mode": mode, "required": True, "ready": exists and present, "tokenSecretName": token_secret, "tokenSecretKey": token_key, "tokenSourceRef": token_source_ref, "tokenSourceKey": token_source_key, "tokenSecretExists": exists, "tokenKeyPresent": present, "tokenKeyBytes": len(encoded) if present else 0, "tokenFingerprint": fingerprint(encoded), "valuesPrinted": False}))
|
||||||
print(json.dumps({"mode": mode, "required": True, "ready": exists and present, "tokenSecretName": secret_name, "tokenSecretKey": secret_key, "tokenSourceRef": source_ref, "tokenSourceKey": source_key, "tokenSecretExists": exists, "tokenKeyPresent": present, "tokenKeyBytes": len(encoded) if present else 0, "tokenFingerprint": fingerprint, "valuesPrinted": False}))
|
|
||||||
PY
|
PY
|
||||||
)
|
)
|
||||||
registry_ready=false
|
registry_ready=false
|
||||||
|
|||||||
@@ -401,7 +401,17 @@ export interface NodeRuntimeGitMirrorTargetSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type NodeRuntimeGitMirrorGithubTransportSpec =
|
export type NodeRuntimeGitMirrorGithubTransportSpec =
|
||||||
| { mode: "ssh" }
|
| {
|
||||||
|
mode: "ssh";
|
||||||
|
privateKeySecretKey: string;
|
||||||
|
privateKeySourceRef: string;
|
||||||
|
privateKeySourceKey: string;
|
||||||
|
privateKeySourceEncoding: "plain" | "base64";
|
||||||
|
knownHostsSecretKey: string | null;
|
||||||
|
knownHostsSourceRef: string | null;
|
||||||
|
knownHostsSourceKey: string | null;
|
||||||
|
knownHostsSourceEncoding: "plain" | "base64" | null;
|
||||||
|
}
|
||||||
| {
|
| {
|
||||||
mode: "https";
|
mode: "https";
|
||||||
username: string;
|
username: string;
|
||||||
|
|||||||
@@ -131,7 +131,21 @@ export function nodeRuntimeGitMirrorGithubTransportEnv(mirror: NodeRuntimeGitMir
|
|||||||
|
|
||||||
export function nodeRuntimeGitMirrorGithubTransportSummary(mirror: NodeRuntimeGitMirrorTargetSpec): Record<string, unknown> {
|
export function nodeRuntimeGitMirrorGithubTransportSummary(mirror: NodeRuntimeGitMirrorTargetSpec): Record<string, unknown> {
|
||||||
const transport = mirror.githubTransport;
|
const transport = mirror.githubTransport;
|
||||||
if (transport.mode === "ssh") return { mode: "ssh", secretName: mirror.secretName, valuesPrinted: false };
|
if (transport.mode === "ssh") {
|
||||||
|
return {
|
||||||
|
mode: "ssh",
|
||||||
|
secretName: mirror.secretName,
|
||||||
|
privateKeySecretKey: transport.privateKeySecretKey,
|
||||||
|
privateKeySourceRef: transport.privateKeySourceRef,
|
||||||
|
privateKeySourceKey: transport.privateKeySourceKey,
|
||||||
|
privateKeySourceEncoding: transport.privateKeySourceEncoding,
|
||||||
|
knownHostsSecretKey: transport.knownHostsSecretKey,
|
||||||
|
knownHostsSourceRef: transport.knownHostsSourceRef,
|
||||||
|
knownHostsSourceKey: transport.knownHostsSourceKey,
|
||||||
|
knownHostsSourceEncoding: transport.knownHostsSourceEncoding,
|
||||||
|
valuesPrinted: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
return {
|
return {
|
||||||
mode: "https",
|
mode: "https",
|
||||||
username: transport.username,
|
username: transport.username,
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ export function nodeRuntimeGitMirrorStatus(scoped: ReturnType<typeof parseNodeSc
|
|||||||
`cache_pvc=${shellQuote(mirror.cachePvcName)}`,
|
`cache_pvc=${shellQuote(mirror.cachePvcName)}`,
|
||||||
`cache_host_path=${shellQuote(mirror.cacheHostPath ?? "")}`,
|
`cache_host_path=${shellQuote(mirror.cacheHostPath ?? "")}`,
|
||||||
`github_transport_mode=${shellQuote(mirror.githubTransport.mode)}`,
|
`github_transport_mode=${shellQuote(mirror.githubTransport.mode)}`,
|
||||||
|
`github_ssh_secret=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.secretName : "")}`,
|
||||||
|
`github_ssh_private_key=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.privateKeySecretKey : "")}`,
|
||||||
|
`github_ssh_private_source_ref=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.privateKeySourceRef : "")}`,
|
||||||
|
`github_ssh_private_source_key=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.privateKeySourceKey : "")}`,
|
||||||
|
`github_ssh_known_hosts_key=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.knownHostsSecretKey ?? "" : "")}`,
|
||||||
|
`github_ssh_known_hosts_source_ref=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.knownHostsSourceRef ?? "" : "")}`,
|
||||||
|
`github_ssh_known_hosts_source_key=${shellQuote(mirror.githubTransport.mode === "ssh" ? mirror.githubTransport.knownHostsSourceKey ?? "" : "")}`,
|
||||||
`github_token_secret=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSecretName : "")}`,
|
`github_token_secret=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSecretName : "")}`,
|
||||||
`github_token_key=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSecretKey : "")}`,
|
`github_token_key=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSecretKey : "")}`,
|
||||||
`github_token_source_ref=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSourceRef : "")}`,
|
`github_token_source_ref=${shellQuote(mirror.githubTransport.mode === "https" ? mirror.githubTransport.tokenSourceRef : "")}`,
|
||||||
@@ -74,24 +81,36 @@ export function nodeRuntimeGitMirrorStatus(scoped: ReturnType<typeof parseNodeSc
|
|||||||
"deploy_ready() { desired=$(kubectl -n \"$1\" get deploy \"$2\" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n \"$1\" get deploy \"$2\" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n \"$desired\" ] && [ \"$desired\" -gt 0 ] 2>/dev/null && [ \"${ready:-0}\" = \"$desired\" ] && printf true || printf false; }",
|
"deploy_ready() { desired=$(kubectl -n \"$1\" get deploy \"$2\" -o 'jsonpath={.spec.replicas}' 2>/dev/null || true); ready=$(kubectl -n \"$1\" get deploy \"$2\" -o 'jsonpath={.status.readyReplicas}' 2>/dev/null || true); [ -n \"$desired\" ] && [ \"$desired\" -gt 0 ] 2>/dev/null && [ \"${ready:-0}\" = \"$desired\" ] && printf true || printf false; }",
|
||||||
"exists_res() { kubectl -n \"$1\" get \"$2\" \"$3\" >/dev/null 2>&1 && printf true || printf false; }",
|
"exists_res() { kubectl -n \"$1\" get \"$2\" \"$3\" >/dev/null 2>&1 && printf true || printf false; }",
|
||||||
"endpoint_ready() { endpoints=$(kubectl -n \"$1\" get endpoints \"$2\" -o 'jsonpath={.subsets[*].addresses[*].ip}' 2>/dev/null || true); [ -n \"$endpoints\" ] && printf true || printf false; }",
|
"endpoint_ready() { endpoints=$(kubectl -n \"$1\" get endpoints \"$2\" -o 'jsonpath={.subsets[*].addresses[*].ip}' 2>/dev/null || true); [ -n \"$endpoints\" ] && printf true || printf false; }",
|
||||||
"github_transport_json=$(python3 - \"$github_transport_mode\" \"$namespace\" \"$github_token_secret\" \"$github_token_key\" \"$github_token_source_ref\" \"$github_token_source_key\" <<'PY'",
|
"github_transport_json=$(python3 - \"$github_transport_mode\" \"$namespace\" \"$github_ssh_secret\" \"$github_ssh_private_key\" \"$github_ssh_private_source_ref\" \"$github_ssh_private_source_key\" \"$github_ssh_known_hosts_key\" \"$github_ssh_known_hosts_source_ref\" \"$github_ssh_known_hosts_source_key\" \"$github_token_secret\" \"$github_token_key\" \"$github_token_source_ref\" \"$github_token_source_key\" <<'PY'",
|
||||||
"import hashlib, json, subprocess, sys",
|
"import hashlib, json, subprocess, sys",
|
||||||
"mode, namespace, secret_name, secret_key, source_ref, source_key = sys.argv[1:7]",
|
"mode, namespace, ssh_secret, ssh_private_key, ssh_private_source_ref, ssh_private_source_key, ssh_known_hosts_key, ssh_known_hosts_source_ref, ssh_known_hosts_source_key, token_secret, token_key, token_source_ref, token_source_key = sys.argv[1:14]",
|
||||||
"if mode != 'https':",
|
"def read_secret(name):",
|
||||||
" print(json.dumps({'mode': mode, 'required': False, 'ready': True, 'valuesPrinted': False}))",
|
" proc = subprocess.run(['kubectl', '-n', namespace, 'get', 'secret', name, '-o', 'json'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)",
|
||||||
" raise SystemExit(0)",
|
" if proc.returncode != 0:",
|
||||||
"proc = subprocess.run(['kubectl', '-n', namespace, 'get', 'secret', secret_name, '-o', 'json'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)",
|
" return False, {}, {}",
|
||||||
"exists = proc.returncode == 0",
|
|
||||||
"data = {}",
|
|
||||||
"if exists:",
|
|
||||||
" try:",
|
" try:",
|
||||||
" data = json.loads(proc.stdout).get('data') or {}",
|
" obj = json.loads(proc.stdout)",
|
||||||
" except Exception:",
|
" except Exception:",
|
||||||
" data = {}",
|
" obj = {}",
|
||||||
"encoded = data.get(secret_key) if isinstance(data, dict) else None",
|
" return True, obj.get('data') or {}, obj.get('metadata', {}).get('annotations') or {}",
|
||||||
|
"def fingerprint(value):",
|
||||||
|
" return 'sha256:' + hashlib.sha256(value.encode()).hexdigest()[:16] if value else None",
|
||||||
|
"if mode == 'ssh':",
|
||||||
|
" exists, data, annotations = read_secret(ssh_secret)",
|
||||||
|
" private_encoded = data.get(ssh_private_key) if isinstance(data, dict) else None",
|
||||||
|
" known_hosts_encoded = data.get(ssh_known_hosts_key) if ssh_known_hosts_key and isinstance(data, dict) else None",
|
||||||
|
" private_present = isinstance(private_encoded, str) and len(private_encoded) > 0",
|
||||||
|
" known_hosts_expected = bool(ssh_known_hosts_key)",
|
||||||
|
" known_hosts_present = isinstance(known_hosts_encoded, str) and len(known_hosts_encoded) > 0",
|
||||||
|
" print(json.dumps({'mode': mode, 'required': True, 'ready': exists and private_present and (not known_hosts_expected or known_hosts_present), 'secretName': ssh_secret, 'privateKeySecretKey': ssh_private_key, 'privateKeySourceRef': ssh_private_source_ref, 'privateKeySourceKey': ssh_private_source_key, 'privateKeySecretExists': exists, 'privateKeyPresent': private_present, 'privateKeyBytes': len(private_encoded) if private_present else 0, 'privateKeyFingerprint': annotations.get('unidesk.ai/private-key-fingerprint') or fingerprint(private_encoded), 'knownHostsSecretKey': ssh_known_hosts_key or None, 'knownHostsSourceRef': ssh_known_hosts_source_ref or None, 'knownHostsSourceKey': ssh_known_hosts_source_key or None, 'knownHostsPresent': (known_hosts_present if known_hosts_expected else None), 'knownHostsBytes': (len(known_hosts_encoded) if known_hosts_present else 0) if known_hosts_expected else None, 'knownHostsFingerprint': annotations.get('unidesk.ai/known-hosts-fingerprint') or fingerprint(known_hosts_encoded), 'valuesPrinted': False}))",
|
||||||
|
" raise SystemExit(0)",
|
||||||
|
"if mode != 'https':",
|
||||||
|
" print(json.dumps({'mode': mode, 'required': True, 'ready': False, 'valuesPrinted': False}))",
|
||||||
|
" raise SystemExit(0)",
|
||||||
|
"exists, data, _ = read_secret(token_secret)",
|
||||||
|
"encoded = data.get(token_key) if isinstance(data, dict) else None",
|
||||||
"present = isinstance(encoded, str) and len(encoded) > 0",
|
"present = isinstance(encoded, str) and len(encoded) > 0",
|
||||||
"fingerprint = 'sha256:' + hashlib.sha256(encoded.encode()).hexdigest()[:16] if present else None",
|
"print(json.dumps({'mode': mode, 'required': True, 'ready': exists and present, 'tokenSecretName': token_secret, 'tokenSecretKey': token_key, 'tokenSourceRef': token_source_ref, 'tokenSourceKey': token_source_key, 'tokenSecretExists': exists, 'tokenKeyPresent': present, 'tokenKeyBytes': len(encoded) if present else 0, 'tokenFingerprint': fingerprint(encoded), 'valuesPrinted': False}))",
|
||||||
"print(json.dumps({'mode': mode, 'required': True, 'ready': exists and present, 'tokenSecretName': secret_name, 'tokenSecretKey': secret_key, 'tokenSourceRef': source_ref, 'tokenSourceKey': source_key, 'tokenSecretExists': exists, 'tokenKeyPresent': present, 'tokenKeyBytes': len(encoded) if present else 0, 'tokenFingerprint': fingerprint, 'valuesPrinted': False}))",
|
|
||||||
"PY",
|
"PY",
|
||||||
")",
|
")",
|
||||||
"summary_json=$(kubectl -n \"$namespace\" exec deploy/\"$read_deploy\" -- sh -lc '/etc/git-mirror/status.sh' 2>/tmp/hwlab-node-gitmirror-status.err || true)",
|
"summary_json=$(kubectl -n \"$namespace\" exec deploy/\"$read_deploy\" -- sh -lc '/etc/git-mirror/status.sh' 2>/tmp/hwlab-node-gitmirror-status.err || true)",
|
||||||
|
|||||||
@@ -1184,7 +1184,27 @@ export function nodeRuntimeGitMirrorTarget(spec: HwlabRuntimeLaneSpec): NodeRunt
|
|||||||
|
|
||||||
export function nodeRuntimeGitMirrorGithubTransportSpec(raw: Record<string, unknown>, path: string): NodeRuntimeGitMirrorGithubTransportSpec {
|
export function nodeRuntimeGitMirrorGithubTransportSpec(raw: Record<string, unknown>, path: string): NodeRuntimeGitMirrorGithubTransportSpec {
|
||||||
const mode = stringValue(raw.mode, `${path}.mode`);
|
const mode = stringValue(raw.mode, `${path}.mode`);
|
||||||
if (mode === "ssh") return { mode };
|
if (mode === "ssh") {
|
||||||
|
const knownHostsSecretKey = optionalStringValue(raw.knownHostsSecretKey, `${path}.knownHostsSecretKey`);
|
||||||
|
const knownHostsSourceRef = optionalStringValue(raw.knownHostsSourceRef, `${path}.knownHostsSourceRef`);
|
||||||
|
const knownHostsSourceKey = optionalStringValue(raw.knownHostsSourceKey, `${path}.knownHostsSourceKey`);
|
||||||
|
const knownHostsSourceEncoding = raw.knownHostsSourceEncoding === undefined ? null : gitMirrorSecretSourceEncoding(raw.knownHostsSourceEncoding, `${path}.knownHostsSourceEncoding`);
|
||||||
|
const knownHostsValues = [knownHostsSecretKey, knownHostsSourceRef, knownHostsSourceKey, knownHostsSourceEncoding];
|
||||||
|
if (knownHostsValues.some((value) => value !== null) && knownHostsValues.some((value) => value === null)) {
|
||||||
|
throw new Error(`${path}.knownHostsSecretKey/sourceRef/sourceKey/sourceEncoding must be declared together`);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
mode,
|
||||||
|
privateKeySecretKey: stringValue(raw.privateKeySecretKey, `${path}.privateKeySecretKey`),
|
||||||
|
privateKeySourceRef: stringValue(raw.privateKeySourceRef, `${path}.privateKeySourceRef`),
|
||||||
|
privateKeySourceKey: stringValue(raw.privateKeySourceKey, `${path}.privateKeySourceKey`),
|
||||||
|
privateKeySourceEncoding: gitMirrorSecretSourceEncoding(raw.privateKeySourceEncoding, `${path}.privateKeySourceEncoding`),
|
||||||
|
knownHostsSecretKey,
|
||||||
|
knownHostsSourceRef,
|
||||||
|
knownHostsSourceKey,
|
||||||
|
knownHostsSourceEncoding,
|
||||||
|
};
|
||||||
|
}
|
||||||
if (mode !== "https") throw new Error(`${path}.mode must be ssh or https`);
|
if (mode !== "https") throw new Error(`${path}.mode must be ssh or https`);
|
||||||
return {
|
return {
|
||||||
mode,
|
mode,
|
||||||
@@ -1196,6 +1216,12 @@ export function nodeRuntimeGitMirrorGithubTransportSpec(raw: Record<string, unkn
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function gitMirrorSecretSourceEncoding(raw: unknown, path: string): "plain" | "base64" {
|
||||||
|
const value = stringValue(raw, path);
|
||||||
|
if (value !== "plain" && value !== "base64") throw new Error(`${path} must be plain or base64`);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
export function nodeRuntimeGitMirrorEgressProxySpec(raw: Record<string, unknown>, path: string): NodeRuntimeGitMirrorEgressProxySpec {
|
export function nodeRuntimeGitMirrorEgressProxySpec(raw: Record<string, unknown>, path: string): NodeRuntimeGitMirrorEgressProxySpec {
|
||||||
const mode = stringValue(raw.mode, `${path}.mode`);
|
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 !== "k8s-service-cluster-ip") throw new Error(`${path}.mode must be k8s-service-cluster-ip`);
|
||||||
|
|||||||
Reference in New Issue
Block a user