From 29021bff70efc6b7518848bd360722ed490015d1 Mon Sep 17 00:00:00 2001 From: Cristian Felipe Cruz Buitron Date: Mon, 17 Aug 2026 01:46:43 -0500 Subject: [PATCH] fix(ci): condicion de carrera en el chequeo de promocion GitOps Con el runner corriendo 1 job a la vez (capacity: 1), cuando 2+ pipelines comparten el mismo commit disparador, el primero en terminar deja su propio commit de GitOps en main -- los siguientes veian 'main ya no es igual a mi commit' y se saltaban su propia promocion, aunque era seguro promocionar. Visto en vivo: Frontend construyo, escaneo y firmo v1.0.119 pero el manifiesto seguia en v1.0.104 (Promocion GitOps: false). Fix en los 3 workflows: git merge-base --is-ancestor en vez de comparacion exacta de SHA. Bump manual de frontend a v1.0.119 (la version que ya estaba lista, sin desplegar). Documentado el limite conocido de este fix en el playbook de la migracion. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_011FqYP3Wf1W63Qmh7qgXAAT --- .gitea/workflows/build-medusa.yaml | 6 +- .gitea/workflows/build.yaml | 13 +++- .gitea/workflows/deploy-docs.yaml | 6 +- .../migracion-platform-templates-2026-08.md | 60 +++++++++++++++++++ .../frontend/patches/deployment.yaml | 2 +- 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build-medusa.yaml b/.gitea/workflows/build-medusa.yaml index d511451..716e74f 100644 --- a/.gitea/workflows/build-medusa.yaml +++ b/.gitea/workflows/build-medusa.yaml @@ -344,6 +344,10 @@ jobs: --insecure-ignore-tlog=true \ "${IMAGE_NAME}@${{ steps.push.outputs.digest }}" + # merge-base --is-ancestor, no comparación exacta -- ver el + # comentario largo en build.yaml sobre por qué (condición de + # carrera real con 2+ pipelines compartiendo un commit, visto en + # vivo 2026-08-17). - name: Verificar promoción segura id: promotion shell: bash @@ -355,7 +359,7 @@ jobs: CURRENT_SHA="${{ github.sha }}" REMOTE_SHA="$(git rev-parse origin/main)" - if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then + if git merge-base --is-ancestor "${CURRENT_SHA}" origin/main; then echo "promote=true" >> "$GITHUB_OUTPUT" else echo "promote=false" >> "$GITHUB_OUTPUT" diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 8a2a596..493a1e0 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -489,6 +489,17 @@ jobs: # Evita que una ejecución antigua actualice frontend.yaml después de que # ya exista un commit más reciente en main. + # + # merge-base --is-ancestor, no comparación exacta de SHA: cuando 2+ + # pipelines comparten el mismo commit disparador y el runner los + # corre en serie (capacity: 1), el primero en terminar ya dejó su + # propio commit de GitOps en main -- con comparación exacta, los + # demás ven "main ya no es igual a mi commit" y se saltan la + # promoción sin necesidad (visto en vivo 2026-08-17, ver + # docs/playbooks/migracion-platform-templates-2026-08.md). Con + # is-ancestor, mientras el commit que disparó ESTE pipeline siga + # siendo ancestro de main, promociona igual -- solo bloquea si de + # verdad hubo un rebase/force-push que lo descartó. - name: Verificar promoción segura id: promotion shell: bash @@ -503,7 +514,7 @@ jobs: echo "Commit del pipeline: ${CURRENT_SHA}" echo "Último commit remoto: ${REMOTE_SHA}" - if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then + if git merge-base --is-ancestor "${CURRENT_SHA}" origin/main; then echo "promote=true" >> "$GITHUB_OUTPUT" echo "La imagen puede promocionarse." else diff --git a/.gitea/workflows/deploy-docs.yaml b/.gitea/workflows/deploy-docs.yaml index 607a8e2..2d5961a 100644 --- a/.gitea/workflows/deploy-docs.yaml +++ b/.gitea/workflows/deploy-docs.yaml @@ -335,6 +335,10 @@ jobs: --insecure-ignore-tlog=true \ "${IMAGE_NAME}@${{ steps.push.outputs.digest }}" + # merge-base --is-ancestor, no comparación exacta -- ver el + # comentario largo en build.yaml sobre por qué (condición de + # carrera real con 2+ pipelines compartiendo un commit, visto en + # vivo 2026-08-17). - name: Verificar promoción segura id: promotion shell: bash @@ -346,7 +350,7 @@ jobs: CURRENT_SHA="${{ github.sha }}" REMOTE_SHA="$(git rev-parse origin/main)" - if [ "${CURRENT_SHA}" = "${REMOTE_SHA}" ]; then + if git merge-base --is-ancestor "${CURRENT_SHA}" origin/main; then echo "promote=true" >> "$GITHUB_OUTPUT" else echo "promote=false" >> "$GITHUB_OUTPUT" diff --git a/workloads/docs-portal/docs/playbooks/migracion-platform-templates-2026-08.md b/workloads/docs-portal/docs/playbooks/migracion-platform-templates-2026-08.md index cba5acb..5032705 100644 --- a/workloads/docs-portal/docs/playbooks/migracion-platform-templates-2026-08.md +++ b/workloads/docs-portal/docs/playbooks/migracion-platform-templates-2026-08.md @@ -110,3 +110,63 @@ público correspondiente después del rollout: Y con el estado del pod nuevo (`kubectl get pods`, sin reinicios, `1/1 Ready`) antes de dar cada migración por cerrada. + +## Actualización (mismo día): 2 bugs más, esta vez en CI, no en K8s + +El primer push real después de migrar las 3 apps (justo el commit que +agregó las secciones de arriba a este playbook) expuso dos problemas +más — ninguno de los dos en Kubernetes esta vez, los dos en los propios +pipelines de Gitea Actions. + +### 4. `MANIFEST_FILE` apuntando a una ruta que ya no existía + +Mover cada `deployment.yaml` suelto a `.../patches/deployment.yaml` +(necesario para el patrón base+overlay) rompió en silencio el paso de +auto-actualización GitOps de los 3 pipelines (`build.yaml`, +`build-medusa.yaml`, `deploy-docs.yaml`) — cada uno tiene su propio +`MANIFEST_FILE` hardcodeado como variable de entorno, y los 3 seguían +apuntando a las rutas viejas. No se notó hasta el primer build real +post-migración: la imagen se construyó, escaneó y firmó bien, pero el +último paso falló con `sed: can't read ...: No such file or directory`, +dejando una imagen firmada y verificada sin desplegar. Fix: actualizar +las 3 rutas de `MANIFEST_FILE`. + +**Para la próxima vez que se mueva un manifiesto:** `grep -rn +MANIFEST_FILE .gitea/workflows/` antes de asumir que un `git mv` dentro +de `workloads/` es solo un problema del propio manifiesto — los +pipelines de CI referencian esas rutas exactas en un archivo aparte. + +### 5. Condición de carrera en el chequeo de "promoción segura" + +El runner de Gitea de este lab corre **un solo job a la vez** +(`capacity: 1` en su config). Cuando el fix del punto 4 disparó los 3 +pipelines a la vez para el mismo commit, se ejecutaron en serie. El +chequeo de "promoción segura" de cada uno compara el commit que lo +disparó contra el HEAD actual de `main` (`if [ "$CURRENT_SHA" = +"$REMOTE_SHA" ]`) para evitar que una corrida vieja pise un manifiesto +más nuevo. Problema real: el primer pipeline en terminar (Medusa) dejó +su propio commit automático en `main` — para cuando les tocó el turno +a Frontend y Docs Portal, `main` ya no coincidía con el commit que los +había disparado, así que ambos se saltaron la actualización del +manifiesto (`Promoción GitOps: false`) aunque era perfectamente seguro +promocionar. Frontend quedó con una imagen nueva construida, escaneada +y firmada (`v1.0.119`) pero sin desplegar — el manifiesto seguía en +`v1.0.104`. + +Fix aplicado en los 3 workflows: cambiar la comparación exacta de SHA +por `git merge-base --is-ancestor "$CURRENT_SHA" origin/main` — permite +promocionar mientras el commit disparador siga siendo ancestro de +`main` (tolera que otro pipeline haya commiteado en el medio), y solo +bloquea si de verdad hubo un rebase/force-push que lo descartó. + +!!! warning "Límite conocido de este fix, aceptado a propósito" + `is-ancestor` es más permisivo que la comparación exacta en un + caso borde distinto: si 2 commits al **mismo** app quedaran en cola + y el más viejo terminara *después* del más nuevo, ahora sí + promocionaría igual — pisando la versión más nueva con la más + vieja. Con un solo mantenedor en este lab, 2 commits casi + simultáneos al mismo app es muy poco probable; se aceptó el + trade-off en vez de una solución más compleja (ignorar commits + `[skip ci]` del propio bot al calcular "el último commit real"). + Revisar si esto deja de ser cierto (más de una persona commiteando, + o automatización que dispare builds más seguido). diff --git a/workloads/ecommerce/frontend/patches/deployment.yaml b/workloads/ecommerce/frontend/patches/deployment.yaml index 7996421..1a816cd 100644 --- a/workloads/ecommerce/frontend/patches/deployment.yaml +++ b/workloads/ecommerce/frontend/patches/deployment.yaml @@ -25,7 +25,7 @@ spec: runAsUser: 1001 containers: - name: app - image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.104 + image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.119 env: - name: HEADLESS_PROVIDER value: medusa