fix(sub2api): add JD01 account local proxy
This commit is contained in:
@@ -14,7 +14,7 @@ import { capture, compactCapture, parseJsonOutput, prepareFrpcSecret, shQuote }
|
||||
import { yamlBooleanField, yamlFieldLabel, yamlIntegerField } from "../platform-infra-ops-library";
|
||||
import { fingerprintSecretValues, parseEnvFile, readEnvSourceFile, readTextFile, redactRepoPath, requiredEnvValue } from "../secrets";
|
||||
|
||||
import type { EgressProxySecretMaterial, ExternalActiveSecretMaterial, PublicExposureSecretMaterial, Sub2ApiConfig, Sub2ApiPublicExposureConfig, Sub2ApiTargetConfig } from "./entry";
|
||||
import type { AccountLocalProxySecretMaterial, EgressProxySecretMaterial, ExternalActiveSecretMaterial, PublicExposureSecretMaterial, Sub2ApiConfig, Sub2ApiPublicExposureConfig, Sub2ApiTargetConfig } from "./entry";
|
||||
import { fieldManager, requiredSecretKeys, sub2apiCaddyManagedMarker } from "./entry";
|
||||
import { managedResourceCleanupPlan } from "./manifest";
|
||||
|
||||
@@ -99,8 +99,30 @@ export function quoteEnv(value: string): string {
|
||||
return `'${value.replaceAll("'", "'\"'\"'")}'`;
|
||||
}
|
||||
|
||||
export function dryRunScript(yaml: string, target: Sub2ApiTargetConfig): string {
|
||||
export function dryRunScript(
|
||||
yaml: string,
|
||||
target: Sub2ApiTargetConfig,
|
||||
accountLocalProxySecretMaterial: AccountLocalProxySecretMaterial | null,
|
||||
): string {
|
||||
const encoded = Buffer.from(yaml, "utf8").toString("base64");
|
||||
const accountLocalProxySecretName = accountLocalProxySecretMaterial?.secretName ?? "sub2api-account-local-proxy-config";
|
||||
const accountLocalProxySecretKey = accountLocalProxySecretMaterial?.secretKey ?? "config.json";
|
||||
const accountLocalProxySecretFile = accountLocalProxySecretMaterial === null
|
||||
? ""
|
||||
: `printf '%s' '${Buffer.from(accountLocalProxySecretMaterial.configJson, "utf8").toString("base64")}' | base64 -d >"$tmp/account-local-proxy-config.json"`;
|
||||
const accountLocalProxySecretSummary = accountLocalProxySecretMaterial === null
|
||||
? "None"
|
||||
: `json.loads(${JSON.stringify(JSON.stringify({
|
||||
sourceRef: accountLocalProxySecretMaterial.sourceRef,
|
||||
sourcePath: accountLocalProxySecretMaterial.sourcePath,
|
||||
secretName: accountLocalProxySecretMaterial.secretName,
|
||||
secretKey: accountLocalProxySecretMaterial.secretKey,
|
||||
fingerprint: accountLocalProxySecretMaterial.fingerprint,
|
||||
sourceBytes: accountLocalProxySecretMaterial.sourceBytes,
|
||||
selectedOutbound: accountLocalProxySecretMaterial.selectedOutbound,
|
||||
proxyUrl: accountLocalProxySecretMaterial.proxyUrl,
|
||||
valuesPrinted: false,
|
||||
}))})`;
|
||||
return `
|
||||
set -u
|
||||
tmp="$(mktemp -d)"
|
||||
@@ -111,6 +133,11 @@ client_out="$tmp/client.out"
|
||||
client_err="$tmp/client.err"
|
||||
server_out="$tmp/server.out"
|
||||
server_err="$tmp/server.err"
|
||||
account_local_proxy_secret_yaml="$tmp/account-local-proxy-secret.yaml"
|
||||
account_local_proxy_secret_out="$tmp/account-local-proxy-secret.out"
|
||||
account_local_proxy_secret_err="$tmp/account-local-proxy-secret.err"
|
||||
account_local_proxy_secret_action="not-enabled"
|
||||
account_local_proxy_secret_rc=0
|
||||
kubectl apply --dry-run=client -f "$manifest" >"$client_out" 2>"$client_err"
|
||||
client_rc=$?
|
||||
if kubectl get namespace ${target.namespace} >/dev/null 2>&1; then
|
||||
@@ -123,20 +150,44 @@ else
|
||||
printf '%s\\n' 'server dry-run skipped because namespace does not exist yet; first apply creates it before namespaced resources' >"$server_out"
|
||||
: >"$server_err"
|
||||
fi
|
||||
python3 - "$client_rc" "$server_rc" "$namespace_exists" "$client_out" "$client_err" "$server_out" "$server_err" <<'PY'
|
||||
if [ "${accountLocalProxySecretMaterial === null ? "false" : "true"}" = "true" ]; then
|
||||
${accountLocalProxySecretFile}
|
||||
kubectl -n ${target.namespace} create secret generic ${accountLocalProxySecretName} \\
|
||||
--from-file=${accountLocalProxySecretKey}="$tmp/account-local-proxy-config.json" \\
|
||||
--dry-run=client -o yaml >"$account_local_proxy_secret_yaml" 2>"$account_local_proxy_secret_err"
|
||||
account_local_proxy_secret_rc=$?
|
||||
account_local_proxy_secret_action="client-dry-run"
|
||||
if [ "$account_local_proxy_secret_rc" -eq 0 ]; then
|
||||
if [ "$namespace_exists" = "true" ]; then
|
||||
kubectl apply --server-side --dry-run=server --field-manager=${fieldManager} -f "$account_local_proxy_secret_yaml" >"$account_local_proxy_secret_out" 2>>"$account_local_proxy_secret_err"
|
||||
account_local_proxy_secret_rc=$?
|
||||
account_local_proxy_secret_action="server-dry-run"
|
||||
else
|
||||
printf '%s\\n' 'server dry-run skipped because namespace does not exist yet; client dry-run succeeded' >"$account_local_proxy_secret_out"
|
||||
fi
|
||||
else
|
||||
: >"$account_local_proxy_secret_out"
|
||||
fi
|
||||
else
|
||||
: >"$account_local_proxy_secret_out"
|
||||
: >"$account_local_proxy_secret_err"
|
||||
fi
|
||||
python3 - "$client_rc" "$server_rc" "$account_local_proxy_secret_rc" "$namespace_exists" "$account_local_proxy_secret_action" "$client_out" "$client_err" "$server_out" "$server_err" "$account_local_proxy_secret_out" "$account_local_proxy_secret_err" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
client_rc = int(sys.argv[1])
|
||||
server_rc = int(sys.argv[2])
|
||||
namespace_exists = sys.argv[3] == "true"
|
||||
paths = sys.argv[4:]
|
||||
account_local_proxy_secret_rc = int(sys.argv[3])
|
||||
namespace_exists = sys.argv[4] == "true"
|
||||
account_local_proxy_secret_action = sys.argv[5]
|
||||
paths = sys.argv[6:]
|
||||
def text(path):
|
||||
try:
|
||||
return open(path, encoding="utf-8").read()
|
||||
except FileNotFoundError:
|
||||
return ""
|
||||
payload = {
|
||||
"ok": client_rc == 0 and server_rc == 0,
|
||||
"ok": client_rc == 0 and server_rc == 0 and account_local_proxy_secret_rc == 0,
|
||||
"target": "${target.id}",
|
||||
"namespace": "${target.namespace}",
|
||||
"namespaceExistsBeforeDryRun": namespace_exists,
|
||||
@@ -151,6 +202,13 @@ payload = {
|
||||
"stdout": text(paths[2])[-4000:],
|
||||
"stderr": text(paths[3])[-4000:],
|
||||
},
|
||||
"accountLocalProxy": ${accountLocalProxySecretSummary},
|
||||
"accountLocalProxySecretDryRun": {
|
||||
"exitCode": account_local_proxy_secret_rc,
|
||||
"action": account_local_proxy_secret_action,
|
||||
"stdout": text(paths[4])[-4000:],
|
||||
"stderr": text(paths[5])[-4000:],
|
||||
},
|
||||
}
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
sys.exit(0 if payload["ok"] else 1)
|
||||
@@ -165,6 +223,7 @@ export function applyScript(
|
||||
secretMaterial: ExternalActiveSecretMaterial | null,
|
||||
publicExposureSecretMaterial: PublicExposureSecretMaterial | null,
|
||||
egressProxySecretMaterial: EgressProxySecretMaterial | null,
|
||||
accountLocalProxySecretMaterial: AccountLocalProxySecretMaterial | null,
|
||||
): string {
|
||||
const encoded = Buffer.from(yaml, "utf8").toString("base64");
|
||||
const cleanupPlan = managedResourceCleanupPlan(sub2api, target);
|
||||
@@ -173,6 +232,8 @@ export function applyScript(
|
||||
const publicExposureSecretKey = publicExposureSecretMaterial?.secretKey ?? "frpc.toml";
|
||||
const egressProxySecretName = egressProxySecretMaterial?.secretName ?? sub2api.defaults.cleanup.egressProxy.secretName;
|
||||
const egressProxySecretKey = egressProxySecretMaterial?.secretKey ?? "config.json";
|
||||
const accountLocalProxySecretName = accountLocalProxySecretMaterial?.secretName ?? "sub2api-account-local-proxy-config";
|
||||
const accountLocalProxySecretKey = accountLocalProxySecretMaterial?.secretKey ?? "config.json";
|
||||
if (target.databaseMode === "external-active" && secretMaterial === null) throw new Error("external-active apply requires secret material");
|
||||
const externalSecretFiles = secretMaterial === null
|
||||
? ""
|
||||
@@ -222,6 +283,22 @@ export function applyScript(
|
||||
noProxy: egressProxySecretMaterial.noProxy,
|
||||
valuesPrinted: false,
|
||||
}))})`;
|
||||
const accountLocalProxySecretFile = accountLocalProxySecretMaterial === null
|
||||
? ""
|
||||
: ` printf '%s' '${Buffer.from(accountLocalProxySecretMaterial.configJson, "utf8").toString("base64")}' | base64 -d >"$tmp/account-local-proxy-config.json"`;
|
||||
const accountLocalProxySecretSummary = accountLocalProxySecretMaterial === null
|
||||
? "None"
|
||||
: `json.loads(${JSON.stringify(JSON.stringify({
|
||||
sourceRef: accountLocalProxySecretMaterial.sourceRef,
|
||||
sourcePath: accountLocalProxySecretMaterial.sourcePath,
|
||||
secretName: accountLocalProxySecretMaterial.secretName,
|
||||
secretKey: accountLocalProxySecretMaterial.secretKey,
|
||||
fingerprint: accountLocalProxySecretMaterial.fingerprint,
|
||||
sourceBytes: accountLocalProxySecretMaterial.sourceBytes,
|
||||
selectedOutbound: accountLocalProxySecretMaterial.selectedOutbound,
|
||||
proxyUrl: accountLocalProxySecretMaterial.proxyUrl,
|
||||
valuesPrinted: false,
|
||||
}))})`;
|
||||
return `
|
||||
set -u
|
||||
tmp="$(mktemp -d)"
|
||||
@@ -236,6 +313,8 @@ exposure_secret_out="$tmp/exposure-secret.out"
|
||||
exposure_secret_err="$tmp/exposure-secret.err"
|
||||
egress_secret_out="$tmp/egress-secret.out"
|
||||
egress_secret_err="$tmp/egress-secret.err"
|
||||
account_local_proxy_secret_out="$tmp/account-local-proxy-secret.out"
|
||||
account_local_proxy_secret_err="$tmp/account-local-proxy-secret.err"
|
||||
apply_out="$tmp/apply.out"
|
||||
apply_err="$tmp/apply.err"
|
||||
cleanup_out="$tmp/cleanup.out"
|
||||
@@ -248,6 +327,8 @@ exposure_secret_action="not-enabled"
|
||||
exposure_secret_rc=0
|
||||
egress_secret_action="not-enabled"
|
||||
egress_secret_rc=0
|
||||
account_local_proxy_secret_action="not-enabled"
|
||||
account_local_proxy_secret_rc=0
|
||||
if [ "$ns_rc" -eq 0 ]; then
|
||||
if [ "${target.databaseMode}" = "external-pending" ]; then
|
||||
secret_action="external-pending-not-managed"
|
||||
@@ -307,14 +388,25 @@ ${egressProxySecretFile}
|
||||
: >"$egress_secret_out"
|
||||
: >"$egress_secret_err"
|
||||
fi
|
||||
if [ "${accountLocalProxySecretMaterial === null ? "false" : "true"}" = "true" ]; then
|
||||
${accountLocalProxySecretFile}
|
||||
kubectl -n ${target.namespace} create secret generic ${accountLocalProxySecretName} \\
|
||||
--from-file=${accountLocalProxySecretKey}="$tmp/account-local-proxy-config.json" \\
|
||||
--dry-run=client -o yaml | kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f - >"$account_local_proxy_secret_out" 2>"$account_local_proxy_secret_err"
|
||||
account_local_proxy_secret_rc=$?
|
||||
account_local_proxy_secret_action="synced"
|
||||
else
|
||||
: >"$account_local_proxy_secret_out"
|
||||
: >"$account_local_proxy_secret_err"
|
||||
fi
|
||||
fi
|
||||
apply_rc=1
|
||||
if [ "$ns_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ] && [ "$exposure_secret_rc" -eq 0 ] && [ "$egress_secret_rc" -eq 0 ]; then
|
||||
if [ "$ns_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ] && [ "$exposure_secret_rc" -eq 0 ] && [ "$egress_secret_rc" -eq 0 ] && [ "$account_local_proxy_secret_rc" -eq 0 ]; then
|
||||
kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >"$apply_out" 2>"$apply_err"
|
||||
apply_rc=$?
|
||||
else
|
||||
: >"$apply_out"
|
||||
printf '%s\\n' 'skipped because namespace, app secret, public exposure secret, or egress proxy secret step failed' >"$apply_err"
|
||||
printf '%s\\n' 'skipped because namespace, app secret, public exposure secret, egress proxy secret, or account-local proxy secret step failed' >"$apply_err"
|
||||
fi
|
||||
cleanup_rc=0
|
||||
if [ "$apply_rc" -eq 0 ]; then
|
||||
@@ -414,19 +506,21 @@ else
|
||||
printf '%s\\n' 'skipped because apply failed' >"$cleanup_err"
|
||||
cleanup_rc=1
|
||||
fi
|
||||
python3 - "$ns_rc" "$secret_rc" "$exposure_secret_rc" "$egress_secret_rc" "$apply_rc" "$cleanup_rc" "$secret_action" "$exposure_secret_action" "$egress_secret_action" "$ns_out" "$ns_err" "$secret_out" "$secret_err" "$exposure_secret_out" "$exposure_secret_err" "$egress_secret_out" "$egress_secret_err" "$apply_out" "$apply_err" "$cleanup_out" "$cleanup_err" <<'PY'
|
||||
python3 - "$ns_rc" "$secret_rc" "$exposure_secret_rc" "$egress_secret_rc" "$account_local_proxy_secret_rc" "$apply_rc" "$cleanup_rc" "$secret_action" "$exposure_secret_action" "$egress_secret_action" "$account_local_proxy_secret_action" "$ns_out" "$ns_err" "$secret_out" "$secret_err" "$exposure_secret_out" "$exposure_secret_err" "$egress_secret_out" "$egress_secret_err" "$account_local_proxy_secret_out" "$account_local_proxy_secret_err" "$apply_out" "$apply_err" "$cleanup_out" "$cleanup_err" <<'PY'
|
||||
import json
|
||||
import sys
|
||||
ns_rc = int(sys.argv[1])
|
||||
secret_rc = int(sys.argv[2])
|
||||
exposure_secret_rc = int(sys.argv[3])
|
||||
egress_secret_rc = int(sys.argv[4])
|
||||
apply_rc = int(sys.argv[5])
|
||||
cleanup_rc = int(sys.argv[6])
|
||||
secret_action = sys.argv[7]
|
||||
exposure_secret_action = sys.argv[8]
|
||||
egress_secret_action = sys.argv[9]
|
||||
paths = sys.argv[10:]
|
||||
account_local_proxy_secret_rc = int(sys.argv[5])
|
||||
apply_rc = int(sys.argv[6])
|
||||
cleanup_rc = int(sys.argv[7])
|
||||
secret_action = sys.argv[8]
|
||||
exposure_secret_action = sys.argv[9]
|
||||
egress_secret_action = sys.argv[10]
|
||||
account_local_proxy_secret_action = sys.argv[11]
|
||||
paths = sys.argv[12:]
|
||||
def text(path):
|
||||
try:
|
||||
return open(path, encoding="utf-8").read()
|
||||
@@ -438,7 +532,7 @@ def parsed(path):
|
||||
except Exception:
|
||||
return None
|
||||
payload = {
|
||||
"ok": ns_rc == 0 and secret_rc == 0 and exposure_secret_rc == 0 and egress_secret_rc == 0 and apply_rc == 0 and cleanup_rc == 0,
|
||||
"ok": ns_rc == 0 and secret_rc == 0 and exposure_secret_rc == 0 and egress_secret_rc == 0 and account_local_proxy_secret_rc == 0 and apply_rc == 0 and cleanup_rc == 0,
|
||||
"target": "${target.id}",
|
||||
"namespace": "${target.namespace}",
|
||||
"databaseMode": "${target.databaseMode}",
|
||||
@@ -454,13 +548,15 @@ payload = {
|
||||
},
|
||||
"publicExposure": ${exposureSecretSummary},
|
||||
"egressProxy": ${egressProxySecretSummary},
|
||||
"accountLocalProxy": ${accountLocalProxySecretSummary},
|
||||
"steps": {
|
||||
"namespace": {"exitCode": ns_rc, "stdout": text(paths[0])[-4000:], "stderr": text(paths[1])[-4000:]},
|
||||
"secret": {"exitCode": secret_rc, "stdout": text(paths[2])[-4000:], "stderr": text(paths[3])[-4000:]},
|
||||
"publicExposureSecret": {"exitCode": exposure_secret_rc, "action": exposure_secret_action, "stdout": text(paths[4])[-4000:], "stderr": text(paths[5])[-4000:]},
|
||||
"egressProxySecret": {"exitCode": egress_secret_rc, "action": egress_secret_action, "stdout": text(paths[6])[-4000:], "stderr": text(paths[7])[-4000:]},
|
||||
"apply": {"exitCode": apply_rc, "stdout": text(paths[8])[-8000:], "stderr": text(paths[9])[-4000:]},
|
||||
"cleanup": {"exitCode": cleanup_rc, "summary": parsed(paths[10]), "stdout": text(paths[10])[-8000:], "stderr": text(paths[11])[-4000:]},
|
||||
"accountLocalProxySecret": {"exitCode": account_local_proxy_secret_rc, "action": account_local_proxy_secret_action, "stdout": text(paths[8])[-4000:], "stderr": text(paths[9])[-4000:]},
|
||||
"apply": {"exitCode": apply_rc, "stdout": text(paths[10])[-8000:], "stderr": text(paths[11])[-4000:]},
|
||||
"cleanup": {"exitCode": cleanup_rc, "summary": parsed(paths[12]), "stdout": text(paths[12])[-8000:], "stderr": text(paths[13])[-4000:]},
|
||||
},
|
||||
}
|
||||
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
||||
|
||||
Reference in New Issue
Block a user