From 13b9fae61c93f521c6979943f4db4c15a6b2fca0 Mon Sep 17 00:00:00 2001 From: iilc Date: Mon, 25 May 2026 14:28:17 -0500 Subject: [PATCH] chart 0.3.0: AddonClaim FUERA del chart Helm (ADR-066 del IDP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .gitea/workflows/APP_NAME-build.yaml | 36 +++++++- README.md | 84 +++++++++++++------ helm/APP_NAME/Chart.yaml | 10 ++- helm/APP_NAME/templates/addonclaim.yaml | 39 --------- .../templates/job-alembic-upgrade.yaml | 8 +- helm/APP_NAME/templates/networkpolicy.yaml | 5 +- .../templates/role-db-secret-reader.yaml | 4 +- manifests/addonclaim.yaml | 34 ++++++++ scripts/pre-helm-detach.sh | 65 ++++++++++++++ 9 files changed, 214 insertions(+), 71 deletions(-) delete mode 100644 helm/APP_NAME/templates/addonclaim.yaml create mode 100644 manifests/addonclaim.yaml create mode 100755 scripts/pre-helm-detach.sh diff --git a/.gitea/workflows/APP_NAME-build.yaml b/.gitea/workflows/APP_NAME-build.yaml index a21be44..4faf5d3 100644 --- a/.gitea/workflows/APP_NAME-build.yaml +++ b/.gitea/workflows/APP_NAME-build.yaml @@ -13,6 +13,8 @@ on: paths: - 'code/**' - 'helm/**' + - 'manifests/**' + - 'scripts/**' - '.gitea/workflows/__APP_NAME__-build.yaml' workflow_dispatch: {} @@ -125,6 +127,36 @@ jobs: echo "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config" chmod 600 "${HOME}/.kube/config" + # ── ADR-066: AddonClaim vive FUERA del chart Helm ─────────────────── + # El claim lo gestiona addon-provisioner via finalizer cleanup-logical; + # no debe acoplarse al lifecycle del release Helm para no rotar + # credenciales en cada upgrade. + + - name: De-adoptar AddonClaim legacy (migracion chart 1.0.0 -> 1.1.0) + # Idempotente: skip silencioso si AddonClaim no existe (primera + # instalacion) o si ya esta de-adoptado (segundo run post-migracion). + # Si tiene annotations Helm de un release v1.0.0, las quita para que + # el siguiente helm upgrade no intente borrarlo (no esta en el chart). + # Sunset del step tras una rotacion completa (~2 semanas). + run: | + RELEASE_NAME=__APP_NAME__ bash scripts/pre-helm-detach.sh + + - name: Aplicar AddonClaim + # Sustitucion de placeholders con sed (evita dependencia de envsubst, + # que no esta garantizado en la imagen del runner). kubectl apply es + # idempotente: primer push lo crea, push subsiguientes son no-op si + # el spec no cambia. helm upgrade --install nunca lo trackea ni lo borra. + env: + RELEASE_NAME: __APP_NAME__ + run: | + # databaseName = release name con guiones a underscores + sufijo _db. + # Coincide con el default del chart 1.0.0 (.Release.Name | replace + # "-" "_" | printf "%s_db"). + DATABASE_NAME="$(echo "${RELEASE_NAME}" | tr '-' '_')_db" + sed -e "s|\${RELEASE_NAME}|${RELEASE_NAME}|g" \ + -e "s|\${DATABASE_NAME}|${DATABASE_NAME}|g" \ + manifests/addonclaim.yaml | kubectl apply -f - + - name: Helm upgrade --install env: TENANT_ID: ${{ vars.TENANT_ID }} @@ -144,8 +176,8 @@ jobs: --wait --timeout 7m # 7m absorbe el peor caso del reconcile de addon-provisioner # (RECONCILE_INTERVAL 300s + initial_delay 60s) + StatefulSet MariaDB - # ready (~60s) + Job DDL (~10s) + InitContainer wait-for-secret (~5s) - # + Alembic upgrade. Ver helm//templates/addonclaim.yaml. + # ready (~60s) + Job DDL (~10s) + InitContainer wait-for-secret + Alembic. + # AddonClaim ya fue aplicado en el step previo (ADR-066). - name: Verificar rollout run: | diff --git a/README.md b/README.md index 595de8c..9b03d33 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,11 @@ tenant, los placeholders en mayúsculas se sustituyen automáticamente. │ ├── pyproject.toml │ ├── requirements.txt │ └── Dockerfile -├── helm/__APP_NAME__/ ← Helm chart con AddonClaim + hook Alembic +├── helm/__APP_NAME__/ ← Helm chart (Deployment, Job Alembic, NP, RBAC) +├── manifests/ +│ └── addonclaim.yaml ← AddonClaim fuera del chart (ADR-066) +├── scripts/ +│ └── pre-helm-detach.sh ← migración v1.0.0 → v1.1.0 (idempotente) └── .gitea/workflows/ ← CI: tests + build + deploy ``` @@ -48,30 +52,57 @@ tenant, los placeholders en mayúsculas se sustituyen automáticamente. --- -## Orden de hooks Helm +## Orden de aplicación — AddonClaim + hooks Helm + +El workflow del tenant sigue este orden en cada `git push`: + +| Paso | Acción | Motivo | +|------|--------|--------| +| 1 | `bash scripts/pre-helm-detach.sh` | Migración chart 1.0.0 → 1.1.0: si el AddonClaim tiene labels/annotations de un release Helm previo, las quita. Idempotente: skip si no aplica. Sunset tras ~2 semanas. | +| 2 | `kubectl apply -f manifests/addonclaim.yaml` (con `sed` para placeholders) | Crea o actualiza el `AddonClaim`. Idempotente. `addon-provisioner` empieza a reconciliar en paralelo. | +| 3 | `helm upgrade --install ./helm/ --wait --timeout 7m` | Aplica el chart Helm. Los hooks `pre-install,pre-upgrade` corren en orden de `hook-weight` (ver tabla abajo). El Job Alembic espera al Secret. | El chart usa hooks `pre-install,pre-upgrade` con `hook-weight` para garantizar -el orden de creación. El `helm upgrade --install` funciona **sin `--no-hooks`**: +el orden de creación de SUS recursos (sin AddonClaim — ese vive fuera). +`helm upgrade --install` funciona **sin `--no-hooks`**: | Recurso | Template | `hook-weight` | Motivo | |---------|----------|---------------|--------| -| `AddonClaim` | `addonclaim.yaml` | `-10` | Declara la intención de la app de tener una BD — el primero en aplicarse para que `addon-provisioner` empiece a reconciliar lo antes posible | +| `NetworkPolicy` | `networkpolicy.yaml` | `-8` | Bajo el `default-deny` del namespace `apps`, esta NP permite DNS + apiserver + addons:3306. Debe existir antes que el InitContainer del Job Alembic intente conectarse al apiserver. | | `Role` + `RoleBinding` | `role-db-secret-reader.yaml` | `-7` | Permite al SA leer el Secret `-db-creds` (lo consume el InitContainer `wait-for-secret` del Job Alembic) | | `ServiceAccount` | `serviceaccount.yaml` | `-5` | Lo referencia el pod del Job Alembic — debe existir antes | | `Job` Alembic | `job-alembic-upgrade.yaml` | `0` | InitContainer `wait-for-secret` espera al Secret publicado por `addon-provisioner` (hasta 240s), luego corre `alembic upgrade head` | -El `AddonClaim` (hook weight `-10`) NO declara `hook-delete-policy` intencionalmente: -así sobrevive entre `helm upgrade` (re-apply idempotente) y se evita disparar el -finalizer `cleanup-logical` del controller en cada upgrade (que dropearía el -user de la BD y regeneraría el password, causando ventana de downtime). -Trade-off: `helm uninstall` deja el AddonClaim huérfano y MariaDB sigue -aprovisionada. Es coherente con `deletionPolicy: Retain` — borrar BD productiva -debe ser decisión explícita, no side-effect del uninstall. Cleanup explícito: +### Por qué el `AddonClaim` vive FUERA del chart (ADR-066) + +En chart 0.2.x el `AddonClaim` era hook `pre-install,pre-upgrade` weight `-10`. +El default de Helm 3 (`hook-delete-policy: before-hook-creation`) borraba el +claim previo antes de aplicar el nuevo en cada `helm upgrade --install`. El +finalizer `addon-provisioner.idp.coralware.cloud/cleanup-logical` ejecutaba +entonces `REVOKE` + `DROP USER` + delete del Secret en cada upgrade → rotación +involuntaria de credenciales + release atorado en `pending-upgrade`. + +Convertir el `AddonClaim` a manifest principal del chart (no hook) también +falla: los hooks `pre-install,pre-upgrade` corren ANTES del manifest principal, +así que el Job Alembic intentaría leer el Secret antes de que el AddonClaim +exista — chicken-and-egg fatal. + +La solución es desacoplar el lifecycle del CRD del lifecycle del release Helm: +el `AddonClaim` lo aplica el workflow con `kubectl apply` (idempotente, sin +ownership Helm), y Helm gobierna sólo la app + sus hooks Alembic/NP/RBAC. + +`helm uninstall` no toca el AddonClaim — desacoplamiento garantizado. +Borrar la BD lógica es decisión explícita: ```bash kubectl delete addonclaim -db -n apps ``` +(Con `deletionPolicy: Retain` — default — preserva la BD lógica; el operador +emite Event `OrphanLogicalDatabase`. Cambiar a `Delete` previo para `DROP DATABASE`.) + +### Por qué el ServiceAccount es hook + El `ServiceAccount` se materializa como hook (no en la fase principal de Helm) porque el Job Alembic, que también es hook, declara `serviceAccountName`. Si el SA viviera en la fase principal se crearía **después** del Job → el pod del Job @@ -108,28 +139,33 @@ claves `DATABASE_URL` / `DATABASE_HOST` / `DATABASE_PORT` / `DATABASE_NAME` / `DATABASE_USER` / `DATABASE_PASSWORD` (y opcionalmente `MIGRATION_DATABASE_URL`). El Secret lo materializa automáticamente la plataforma vía el flujo -`AddonClaim` → `addon-provisioner` (ADR-052 + ADR-064): +`AddonClaim` → `addon-provisioner` (ADR-052 + ADR-064 + ADR-066): -1. El chart aplica `helm/__APP_NAME__/templates/addonclaim.yaml` como hook - `pre-install,pre-upgrade` weight `-10` — declara `addonType: mariadb`, - `databaseName` derivado del Release name y el `secretName` esperado. +1. El workflow del tenant aplica `manifests/addonclaim.yaml` con + `kubectl apply -f -` (placeholders sustituidos con `sed`). El claim declara + `addonType: mariadb`, `databaseName` derivado del nombre del release y el + `secretName` esperado. 2. `addon-provisioner` (corriendo en el plano de control IDP) reconcilia el `AddonClaim`: crea una BD lógica + user + grant en la instancia MariaDB compartida del tenant (ns `addons` del cluster tenant) vía Job DDL efímero. 3. Publica el Secret `__APP_NAME__-db-creds` en el ns `apps`. -4. El InitContainer `wait-for-secret` del Job Alembic espera al Secret (hasta - 240s), Alembic ejecuta `upgrade head`, y el Deployment rola. +4. El workflow corre `helm upgrade --install`. El InitContainer + `wait-for-secret` del Job Alembic (hook `pre-install,pre-upgrade`) espera al + Secret (hasta 240s), Alembic ejecuta `upgrade head`, y el Deployment rola. ### Opt-out — apps stateless -Si tu app no necesita BD, en `values.yaml`: +Si tu app no necesita BD: -```yaml -database: - enabled: false # no se aplica AddonClaim ni Role/RoleBinding -alembic: - enabled: false # no corre el Job Alembic -``` +1. En `values.yaml`: + ```yaml + database: + enabled: false # no se aplica Role/RoleBinding al SA + alembic: + enabled: false # no corre el Job Alembic + ``` +2. Elimina el step "Aplicar AddonClaim" del workflow `.gitea/workflows/__APP_NAME__-build.yaml` + (junto con `manifests/addonclaim.yaml` y el step `pre-helm-detach`). Con `database.enabled=false` la app se despliega sin BD ni migraciones. diff --git a/helm/APP_NAME/Chart.yaml b/helm/APP_NAME/Chart.yaml index 599a6cb..a9ab71d 100644 --- a/helm/APP_NAME/Chart.yaml +++ b/helm/APP_NAME/Chart.yaml @@ -1,6 +1,14 @@ apiVersion: v2 name: __APP_NAME__ # Helm chart del Golden Path web-backend/python. +# 0.3.0 (2026-05-25): ADR-066 — AddonClaim sale del chart y vive en +# manifests/addonclaim.yaml. El workflow del tenant lo aplica con +# kubectl apply ANTES de helm upgrade --install. Razon: en chart 0.2.x el +# AddonClaim era hook pre-install,pre-upgrade weight -10 → el default Helm +# `before-hook-creation` borraba el claim en cada upgrade → el finalizer +# cleanup-logical rotaba credenciales y atoraba el release en +# pending-upgrade. Desacoplar el lifecycle del CRD del lifecycle del +# release Helm resuelve el bug de raiz. # 0.2.0 (2026-05-19): cambio Ingress controller Traefik (IngressRoute CRD) # → rke2-ingress-nginx (networking.k8s.io/v1 Ingress). Razón: # TENANT-INGRESS-CONTROLLER-MISMATCH (APERTURA COVE Sesión 3). El cluster @@ -8,7 +16,7 @@ name: __APP_NAME__ # adicional. tlsSecretName por default = apps-wildcard-tls (Gap 3). description: Helm chart del Golden Path web-backend/python type: application -version: 0.2.0 +version: 0.3.0 appVersion: "0.1.0" keywords: - fastapi diff --git a/helm/APP_NAME/templates/addonclaim.yaml b/helm/APP_NAME/templates/addonclaim.yaml deleted file mode 100644 index d17876c..0000000 --- a/helm/APP_NAME/templates/addonclaim.yaml +++ /dev/null @@ -1,39 +0,0 @@ -{{- if .Values.database.enabled }} -# AddonClaim — declara la intencion de la app de tener una BD (default MariaDB). -# Lo materializa addon-provisioner (CRD idp.coralware.cloud/v1alpha1): -# - reconcilia este claim cada RECONCILE_INTERVAL (default 300s) por TenantProfile. -# - crea BD logica + user + grant via Job DDL efimero en el cluster tenant. -# - publica el Secret `{{ .Values.database.secretName }}` en este ns (`apps`) -# con DATABASE_URL / DATABASE_HOST / DATABASE_PORT / DATABASE_USER / -# DATABASE_PASSWORD / DATABASE_NAME (contrato §2.3 del OpenSpec). -# -# Ordering con los hooks del chart (clave para que Alembic encuentre el Secret): -# weight -10 (este AddonClaim) -# -> weight -5 (ServiceAccount) -# -> weight 0 (Job Alembic — espera al Secret via InitContainer wait-for-secret) -# -# delete-policy: NO se declara intencionalmente. -# - Asi el AddonClaim sobrevive entre `helm upgrade` (re-apply idempotente). -# - Evita disparar el finalizer `cleanup-logical` en cada upgrade (que dropearia -# el user y regeneraria el password, causando ventana de downtime). -# - Trade-off documentado: `helm uninstall` deja el AddonClaim orfano y MariaDB -# sigue aprovisionada. Es coherente con `deletionPolicy: Retain` (default): -# borrar BD productiva debe ser decision explicita, no side-effect del uninstall. -# Cleanup explicito: `kubectl delete addonclaim {{ .Release.Name }}-db -n apps`. -apiVersion: idp.coralware.cloud/v1alpha1 -kind: AddonClaim -metadata: - name: {{ .Release.Name }}-db - labels: - {{- include "app.labels" . | nindent 4 }} - annotations: - "helm.sh/hook": pre-install,pre-upgrade - "helm.sh/hook-weight": "-10" -spec: - addonType: {{ .Values.database.addonType | default "mariadb" | quote }} - databaseName: {{ .Values.database.databaseName | default (printf "%s_db" (.Release.Name | replace "-" "_")) | quote }} - user: - permissions: {{ .Values.database.user.permissions | default "rw" | quote }} - secretName: {{ .Values.database.secretName | quote }} - deletionPolicy: {{ .Values.database.deletionPolicy | default "Retain" | quote }} -{{- end }} diff --git a/helm/APP_NAME/templates/job-alembic-upgrade.yaml b/helm/APP_NAME/templates/job-alembic-upgrade.yaml index 197fe28..c6db81f 100644 --- a/helm/APP_NAME/templates/job-alembic-upgrade.yaml +++ b/helm/APP_NAME/templates/job-alembic-upgrade.yaml @@ -5,9 +5,11 @@ # 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 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. diff --git a/helm/APP_NAME/templates/networkpolicy.yaml b/helm/APP_NAME/templates/networkpolicy.yaml index 46a8ac0..99efc6c 100644 --- a/helm/APP_NAME/templates/networkpolicy.yaml +++ b/helm/APP_NAME/templates/networkpolicy.yaml @@ -12,7 +12,10 @@ # NetworkPolicy nunca se aplica en la fase principal. # # Weight ordering (mas chico = antes): -# -10 AddonClaim -> -8 NetworkPolicy -> -7 Role/RoleBinding -> -5 SA -> 0 Job Alembic +# -8 NetworkPolicy -> -7 Role/RoleBinding -> -5 SA -> 0 Job Alembic +# (El AddonClaim vive FUERA del chart desde 0.3.0 — ADR-066. Lo aplica el +# workflow del tenant ANTES de `helm upgrade --install`, asi que en la fase +# de hooks ya existe y addon-provisioner ya esta reconciliando.) # # delete-policy: before-hook-creation (NO hook-succeeded): la fase principal del # Deployment necesita la NetworkPolicy viva en runtime; solo se borra/recrea al diff --git a/helm/APP_NAME/templates/role-db-secret-reader.yaml b/helm/APP_NAME/templates/role-db-secret-reader.yaml index ddabd7c..8310a3e 100644 --- a/helm/APP_NAME/templates/role-db-secret-reader.yaml +++ b/helm/APP_NAME/templates/role-db-secret-reader.yaml @@ -4,11 +4,13 @@ # (hook pre-install/pre-upgrade weight 0) que hace busy-wait hasta que # addon-provisioner publica el Secret tras reconciliar el AddonClaim. # -# Hook weight "-7" — entre AddonClaim (-10) y SA (-5): +# Hook weight "-7" — entre NetworkPolicy (-8) y SA (-5): # - Garantiza que RBAC exista antes de que el SA del hook -5 se materialice # y antes de que el Job -0 intente leer el Secret. # - delete-policy: before-hook-creation para que sea idempotente entre upgrades # sin dejar Roles huerfanos. +# (El AddonClaim ya no es hook del chart desde 0.3.0 — ADR-066. Lo aplica el +# workflow del tenant ANTES del `helm upgrade --install`.) # # Scope minimo: solo lectura (get/watch) del Secret `-db-creds`, no # secrets cluster-wide ni del ns `addons`. El root Secret de MariaDB vive en diff --git a/manifests/addonclaim.yaml b/manifests/addonclaim.yaml new file mode 100644 index 0000000..d1fd8df --- /dev/null +++ b/manifests/addonclaim.yaml @@ -0,0 +1,34 @@ +# AddonClaim — vive FUERA del chart Helm (ADR-066). +# +# El workflow del tenant sustituye los placeholders con `sed` y aplica con +# `kubectl apply -f -` ANTES de `helm upgrade --install`. helm nunca lo +# trackea — al `helm uninstall` no lo borra; al `helm upgrade --install` +# repetido no lo toca. Idempotente. +# +# Lifecycle gobernado por addon-provisioner (CRD idp.coralware.cloud/v1alpha1): +# - reconcilia cada RECONCILE_INTERVAL (default 300s) por TenantProfile. +# - crea BD logica + user + GRANT via Job DDL efimero en cluster tenant. +# - publica Secret `${RELEASE_NAME}-db-creds` en ns `apps`. +# - agrega finalizer `addon-provisioner.idp.coralware.cloud/cleanup-logical`. +# +# Cleanup explicito post `helm uninstall`: +# kubectl delete addonclaim ${RELEASE_NAME}-db -n apps +# (con deletionPolicy: Retain — default — preserva la BD; Event +# OrphanLogicalDatabase. Cambiar a Delete previo para DROP DATABASE.) +apiVersion: idp.coralware.cloud/v1alpha1 +kind: AddonClaim +metadata: + name: ${RELEASE_NAME}-db + namespace: apps + labels: + app.kubernetes.io/name: ${RELEASE_NAME} + app.kubernetes.io/part-of: ${RELEASE_NAME} + idp.coralware.cloud/golden-path: web-backend + idp.coralware.cloud/golden-path-lang: python +spec: + addonType: mariadb + databaseName: ${DATABASE_NAME} + user: + permissions: rw + secretName: ${RELEASE_NAME}-db-creds + deletionPolicy: Retain diff --git a/scripts/pre-helm-detach.sh b/scripts/pre-helm-detach.sh new file mode 100755 index 0000000..86c819c --- /dev/null +++ b/scripts/pre-helm-detach.sh @@ -0,0 +1,65 @@ +#!/usr/bin/env bash +# Script de migracion v1.0.0 -> v1.1.0 del chart web-backend-python (ADR-066). +# +# Las apps desplegadas con chart 1.0.0 tienen el AddonClaim creado como Helm +# hook con annotations `meta.helm.sh/release-name`, `app.kubernetes.io/managed-by=Helm` +# y `helm.sh/hook`. El chart 1.1.0 mueve el AddonClaim FUERA del chart; el +# workflow del tenant lo aplica con `kubectl apply -f` despues de este script. +# +# Sin de-adopcion, el siguiente `helm upgrade --install` veria el AddonClaim +# en el state previo (release v_n) pero no en el manifest nuevo (chart 1.1.0) +# → helm intentaria delete del recurso → el finalizer dropearia user/Secret. +# +# Este script: +# - skip silencioso si el AddonClaim no existe (primera instalacion). +# - skip silencioso si el AddonClaim NO tiene annotations Helm (ya de-adoptado). +# - de-adopta si encuentra annotations Helm — quita las labels/annotations +# que hacen que Helm lo reconozca como recurso del release. +# +# Idempotente. Sunset del script tras una rotacion completa de releases +# desplegadas con chart 1.0.0 (~2 semanas). +# +# Variables de entorno requeridas: +# - RELEASE_NAME: nombre del release Helm (== nombre de la app del tenant) +# - KUBECONFIG: apuntando al cluster tenant +# +# Uso (desde el workflow del tenant): +# RELEASE_NAME=__APP_NAME__ bash scripts/pre-helm-detach.sh + +set -euo pipefail + +: "${RELEASE_NAME:?ERROR: RELEASE_NAME no esta seteado}" +NAMESPACE="${NAMESPACE:-apps}" +CLAIM_NAME="${RELEASE_NAME}-db" + +# Comprobar existencia. kubectl get devuelve exit 1 si NotFound — capturamos +# con `|| true` para no romper bajo `set -e`. +if ! kubectl get addonclaim "${CLAIM_NAME}" -n "${NAMESPACE}" >/dev/null 2>&1; then + echo "[pre-helm-detach] AddonClaim ${CLAIM_NAME} no existe en ns ${NAMESPACE} — skip (primera instalacion)." + exit 0 +fi + +# Detectar si tiene annotations Helm — si no, ya esta de-adoptado. +RELEASE_ANNOTATION=$(kubectl get addonclaim "${CLAIM_NAME}" -n "${NAMESPACE}" \ + -o jsonpath='{.metadata.annotations.meta\.helm\.sh/release-name}' 2>/dev/null || echo "") + +if [ -z "${RELEASE_ANNOTATION}" ]; then + echo "[pre-helm-detach] AddonClaim ${CLAIM_NAME} sin annotations Helm — skip (ya de-adoptado)." + exit 0 +fi + +echo "[pre-helm-detach] AddonClaim ${CLAIM_NAME} con release=${RELEASE_ANNOTATION} — de-adoptando…" + +# Quitar annotations Helm — `-` sufijo significa "remove". +kubectl annotate addonclaim "${CLAIM_NAME}" -n "${NAMESPACE}" \ + meta.helm.sh/release-name- \ + meta.helm.sh/release-namespace- \ + --overwrite + +# Quitar label de managed-by — el hook tambien deja la annotation `helm.sh/hook` +# en algunos casos; la dejamos viva porque no afecta el ownership tracking. +kubectl label addonclaim "${CLAIM_NAME}" -n "${NAMESPACE}" \ + app.kubernetes.io/managed-by- \ + --overwrite + +echo "[pre-helm-detach] De-adopcion completa. El siguiente helm upgrade --install no tocara este AddonClaim."