{{- /*
Helm post-install Job that copies the CNPG-emitted `gitea-pg-app` Secret
into `gitea-database-secret` so Harbor core's env binding finds the
expected `password` key.

WHY A JOB INSTEAD OF REFLECTOR
--------------------------------
The earlier Reflector-based path relied on the Reflector daemon refiring
when CNPG creates `gitea-pg-app` AFTER `gitea-database-secret` was
processed. In practice (caught live on otech30) Reflector logs:
    "Could not update harbor/gitea-database-secret —
     Source harbor/gitea-pg-app could not be found"
once at install time and never retries. Even with `auto-enabled: true`
on the source's inherited annotations, Reflector's auto-reflect path
copies the SOURCE name (gitea-pg-app) — it does NOT update the
explicit `reflects:` destination (gitea-database-secret). So the
destination stays empty and gitea stalls forever with
`couldn't find key password in Secret harbor/gitea-database-secret`.

A post-install Job avoids the watcher race entirely:
  1. Polls until CNPG has provisioned gitea-pg-app
  2. Reads `password` from it
  3. kubectl-applies gitea-database-secret with both the original
     `password` key (used by gitea) and `HARBOR_DATABASE_PASSWORD`
     (used by upstream Harbor charts that bind via envFrom)
  4. 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.

WHY NOT INIT-CONTAINER ON HARBOR-CORE
--------------------------------------
Harbor's upstream chart's deployment template doesn't expose an
init-containers extension point per service, and patching it via a
post-render kustomize patch would diverge from the upstream chart's
contract. A separate Helm hook keeps the override surgical.

NB: still keep `templates/database-secret.yaml` as a pre-install
placeholder (so `kubectl get secret gitea-database-secret -n harbor`
returns Found before this Job populates it; some tooling assumes
the Secret is named for the lifetime of the release). Reflector
annotations are kept harmless — they just become a redundant
no-op once this Job has populated the Secret.
*/}}
{{- $ns := .Values.postgres.cluster.namespace | default .Release.Namespace }}
{{- $clusterName := .Values.postgres.cluster.name | default "gitea-pg" }}
apiVersion: batch/v1
kind: Job
metadata:
  name: gitea-database-secret-sync
  namespace: {{ $ns }}
  labels:
    catalyst.openova.io/blueprint: bp-gitea
    catalyst.openova.io/component: gitea-database-secret-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: gitea-database-secret-sync
    spec:
      restartPolicy: OnFailure
      serviceAccountName: gitea-database-secret-sync
      imagePullSecrets:
        - name: ghcr-pull
      containers:
        - name: sync
          # curlimages/curl is a tiny alpine-based image with curl + sh,
          # avoiding kubectl entirely. We talk to the k8s API directly via
          # the in-pod ServiceAccount token. Earlier attempts:
          #   - bitnami/kubectl:1.31.4 — bitnami moved to sha256-only tags
          #   - rancher/kubectl:v1.34.6 — distroless, no /bin/sh; container
          #     can't run our inline shell script.
          # Fix #163 (2026-05-11, MIRROR-EVERYTHING): explicit
          # harbor.openova.io/proxy-dockerhub prefix per CLAUDE.md
          # inviolable rule.
          image: harbor.openova.io/proxy-dockerhub/curlimages/curl:8.10.1
          imagePullPolicy: IfNotPresent
          env:
            - name: SOURCE_SECRET
              value: {{ printf "%s-app" $clusterName | quote }}
            - name: DEST_SECRET
              value: "gitea-database-secret"
            - name: NAMESPACE
              value: {{ $ns | quote }}
          command:
            - /bin/sh
            - -c
            - |
              set -eu
              # In-cluster k8s API access via projected ServiceAccount token.
              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.
              # k8s apiserver doesn't pretty-print but other proxies might;
              # tr -d '\n' makes the next grep robust.
              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
              # PATCH the destination Secret merging both keys. Strategic
              # merge tolerates the existing pre-install hook annotations.
              PATCH=$(printf '{"data":{"password":"%s"}}' "${PWD_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} from ${SOURCE_SECRET}"
          resources:
            requests:
              cpu: 10m
              memory: 32Mi
            limits:
              cpu: 100m
              memory: 64Mi
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: gitea-database-secret-sync
  namespace: {{ $ns }}
  labels:
    catalyst.openova.io/blueprint: bp-gitea
    catalyst.openova.io/component: gitea-database-secret-sync
  annotations:
    "helm.sh/hook": "post-install,post-upgrade"
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": "before-hook-creation"
imagePullSecrets:
  - name: ghcr-pull
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: gitea-database-secret-sync
  namespace: {{ $ns }}
  labels:
    catalyst.openova.io/blueprint: bp-gitea
    catalyst.openova.io/component: gitea-database-secret-sync
  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: gitea-database-secret-sync
  namespace: {{ $ns }}
  labels:
    catalyst.openova.io/blueprint: bp-gitea
    catalyst.openova.io/component: gitea-database-secret-sync
  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: gitea-database-secret-sync
subjects:
  - kind: ServiceAccount
    name: gitea-database-secret-sync
    namespace: {{ $ns }}
