diff --git a/scripts/src/frontend-auth.ts b/scripts/src/frontend-auth.ts index 647e8857..153a2b33 100644 --- a/scripts/src/frontend-auth.ts +++ b/scripts/src/frontend-auth.ts @@ -207,8 +207,17 @@ function readAuthMaterial(auth: RuntimeAuthConfig): AuthMaterial { } function applyScript(auth: RuntimeAuthConfig, material: AuthMaterial): string { - const manifest = renderManifest(auth, material); - const manifestB64 = Buffer.from(manifest, "utf8").toString("base64"); + if (material.username === null || material.password === null || material.fingerprint === null) throw new Error("auth material is incomplete"); + const configMapPatch = { + metadata: { annotations: authAnnotations(auth, material) }, + data: { [auth.target.usernameKey]: material.username }, + }; + const secretPatch = { + metadata: { annotations: authAnnotations(auth, material) }, + data: { [auth.target.passwordKey]: Buffer.from(material.password, "utf8").toString("base64") }, + }; + const configMapPatchB64 = Buffer.from(JSON.stringify(configMapPatch), "utf8").toString("base64"); + const secretPatchB64 = Buffer.from(JSON.stringify(secretPatch), "utf8").toString("base64"); const summaryB64 = Buffer.from(JSON.stringify({ namespace: auth.target.namespace, configMapName: auth.target.configMapName, @@ -224,21 +233,25 @@ function applyScript(auth: RuntimeAuthConfig, material: AuthMaterial): string { set -u tmp="$(mktemp -d)" trap 'rm -rf "$tmp"' EXIT -manifest="$tmp/frontend-auth.yaml" -printf '%s' '${manifestB64}' | base64 -d >"$manifest" -kubectl apply --server-side --force-conflicts --field-manager=${fieldManager} -f "$manifest" >"$tmp/apply.out" 2>"$tmp/apply.err" -apply_rc=$? -if [ "$apply_rc" -eq 0 ]; then +cm_patch="$tmp/configmap.patch.json" +secret_patch="$tmp/secret.patch.json" +printf '%s' '${configMapPatchB64}' | base64 -d >"$cm_patch" +printf '%s' '${secretPatchB64}' | base64 -d >"$secret_patch" +kubectl -n ${sh(auth.target.namespace)} patch configmap ${sh(auth.target.configMapName)} --type merge --patch-file "$cm_patch" >"$tmp/configmap.out" 2>"$tmp/configmap.err" +cm_rc=$? +kubectl -n ${sh(auth.target.namespace)} patch secret ${sh(auth.target.secretName)} --type merge --patch-file "$secret_patch" >"$tmp/secret.out" 2>"$tmp/secret.err" +secret_rc=$? +if [ "$cm_rc" -eq 0 ] && [ "$secret_rc" -eq 0 ]; then kubectl -n ${sh(auth.target.namespace)} rollout restart deployment/${sh(auth.target.rolloutDeployment)} >"$tmp/rollout.out" 2>"$tmp/rollout.err" rollout_rc=$? else : >"$tmp/rollout.out" - printf '%s\\n' 'skipped because apply failed' >"$tmp/rollout.err" + printf '%s\\n' 'skipped because configmap or secret patch failed' >"$tmp/rollout.err" rollout_rc=1 fi -python3 - "$apply_rc" "$rollout_rc" "$tmp/apply.out" "$tmp/apply.err" "$tmp/rollout.out" "$tmp/rollout.err" <<'PY' +python3 - "$cm_rc" "$secret_rc" "$rollout_rc" "$tmp/configmap.out" "$tmp/configmap.err" "$tmp/secret.out" "$tmp/secret.err" "$tmp/rollout.out" "$tmp/rollout.err" <<'PY' import base64, json, sys -apply_rc, rollout_rc = int(sys.argv[1]), int(sys.argv[2]) +cm_rc, secret_rc, rollout_rc = int(sys.argv[1]), int(sys.argv[2]), int(sys.argv[3]) def tail(path, limit=4000): try: return open(path, encoding="utf-8", errors="replace").read()[-limit:] @@ -246,11 +259,12 @@ def tail(path, limit=4000): return "" summary = json.loads(base64.b64decode("${summaryB64}").decode("utf-8")) payload = { - "ok": apply_rc == 0 and rollout_rc == 0, + "ok": cm_rc == 0 and secret_rc == 0 and rollout_rc == 0, **summary, "steps": { - "apply": {"exitCode": apply_rc, "stdoutTail": tail(sys.argv[3]), "stderrTail": tail(sys.argv[4])}, - "rolloutRestart": {"exitCode": rollout_rc, "stdoutTail": tail(sys.argv[5]), "stderrTail": tail(sys.argv[6])}, + "configMapPatch": {"exitCode": cm_rc, "stdoutTail": tail(sys.argv[4]), "stderrTail": tail(sys.argv[5])}, + "secretPatch": {"exitCode": secret_rc, "stdoutTail": tail(sys.argv[6]), "stderrTail": tail(sys.argv[7])}, + "rolloutRestart": {"exitCode": rollout_rc, "stdoutTail": tail(sys.argv[8]), "stderrTail": tail(sys.argv[9])}, }, "valuesPrinted": False, } @@ -331,33 +345,12 @@ PY `; } -function renderManifest(auth: RuntimeAuthConfig, material: AuthMaterial): string { - if (material.username === null || material.password === null || material.fingerprint === null) throw new Error("auth material is incomplete"); - const annotations = [ - ` unidesk.ai/source-ref: ${yaml(auth.source.path)}`, - ` unidesk.ai/frontend-auth-fingerprint: ${yaml(material.fingerprint)}`, - ].join("\n"); - return `apiVersion: v1 -kind: ConfigMap -metadata: - name: ${auth.target.configMapName} - namespace: ${auth.target.namespace} - annotations: -${annotations} -data: - ${auth.target.usernameKey}: ${yaml(material.username)} ---- -apiVersion: v1 -kind: Secret -metadata: - name: ${auth.target.secretName} - namespace: ${auth.target.namespace} - annotations: -${annotations} -type: Opaque -stringData: - ${auth.target.passwordKey}: ${yaml(material.password)} -`; +function authAnnotations(auth: RuntimeAuthConfig, material: AuthMaterial): Record { + if (material.fingerprint === null) throw new Error("auth material fingerprint is missing"); + return { + "unidesk.ai/source-ref": auth.source.path, + "unidesk.ai/frontend-auth-fingerprint": material.fingerprint, + }; } function parseAction(value: string): Action { @@ -462,7 +455,8 @@ function renderApply(result: Record): RenderedCliResult { `mode: ${str(result.mode)} mutation: ${str(result.mutation)} ok: ${bool(result.ok)}`, ...table(["STEP", "OK", "DETAIL"], [ ["local-source", bool(asRecord(result.localSource, "localSource").fingerprint !== undefined && asRecord(result.localSource, "localSource").fingerprint !== null), str(asRecord(result.localSource, "localSource").sourceRef)], - ["apply", bool(asRecord(steps.apply, "steps.apply").exitCode === 0), compact(str(asRecord(steps.apply, "steps.apply").stderrTail, str(asRecord(steps.apply, "steps.apply").stdoutTail)))], + ["configmap-patch", bool(asRecord(steps.configMapPatch, "steps.configMapPatch").exitCode === 0), compact(str(asRecord(steps.configMapPatch, "steps.configMapPatch").stderrTail, str(asRecord(steps.configMapPatch, "steps.configMapPatch").stdoutTail)))], + ["secret-patch", bool(asRecord(steps.secretPatch, "steps.secretPatch").exitCode === 0), compact(str(asRecord(steps.secretPatch, "steps.secretPatch").stderrTail, str(asRecord(steps.secretPatch, "steps.secretPatch").stdoutTail)))], ["rollout-restart", bool(asRecord(steps.rolloutRestart, "steps.rolloutRestart").exitCode === 0), compact(str(asRecord(steps.rolloutRestart, "steps.rolloutRestart").stdoutTail, str(asRecord(steps.rolloutRestart, "steps.rolloutRestart").stderrTail)))], ["runtime-status", bool(asRecord(result.runtime, "runtime").ok), str(asRecord(result.runtime, "runtime").observedFingerprint)], ]),