{{- if .Values.cnpg.enabled }}
{{- /*
Helm post-install/post-upgrade Job that composes the canonical SQL_DSN
string from the CNPG-emitted `<cluster>-app` Secret (which holds the
rotating Postgres password) and PATCHes it into the chart-managed
DSN-placeholder Secret `<release>-newapi-db-dsn`.

WHY A JOB INSTEAD OF HELM `lookup`
----------------------------------
Pre-#1834 the chart rendered the DSN at template time via `lookup`
against `<cluster>-app`. On every freshly-franchised Sovereign CNPG
materialises that source Secret only AFTER bp-newapi's HelmRelease
applies, so the first render's lookup returns nil and the Secret was
committed with an empty password — `postgres://newapi:@.../`. The
chart annotated the rendered Secret `helm.sh/resource-policy: keep`,
so Flux never overwrote the empty bytes on subsequent reconciles even
after CNPG had populated the source. Result on t32: newapi Pod in
21x CrashLoopBackOff with `password authentication failed for user
"newapi"`. Probe to `newapi.t32.omani.works` returned envoy 503
"no healthy upstream".

A post-install Job avoids the lookup race entirely:
  1. Polls until CNPG has provisioned `<cluster>-app`.
  2. Reads `password` from it.
  3. Composes SQL_DSN = `postgres://<user>:<password>@<host>:5432/<db>?sslmode=<mode>`
  4. PATCHes `<release>-newapi-db-dsn` with the SQL_DSN bytes.
  5. Exits 0; Helm marks the post-install hook complete.

The Job is idempotent — re-running it overwrites with the same data,
which CNPG never rotates without operator action.

Pattern lifted from platform/gitea/chart/templates/database-secret-sync
-job.yaml (canonical seam — issue #830 Bug 2 + feedback_passwords.md
+ ADR-0001 §11.3 anti-duplication) + platform/wordpress-tenant/chart/
templates/database-secret-sync-job.yaml (issue #1786).

Fix #1834 (TBD-A39, 2026-05-19): D34 newapi LLM gateway crashed in 21x
CrashLoopBackOff on t32 because the chart's DSN secret carried an
empty password (Helm `lookup` race with CNPG's first-install). Replaced
the lookup pattern with this sync-job.

URL-ENCODING NOTE
-----------------
CNPG's auto-generated password is alphanumeric (no special chars), so
URL-encoding it in the DSN is a no-op. If the operator overrides
.Values.cnpg.cluster.owner to a name with shell-special characters or
sets a custom password with `+`, `@`, `:`, etc. via a forked path, this
script will not encode them. Within scope of #943 (CNPG-owned password
only) URL-encoding is unnecessary; deferred to a follow-up if the
operator-supplied-password path resurfaces.
*/}}
{{- $clusterName := printf "%s-newapi-pg" (include "bp-newapi.fullname" .) }}
{{- $cnpgSecretName := printf "%s-app" $clusterName }}
{{- $dsnSecretName := default (printf "%s-newapi-db-dsn" (include "bp-newapi.fullname" .)) .Values.cnpg.dsnSecretName }}
{{- $pgUser := .Values.cnpg.cluster.owner | default "newapi" }}
{{- $pgHost := printf "%s-rw.%s.svc.cluster.local" $clusterName .Release.Namespace }}
{{- $database := .Values.cnpg.database | default "newapi" }}
{{- $sslMode := .Values.cnpg.sslMode | default "require" }}
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-newapi.labels" . | nindent 4 }}
    catalyst.openova.io/component: newapi-db-dsn-sync
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "5"
    "helm.sh/hook-delete-policy": "before-hook-creation,hook-succeeded"
