Files
template-web-backend-python/helm/APP_NAME/templates/job-alembic-upgrade.yaml
iilc 13b9fae61c
Some checks failed
__APP_NAME__ — build & deploy / Calidad + build + push (push) Failing after 14s
__APP_NAME__ — build & deploy / Helm upgrade --install (push) Has been skipped
chart 0.3.0: AddonClaim FUERA del chart Helm (ADR-066 del IDP)
Bug en chart 0.2.x: AddonClaim era Helm hook pre-install,pre-upgrade
weight -10 SIN hook-delete-policy. El default Helm 3 es
before-hook-creation → borra el claim en cada upgrade → finalizer
cleanup-logical rotaba credenciales + atoraba release en pending-upgrade.

Cambios:
- helm/APP_NAME/templates/addonclaim.yaml eliminado.
- manifests/addonclaim.yaml creado con placeholders sed.
- scripts/pre-helm-detach.sh idempotente para migracion v1.0.0 → 1.1.0.
- .gitea/workflows/APP_NAME-build.yaml: 2 nuevos steps detach + apply
  AddonClaim entre kubeconfig y helm upgrade.
- Chart.yaml bump 0.2.0 → 0.3.0.
- README reescrito (3 pasos workflow + tabla hooks sin AddonClaim +
  seccion Por que FUERA del chart + opt-out actualizado).
- Comentarios stale del AddonClaim como hook -10 limpiados en
  networkpolicy.yaml, role-db-secret-reader.yaml, job-alembic-upgrade.yaml.

ADR-066 del IDP documenta la decision arquitectural completa.
2026-05-25 14:28:17 -05:00

122 lines
5.0 KiB
YAML

{{- 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 ya fue aplicado por el workflow del tenant ANTES del
# `helm upgrade --install` (ADR-066, vive FUERA del chart en
# manifests/addonclaim.yaml). 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 -u: undefined vars matan el script.
# set -e NO: queremos que curl fail (DNS transitorio, API server
# caido por instante) NO mate el loop — el retry lo absorbe.
set -u
# 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
# || echo "000": si curl falla (exit 6 DNS, etc.) capturamos
# un codigo HTTP "000" para que el retry siga corriendo.
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" \
|| echo "000")
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 }}