From a9b01800e02ca1d91a001187a26d81dea1f8c018 Mon Sep 17 00:00:00 2001 From: aarroyo Date: Tue, 1 Sep 2026 10:55:44 -0500 Subject: [PATCH] ci(imagenes): preguntar si la imagen construye antes del merge, no tres promociones despues El job que construye las tres imagenes de servicio estaba gateado a `main` y a tags. Eso ponia la pregunta "esto todavia construye?" despues de todas las puertas que podrian haberla parado: un Dockerfile roto de agent-runtime cruzo los PR #669, #670 y #673 con 46, 78 y 47 checks en verde cada uno, y lo encontro un humano leyendo el registry. El build pasa a correr en cada PR; el push sigue gateado a main y tags, que es exactamente la conducta de publicacion que el job ya tenia. En un pull request `github.ref` es refs/pull/N/merge, asi que no se hace login contra GHCR ni sale nada hacia el registry: el PR demuestra que la imagen compila, y nada mas. Un matrix reporta un check por rama y cada contexto arrastra sus parametros --- "(core-api, ., ./src/apps/core-api/Dockerfile)" ---, que cambian cuando cambia una ruta. Por eso las tres se colapsan en un job sin matrix, `Services build (GHCR)`, con nombre estable: ese es el contexto que la proteccion de rama puede exigir. Trata `skipped` como paso porque significa que fallo una suite previa, y esa ya tiene su propio check en rojo; duplicarlo solo aniade ruido. Con el build ahora en la ruta caliente de cada PR, se aniade cache de GitHub Actions con scope por servicio, para que las tres ramas no se desalojen entre si. Co-Authored-By: Claude Opus 5 Signed-off-by: aarroyo --- .github/workflows/ci-cd.yml | 60 +++++++++++++++++-- .../core/control-center/deploy/README.es.md | 20 ++++--- .../core/control-center/deploy/README.md | 18 ++++-- 3 files changed, 81 insertions(+), 17 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 5cf1c42e0..2a8e14442 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -860,14 +860,30 @@ jobs: # workspaces, in dependency order, with --provenance, idempotent against the # registry, and rehearsable via a dry run that defaults to on. + # GT-679 — the image build now runs on the PR, not after it. + # + # This job was gated `if: ref == main || tags`, which meant the question "does + # this still build?" was asked only AFTER every gate that could have stopped + # the change had already let it through. That is not a theoretical ordering + # complaint: a broken agent-runtime Dockerfile crossed PRs #669, #670 and #673 + # carrying 46, 78 and 47 green checks, and was found on main, three promotions + # later, by a human reading the registry. + # + # So the BUILD runs everywhere and the PUSH stays ref-guarded. A pull request + # proves all three images compile; only main and version tags publish to GHCR, + # which is the same publishing behaviour this job had before. docker-services: - name: Build & Push Services (GHCR) + name: Build Services (GHCR) needs: [test, test-core-domain, test-core, test-mcp-server, test-core-api, test-sdk-client, test-infra-providers] runs-on: ubuntu-latest - if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') permissions: contents: read packages: write + env: + # Single source of truth for "is this a publishing ref?", read by both the + # login step and the push flag below. On a pull_request `github.ref` is + # refs/pull/N/merge, so this is false and nothing reaches the registry. + PUBLISH: ${{ github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v') }} strategy: fail-fast: false matrix: @@ -894,22 +910,58 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to GHCR + if: env.PUBLISH == 'true' uses: docker/login-action@v4 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push ${{ matrix.service }} + - name: Build ${{ matrix.service }} (push only from main/tags) uses: docker/build-push-action@v5 with: context: ${{ matrix.context }} file: ${{ matrix.dockerfile }} - push: true + push: ${{ env.PUBLISH == 'true' }} + # Now that this runs on every PR, an uncached three-image rebuild would + # be the slowest thing in the pipeline. Scoped per service so the three + # matrix legs do not evict each other. + cache-from: type=gha,scope=${{ matrix.service }} + cache-to: type=gha,mode=max,scope=${{ matrix.service }} tags: | ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:latest ghcr.io/${{ github.repository_owner }}/${{ matrix.image }}:${{ github.sha }} + # A matrix job reports one check per leg, and each context carries its matrix + # values -- "Build Services (GHCR) (core-api, ., ./src/apps/core-api/Dockerfile)". + # Those names change whenever a path or a service does, so they are a poor thing + # for branch protection to name. This job collapses the three into one stable + # context that CAN be marked required. + docker-services-gate: + name: Services build (GHCR) + needs: [docker-services] + runs-on: ubuntu-latest + if: always() + steps: + - name: Assert every service image built + run: | + result='${{ needs.docker-services.result }}' + echo "docker-services: ${result}" + case "${result}" in + success) + echo "las tres imagenes construyen" + ;; + skipped) + # An upstream test suite failed, so the build never ran. That + # failure already has its own red check; do not double-report it. + echo "no se construyo: una suite previa fallo y lo reporta su propio check" + ;; + *) + echo "::error::al menos una imagen de servicio no construye" + exit 1 + ;; + esac + # GT-324 — deploy the freshly-pushed images to the Coolify runtime. # # REWRITTEN 2026-08-22. The previous version fanned out to THREE per-service deploy diff --git a/reference/core/control-center/deploy/README.es.md b/reference/core/control-center/deploy/README.es.md index 4c54eb4f3..9cfc3f066 100644 --- a/reference/core/control-center/deploy/README.es.md +++ b/reference/core/control-center/deploy/README.es.md @@ -68,10 +68,16 @@ Fuente de verdad: `.github/workflows/ci-cd.yml`. 1. **Build & test** — los jobs `Test`, `Test core-domain`, `Test core`, `Test mcp-server`, `Test core-api`, `Test sdk-client`, `Test contract`, `Test infra-providers` corren en cada PR a `main`/`develop`. -2. **`Build & Push Services (GHCR)`** (job `docker-services`) — corre solo en - `main` o tags `v*`. Construye `core-api`, `mcp-server`, `agent-runtime` y - publica `:latest` + `:` en GHCR usando el `GITHUB_TOKEN` incorporado (sin - secret extra). +2. **`Build Services (GHCR)`** (job `docker-services`) — construye `core-api`, + `mcp-server` y `agent-runtime` en **cada PR**, y publica `:latest` + `:` + en GHCR solo desde `main` o tags `v*`, usando el `GITHUB_TOKEN` incorporado + (sin secret extra). El build era solo-main hasta GT-679; un Dockerfile roto + cruzó tres PRs en verde porque el único job que lo habría detenido corría + después de que ya hubieran mergeado. Las tres ramas del matrix se colapsan en + un único check estable, **`Services build (GHCR)`** (job + `docker-services-gate`), que es el contexto que nombra la protección de rama: + los contextos de matrix llevan sus parámetros y cambian cada vez que cambia + una ruta. 3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) — el paso de promoción. Su guarda es: @@ -194,9 +200,9 @@ el go-live irreversible y le corresponde ejecutarlo al usuario.** Con estos definidos y `VPS_DEPLOY_ENABLED` aún sin definir, el job de deploy sigue saltándose — nada se despliega todavía. 3. **Mergea un cambio normal a `main`** (o vuelve a ejecutar el workflow) y confirma - que el job `Build & Push Services (GHCR)` está en verde y las tres imágenes aparecen - en GHCR. El job `deploy` debería seguir mostrándose como **skipped** (la guarda está - apagada). + que el job `Build Services (GHCR)` está en verde y las tres imágenes aparecen en + GHCR (en un PR el mismo job va en verde sin publicar nada). El job `deploy` debería + seguir mostrándose como **skipped** (la guarda está apagada). 4. **Opcionalmente despliega una vez manualmente desde la UI de Coolify** (cada app → Deploy) para probar que las imágenes arrancan y el env de runtime es correcto, antes de cablear el disparador automático. Observa que cada `/health` pase a verde. diff --git a/reference/core/control-center/deploy/README.md b/reference/core/control-center/deploy/README.md index 6b56e1cb4..c045b4413 100644 --- a/reference/core/control-center/deploy/README.md +++ b/reference/core/control-center/deploy/README.md @@ -62,10 +62,15 @@ Source of truth: `.github/workflows/ci-cd.yml`. 1. **Build & test** — jobs `Test`, `Test core-domain`, `Test core`, `Test mcp-server`, `Test core-api`, `Test sdk-client`, `Test contract`, `Test infra-providers` run on every PR to `main`/`develop`. -2. **`Build & Push Services (GHCR)`** (job `docker-services`) — runs only on - `main` or `v*` tags. Builds `core-api`, `mcp-server`, `agent-runtime` and - pushes `:latest` + `:` to GHCR using the built-in `GITHUB_TOKEN` (no - extra secret). +2. **`Build Services (GHCR)`** (job `docker-services`) — builds `core-api`, + `mcp-server` and `agent-runtime` on **every PR**, and pushes `:latest` + + `:` to GHCR only from `main` or `v*` tags, using the built-in + `GITHUB_TOKEN` (no extra secret). The build was main-only until GT-679; a + broken Dockerfile crossed three PRs with green checks because the only job + that would have caught it ran after they had already merged. The three matrix + legs are collapsed into one stable check, **`Services build (GHCR)`** (job + `docker-services-gate`), which is the context branch protection names — + matrix contexts carry their parameters and change whenever a path does. 3. **`Deploy services (Coolify)`** (job `deploy`, `needs: [docker-services]`) — the promotion step. Its guard is: @@ -188,8 +193,9 @@ irreversible go-live and is the user's to run.** With these set and `VPS_DEPLOY_ENABLED` still unset, the deploy job stays skipped — nothing deploys yet. 3. **Merge a normal change to `main`** (or re-run the workflow) and confirm the - `Build & Push Services (GHCR)` job is green and the three images appear in - GHCR. The `deploy` job should still show as **skipped** (guard is off). + `Build Services (GHCR)` job is green and the three images appear in GHCR (on a + PR the same job is green without publishing anything). The `deploy` job should + still show as **skipped** (guard is off). 4. **Optionally deploy once manually from the Coolify UI** (each app → Deploy) to prove the images boot and the runtime env is correct, before wiring the automatic trigger. Watch each `/health` go green.