spec:
  backoffLimit: 6
  ttlSecondsAfterFinished: 300
  template:
    metadata:
      labels:
        app.kubernetes.io/name: newapi-db-dsn-sync
        catalyst.openova.io/blueprint: bp-newapi
    spec:
      restartPolicy: OnFailure
      serviceAccountName: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: sync
          # Fix #163 (2026-05-11, MIRROR-EVERYTHING): explicit
          # harbor.openova.io/proxy-dockerhub prefix per CLAUDE.md
          # inviolable rule. curlimages/curl is the canonical image
          # used by platform/gitea + platform/wordpress-tenant sync
          # jobs — no kubectl required (apiserver via in-pod token).
          image: harbor.openova.io/proxy-dockerhub/curlimages/curl:8.10.1
          imagePullPolicy: IfNotPresent
          env:
            - name: SOURCE_SECRET
              value: {{ $cnpgSecretName | quote }}
            - name: DEST_SECRET
              value: {{ $dsnSecretName | quote }}
            - name: NAMESPACE
              value: {{ .Release.Namespace | quote }}
            - name: PG_USER
              value: {{ $pgUser | quote }}
            - name: PG_HOST
              value: {{ $pgHost | quote }}
            - name: PG_DB
              value: {{ $database | quote }}
            - name: PG_SSLMODE
              value: {{ $sslMode | quote }}
          command:
            - /bin/sh
            - -c
            - |
              set -eu
              APISERVER="https://kubernetes.default.svc"
              TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
              CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
              echo "Waiting for ${SOURCE_SECRET} in ${NAMESPACE}..."
              for i in $(seq 1 120); do
                code=$(curl -s -o /tmp/src.json -w '%{http_code}' --cacert "${CA}" \
                  -H "Authorization: Bearer ${TOKEN}" \
                  "${APISERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${SOURCE_SECRET}")
                if [ "${code}" = "200" ]; then
                  echo "Found ${SOURCE_SECRET}"; break
                fi
                if [ "$i" = "120" ]; then
                  echo "Timeout: ${SOURCE_SECRET} not present after 10 min" >&2
                  exit 1
                fi
                sleep 5
              done
              # Extract base64 password from possibly multi-line JSON.
              PWD_B64=$(tr -d '\n' < /tmp/src.json | grep -oE '"password"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\([^"]*\)"$/\1/')
              if [ -z "${PWD_B64}" ]; then
                echo "Source ${SOURCE_SECRET} has empty password" >&2
                exit 1
              fi
              PWD=$(echo -n "${PWD_B64}" | base64 -d)
              if [ -z "${PWD}" ]; then
                echo "Source ${SOURCE_SECRET} decoded to empty password" >&2
                exit 1
              fi
              DSN="postgres://${PG_USER}:${PWD}@${PG_HOST}:5432/${PG_DB}?sslmode=${PG_SSLMODE}"
              DSN_B64=$(echo -n "${DSN}" | base64 -w 0)
              PATCH=$(printf '{"data":{"SQL_DSN":"%s"}}' "${DSN_B64}")
              code=$(curl -s -o /tmp/patch.json -w '%{http_code}' --cacert "${CA}" \
                -H "Authorization: Bearer ${TOKEN}" \
                -H "Content-Type: application/strategic-merge-patch+json" \
                -X PATCH --data "${PATCH}" \
                "${APISERVER}/api/v1/namespaces/${NAMESPACE}/secrets/${DEST_SECRET}")
              if [ "${code}" != "200" ] && [ "${code}" != "201" ]; then
                echo "PATCH ${DEST_SECRET} returned HTTP ${code}:" >&2
                cat /tmp/patch.json >&2
                exit 1
              fi
              echo "Synced ${DEST_SECRET} SQL_DSN from ${SOURCE_SECRET} password"
          resources:
            requests:
              cpu: 10m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 64Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-newapi.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": "before-hook-creation"
  {{- with .Values.imagePullSecrets }}
imagePullSecrets:
  {{- toYaml . | nindent 2 }}
  {{- end }}
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-newapi.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": "before-hook-creation"
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "create", "update", "patch", "apply"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
  namespace: {{ .Release.Namespace }}
  labels:
    {{- include "bp-newapi.labels" . | nindent 4 }}
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": "before-hook-creation"
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
subjects:
  - kind: ServiceAccount
    name: {{ include "bp-newapi.fullname" . }}-db-dsn-sync
    namespace: {{ .Release.Namespace }}
{{- end }}
