fix(ci): guard concurrency + recuperación de release pending-install en helm-deploy (TENANT-BUILD-NO-CONCURRENCY-GUARD)

Sin guard `concurrency:` múltiples dispatches del repo del tenant corrían
helm upgrade en paralelo sobre el mismo release → 'another operation in
progress' → release trabado en pending-install → primer deploy nunca completa
(vivido E2E 2026-06-10, tenant 9e6babb0). Causa raíz del first-deploy flaky
(runs 3418/3419).

- concurrency workflow-level (group por repo, cancel-in-progress:false: encola,
  no cancela — cancelar a mitad de helm install es lo que CREA el pending-install)
- step de recuperación: detecta pending-install/uninstalling→uninstall,
  pending-upgrade/pending-rollback→rollback, antes del helm upgrade. Fail-safe:
  solo toca estados pending-*, no toca deployed/failed/inexistente.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Además: añadido el wait de rancher-webhook (RANCHER-WEBHOOK-RACE-RECOVERY) que
faltaba en rest-api — paridad con el template web-backend.
This commit is contained in:
Irving Lopez
2026-06-14 17:01:56 -05:00
parent e7e1801dcb
commit 929a4cefee

View File

@@ -16,6 +16,19 @@ on:
- '.gitea/workflows/__APP_NAME__-build.yaml'
workflow_dispatch: {}
# ── Serialización de deploys (TENANT-BUILD-NO-CONCURRENCY-GUARD) ──────────────
# Sin este guard, múltiples dispatches del mismo repo (scaffold `on:push` +
# tenant-manager fase 1/fase 2 + re-dispatch por rotación de kubeconfig + smoke)
# corren `helm upgrade --install` EN PARALELO sobre el MISMO release → Helm aborta
# con "another operation (install/upgrade/rollback) is in progress" y deja el
# release trabado en `pending-install` → el primer deploy del tenant nunca
# completa (vivido E2E 2026-06-10, tenant 9e6babb0; destrabado a mano borrando el
# secret del release). Los runs se ENCOLAN, NO se cancelan: cancelar un run a mitad
# de `helm install` es justo lo que CREA el estado `pending-install`.
concurrency:
group: deploy-${{ gitea.repository }}
cancel-in-progress: false
jobs:
# ── Gate de credenciales de build (GOLDEN-PATH-BUILD-DEPLOY-ORDER-RACE) ───
# Los secrets HARBOR_ROBOT_USER/TOKEN los siembra tenant-manager en su fase 1
@@ -180,6 +193,61 @@ jobs:
echo "$KUBECONFIG_B64" | base64 -d > "${HOME}/.kube/config"
chmod 600 "${HOME}/.kube/config"
- name: Esperar rancher-webhook ready (RANCHER-WEBHOOK-RACE-RECOVERY)
# Race condition durante el bootstrap del cluster tenant: el
# `rancher-webhook` Deployment en cattle-system aún no tiene endpoints
# cuando el primer `helm upgrade --install` intenta crear el namespace
# `apps`, lo que dispara el webhook
# `rancher.cattle.io.namespaces.create-non-kubesystem` y aborta el
# release con `no endpoints available for service rancher-webhook`.
# Paridad con el template web-backend (este paso faltaba en rest-api).
run: |
# `kubectl wait` falla con NotFound si el Deployment aún no existe:
# durante el bootstrap del cluster tenant, `rancher-webhook` puede no
# haberse creado todavía cuando corre este step. Primero esperamos a
# que el objeto APAREZCA (poll), luego a que esté Available.
until kubectl get deployment rancher-webhook -n cattle-system >/dev/null 2>&1; do
echo "Esperando a que el Deployment rancher-webhook exista en cattle-system..."
sleep 5
done
kubectl wait deployment rancher-webhook \
-n cattle-system \
--for=condition=Available \
--timeout=600s
- name: Recuperar release Helm atascado (TENANT-BUILD-NO-CONCURRENCY-GUARD)
# Red de seguridad ADEMÁS del guard `concurrency`: si un run previo quedó a
# mitad de una operación Helm (cancelado, runner reiniciado, o carrera
# histórica anterior a este fix), el release queda en un estado `pending-*`
# y el siguiente `helm upgrade --install` aborta. Lo detectamos y recuperamos
# antes de desplegar.
# Fail-safe: SOLO actúa sobre estados `pending-*`/`uninstalling`; un release
# `deployed`, `failed` o inexistente NO se toca. Si el parseo no encuentra
# STATUS (formato inesperado) cae al branch no-op → comportamiento idéntico
# al actual, nunca peor.
run: |
STATUS="$(helm status __APP_NAME__ -n apps 2>/dev/null | sed -n 's/^STATUS: //p')"
echo "Estado actual del release __APP_NAME__: ${STATUS:-<no existe>}"
case "$STATUS" in
pending-install|uninstalling)
# Nunca hubo una revisión desplegada con éxito → no se puede rollback.
# `helm uninstall` limpia el secret del release trabado (equivale al
# destrabado manual del 2026-06-10) sin riesgo de datos: el primer deploy
# aún no levantó la app.
echo "Release en '${STATUS}' (sin revisión estable) → helm uninstall para limpiar."
helm uninstall __APP_NAME__ -n apps --wait --timeout 2m || true
;;
pending-upgrade|pending-rollback)
# Hubo una revisión estable previa → rollback a la última desplegada
# (preserva la app viva; el upgrade siguiente reintenta la nueva imagen).
echo "Release en '${STATUS}' → helm rollback a la última revisión estable."
helm rollback __APP_NAME__ 0 -n apps --wait --timeout 2m || true
;;
*)
echo "Release en estado estable o inexistente → sin recuperación necesaria."
;;
esac
- name: Helm upgrade --install
env:
TENANT_ID: ${{ vars.TENANT_ID }}