# Copies the CNPG-generated temporal-db-role Secret from the ddb namespace # into the temporal namespace, as a plain (non-SOPS) k8s Secret with the same # keys. Kubernetes Secrets are strictly namespace-scoped - a Deployment in # `temporal` cannot reference a Secret living in `ddb` via secretKeyRef, and # temporal-values.yaml's server.config.persistence.*.sql.existingSecret: # temporal-db-role expects to find it in ITS OWN namespace (temporal). # # Deliberately a standalone directory (no kustomization.yaml) applied as its # own small Application - avoids the k8s/applications/temporal/kustomization.yaml # `namespace: temporal` transformer, which would silently force-rewrite this # Job's ddb-scoped RoleBinding back to temporal (same class of bug fixed # earlier in k8s/security/iam/kustomization.yaml - see that file's comments). # # PostSync (not PreSync!) + BeforeHookCreation: reruns on every ArgoCD sync # of this app, re-copying the password if CNPG ever rotates it. # # IMPORTANT: this MUST be PostSync, not PreSync. The ServiceAccount/ # ClusterRole/RoleBindings below are plain (non-hook) resources - ArgoCD # creates those during the normal "Sync" phase, which happens AFTER PreSync # hooks run. A PreSync-hooked Job here would try to start before its own # ServiceAccount exists (chicken-and-egg deadlock: confirmed live - the Job # sat 'Running' for 14 minutes, unable to create any pod at all, event log # showed "serviceaccount temporal/temporal-db-secret-sync not found" on # every attempt). PostSync runs after this app's own normal resources are # already applied, and this whole app (sync-wave 7) still fully completes # before the `temporal` Application (sync-wave 8) begins, so the ordering # guarantee we actually need (secret exists before Temporal's pods start) # is preserved regardless of PreSync vs PostSync here. apiVersion: v1 kind: ServiceAccount metadata: name: temporal-db-secret-sync namespace: temporal --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: temporal-db-secret-sync rules: - apiGroups: [""] resources: ["secrets"] verbs: ["get", "list", "create", "update", "patch"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: temporal-db-secret-sync namespace: ddb subjects: - kind: ServiceAccount name: temporal-db-secret-sync namespace: temporal roleRef: kind: ClusterRole name: temporal-db-secret-sync apiGroup: rbac.authorization.k8s.io --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: temporal-db-secret-sync namespace: temporal subjects: - kind: ServiceAccount name: temporal-db-secret-sync namespace: temporal roleRef: kind: ClusterRole name: temporal-db-secret-sync apiGroup: rbac.authorization.k8s.io --- apiVersion: batch/v1 kind: Job metadata: name: temporal-db-secret-sync namespace: temporal annotations: argocd.argoproj.io/hook: PostSync argocd.argoproj.io/hook-delete-policy: BeforeHookCreation spec: ttlSecondsAfterFinished: 600 backoffLimit: 5 template: spec: serviceAccountName: temporal-db-secret-sync restartPolicy: Never securityContext: runAsNonRoot: true runAsUser: 1000 seccompProfile: type: RuntimeDefault containers: - name: copy image: bitnami/kubectl:1.30 securityContext: allowPrivilegeEscalation: false capabilities: drop: ["ALL"] command: - /bin/sh - -c - | set -e echo "waiting for ddb/temporal-db-role..." until kubectl -n ddb get secret temporal-db-role >/dev/null 2>&1; do echo " not ready yet, retrying..." sleep 5 done USERNAME=$(kubectl -n ddb get secret temporal-db-role -o jsonpath='{.data.username}' | base64 -d) PASSWORD=$(kubectl -n ddb get secret temporal-db-role -o jsonpath='{.data.password}' | base64 -d) kubectl -n temporal create secret generic temporal-db-role \ --from-literal=username="$USERNAME" \ --from-literal=password="$PASSWORD" \ --dry-run=client -o yaml | kubectl apply -f - echo "synced temporal-db-role -> temporal namespace"