{{- if and .Values.alembic.enabled .Values.database.enabled }} # Job pre-upgrade que corre Alembic antes de rolar el Deployment. # - backoffLimit: 0 -> si falla, NO reintenta; Helm aborta el upgrade. # - hook-delete-policy hook-succeeded -> solo se borra al exito; en fallo queda # visible para `kubectl logs job/...` y diagnostico. # # InitContainer `wait-for-secret`: # - El AddonClaim (hook weight -10) ya fue aplicado, pero addon-provisioner es # timer-based (RECONCILE_INTERVAL 300s, initial_delay 60s) y materializa el # Secret `{{ .Values.database.secretName }}` de forma asincrona. # - El InitContainer hace busy-wait hasta que el Secret existe — absorbe la # latencia del reconcile dentro del pod, sin tocar el operator ni # bajar RECONCILE_INTERVAL global. # - Loop: 48 iteraciones x 5s = 240s — encaja dentro del helm timeout 7m. apiVersion: batch/v1 kind: Job metadata: name: {{ .Release.Name }}-alembic-{{ .Release.Revision }} labels: {{- include "app.labels" . | nindent 4 }} annotations: "helm.sh/hook": pre-upgrade,pre-install "helm.sh/hook-weight": "0" "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: backoffLimit: 0 ttlSecondsAfterFinished: 600 template: metadata: labels: {{- include "app.labels" . | nindent 8 }} spec: restartPolicy: Never serviceAccountName: {{ .Release.Name }} imagePullSecrets: - name: {{ .Values.image.pullSecret }} securityContext: runAsNonRoot: true runAsUser: 1001 initContainers: - name: wait-for-secret # curl + REST API K8s con SA token. Evita dependencia de un binario # kubectl con tags que cambian (bitnami/kubectl retiro los tags # semanticos minor 2026-05). curlimages/curl es Docker Official. image: curlimages/curl:8.20.0 command: - sh - -c - | set -eu # Espera hasta que addon-provisioner publique el Secret tras # reconciliar el AddonClaim (hook weight -10). 48 x 5s = 240s. SECRET="{{ .Values.database.secretName }}" TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt i=1 while [ "$i" -le 48 ]; do HTTP=$(curl -s -o /dev/null -w '%{http_code}' \ --cacert "$CACERT" \ -H "Authorization: Bearer $TOKEN" \ "https://kubernetes.default.svc/api/v1/namespaces/$NAMESPACE/secrets/$SECRET") if [ "$HTTP" = "200" ]; then echo "Secret $SECRET encontrado en intento $i" exit 0 fi echo "[$i/48] Secret $SECRET aun no publicado (HTTP=$HTTP), esperando 5s..." sleep 5 i=$((i + 1)) done echo "ERROR: Secret $SECRET no aparecio tras 240s — addon-provisioner stuck?" exit 1 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace resources: requests: {cpu: 10m, memory: 32Mi} limits: {cpu: 100m, memory: 64Mi} securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] containers: - name: alembic image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" command: {{ .Values.alembic.command | toJson }} envFrom: - secretRef: name: {{ .Values.database.secretName }} env: # Si el Secret expone MIGRATION_DATABASE_URL, lo usa; si no, env.py # cae a DATABASE_URL automaticamente (ver settings.migration_url). - name: DATABASE_URL valueFrom: secretKeyRef: name: {{ .Values.database.secretName }} key: DATABASE_URL - name: MIGRATION_DATABASE_URL valueFrom: secretKeyRef: name: {{ .Values.database.secretName }} key: MIGRATION_DATABASE_URL optional: true resources: {{- toYaml .Values.alembic.resources | nindent 12 }} securityContext: allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"] {{- end }}