Módulo 5: Apply On Merge The Cd Half
1. Introducción: de la revisión a la aplicación
Descripción
Los cuatro módulos anteriores construyeron todo lo que hace falta antes de tocar infraestructura real: sabes por qué un apply manual es un problema (Módulo 1), sabes leer y escribir cualquier workflow de GitHub Actions con act (Módulo 2), tienes un ci.yml que corre fmt/validate/plan en cada Pull Request y publica ese plan como evidencia de revisión (Módulo 3), y sabes cómo pasarle credenciales a un pipeline sin comprometerlas, incluida la honestidad completa sobre OIDC y ambientes con aprobación (Módulo 4). Lo que falta es la mitad que le da sentido a todo lo anterior: el apply real, disparado automáticamente, solo cuando el cambio ya se fusionó a main, aplicando exactamente el plan que una persona revisó — no uno nuevo, recalculado a ciegas.
Este módulo construye esa mitad. Al terminarlo vas a tener apply.yml —el workflow que corre terraform apply sin que nadie lo teclee—, drift.yml —que detecta cambios hechos por fuera de Terraform, de forma periódica—, y vas a entender exactamente qué riesgo evita un bloque concurrency: que, hasta ahora, no habías necesitado.
Conexión con el módulo
Este Módulo 5 es la contraparte directa del Módulo 3. Donde ci.yml respondía "¿qué cambiaría, y quién lo revisa?", apply.yml responde "¿quién lo aplica, y cuándo?" — con una respuesta muy concreta: nadie lo teclea, y solo cuando el código ya está en main. Las lecciones 2 y 3 dan el porqué y el cómo del disparador y del encadenamiento entre ci.yml y apply.yml; la lección 4 —manos a la obra— construye apply.yml completo y lo corre de verdad; la lección 5 cierra un riesgo que no existía hasta que hubo un apply real: dos corridas simultáneas escribiendo sobre el mismo state. Las lecciones 6 y 7 agregan drift.yml, la pieza que vigila la infraestructura entre despliegues. La lección 8 —el proyecto de este módulo— corre los tres workflows en secuencia, sobre el mismo repositorio que vienes construyendo desde el Módulo 1.
Qué le falta al pipeline para hacer lo que hacías a mano
Repasa, con precisión, qué hacía la guía terraform-and-iac-guide cuando el apply todavía era manual: tú corrías terraform plan, leías la salida en tu terminal, y si te convencía, corrías terraform apply — la misma persona, en la misma sesión de terminal, decidiendo y ejecutando. El Módulo 3 de esta guía ya automatizó la primera mitad: ci.yml corre ese mismo plan en cada Pull Request, sin que nadie lo teclee. Pero hasta el cierre del Módulo 4, si alguien quisiera aplicar ese cambio, todavía tendría que hacerlo a mano — ningún workflow de esta guía, hasta ahora, ha corrido terraform apply ni una sola vez.
LO QUE YA EXISTE (cierre del Módulo 4) LO QUE FALTA (este módulo)
Pull Request git push a main (fusión)
│ │
▼ ▼
ci.yml (pull_request) apply.yml (push a main)
├─ fmt ├─ descarga el MISMO plan
├─ validate │ que ci.yml calculó
├─ plan ──────► plan-output.txt ├─ terraform apply
└─ publica el plan └─ infraestructura real
(STEP_SUMMARY) creada/actualizada
│
▼
una persona revisa el plan
y aprueba la fusión drift.yml (schedule)
└─ vigila cambios fuera
de Terraform, cada día
Fíjate en la flecha punteada entre "publica el plan" y "una persona revisa": ese es, exactamente, el punto donde el Módulo 4 terminó — con el plan visible, pero sin ningún mecanismo que lo aplicara automáticamente después de la aprobación. Este módulo dibuja el resto del diagrama.
El mapa de este módulo: las 8 lecciones
| # | Lección | Qué practicas |
|---|---|---|
| 1 | Introducción (esta) | El mapa completo; qué falta para que el pipeline haga lo que hacías a mano |
| 2 | El disparador de fusión: push a main | on: push: branches: [main]; por qué apply.yml nunca debe correr sobre una rama de feature |
| 3 | Encadenar jobs con needs y pasar el plan exacto | needs: dentro de un mismo workflow; actions/upload-artifact/download-artifact para que apply use el plan que ya se revisó |
| 4 | Manos a la obra: construyendo apply.yml | Ejecutado: el workflow completo, corrido con act push, con el apply real contra LocalStack representativo |
| 5 | Control de concurrencia: evitar el doble apply | concurrency: { group, cancel-in-progress }; el mismo riesgo de lock que ya viste con Terraform |
| 6 | Detección de drift programada | on: schedule: cron:; la sintaxis completa, el temporizador real como representativo |
| 7 | Manos a la obra: corriendo el job de drift a mano | Ejecutado: drift.yml corrido con act workflow_dispatch |
| 8 | Proyecto: el pipeline completo de plan a apply | Ejecutado: ci.yml + apply.yml + drift.yml en secuencia, sobre un cambio real |
Lo que este módulo NO construye
Dos fronteras, declaradas desde ya para que las tengas presentes en cada lección:
- Rollback y redes de seguridad —revertir un
applyque salió mal, branch protection, un guardrail que impida destruir la tablaShipments— es el Módulo 6 completo. Este módulo aplica cambios; el siguiente enseña qué hacer cuando un cambio aplicado resulta ser un error. - OIDC federado de punta a punta contra una cuenta AWS real ya se nombró y se mostró en YAML en el Módulo 4 (
04-what-is-oidc-federation.md,05-the-oidc-workflow-pattern-named.md); este módulo sigue usando exactamente el mismo mecanismo de.secretscon credenciales dummy que el Módulo 4 estableció para LocalStack —apply.ymlno introduce ningún mecanismo de identidad nuevo—. La construcción completa de OIDC contra una cuenta real vive encloud-security-and-guardrails-guide.
Honestidad de ejecución de este módulo específico
El mismo patrón de las cuatro guías anteriores, con un matiz nuevo que vale la pena nombrar desde ya: el propio job de apply.yml corre de verdad con act push —no es representativo—, pero el terraform apply que ese job intenta correr contra LocalStack sí depende de un LOCALSTACK_AUTH_TOKEN válido, exactamente como cada awslocal de las guías anteriores. La diferencia con el Módulo 3 es que ahí el terraform plan lograba completarse sin necesitar esa conexión (gracias a skip_requesting_account_id, el hallazgo de esa lección); un terraform apply real, en cambio, sí necesita crear recursos de verdad contra un endpoint alcanzable — no hay forma de evitarlo, porque aplicar significa, literalmente, hacer llamadas de escritura a la API. Vas a ver, en la lección 4, el intento real, con su error real, exactamente con el mismo nivel de detalle que ya viste en el Módulo 2 (lección 8) y el Módulo 3 (lección 5).
drift.yml, en las lecciones 6 y 7, sigue el mismo patrón que ci.yml: el terraform plan de solo lectura que corre dentro de ese job sí logra completarse sin LocalStack corriendo (el mismo mecanismo del Módulo 3), así que el job en sí corre de punta a punta con salida literal — lo que queda representativo es la parte que requiere un token válido: modificar algo con awslocal directamente para simular un cambio hecho por fuera de Terraform.
Errores comunes
Asumir que apply.yml recalcula el plan (conceptual, el error que este módulo existe para prevenir). Qué pasa: alguien construye apply.yml con un step de terraform plan seguido de uno de terraform apply, pensando que es lo mismo que hace ci.yml. Por qué pasa: parece razonable que "aplicar" incluya "calcular primero qué aplicar". Cómo detectarlo: si tu apply.yml tiene la palabra plan en algún run:. Cómo corregirlo: como ya viste en el Módulo 3 (lección 2), el patrón HashiCorp/GitHub existe exactamente para evitar esto — entre el momento en que alguien aprueba un plan y el momento en que se fusiona, el estado real pudo cambiar; recalcular sería aplicar algo distinto a lo que se aprobó. La lección 3 de este módulo construye el mecanismo correcto: descargar el mismo archivo de plan que ci.yml ya calculó.
Creer que este módulo reemplaza al Módulo 3 (de alcance). Qué pasa: alguien piensa que, una vez que existe apply.yml, ci.yml deja de ser necesario. Cómo corregirlo: son piezas complementarias, no sucesivas — ci.yml sigue corriendo en cada Pull Request nuevo, para siempre; apply.yml solo entra en juego después de que un PR se fusiona. Ambos archivos coexisten en .github/workflows/ de manera permanente.
Ejercicios
Ejercicio 1 — Traduce el objetivo de este módulo sin usar la palabra "apply". En dos frases, describe qué le falta al pipeline construido en los Módulos 1 a 4 para completar el patrón GitOps completo.
Ver solución
Una respuesta completa suena, más o menos, así: "Hasta ahora, el pipeline sabe calcular qué cambiaría y mostrárselo a alguien para que lo revise, pero ningún workflow escribe infraestructura real todavía — cada cambio, una vez aprobado, todavía requeriría que una persona lo aplicara a mano. Este módulo cierra ese último paso manual: cuando el cambio llega a la rama principal, el pipeline mismo lo materializa, sin que nadie teclee el comando."
Ejercicio 2 — Ubica la frontera con el Módulo 6. Sin mirar el DISEÑO de esta guía, ¿qué crees que pasaría si el apply.yml de este módulo aplicara, por error, un cambio que destruye la tabla Shipments? ¿Este módulo lo detiene?
Ver solución
No — este módulo construye el mecanismo de aplicación, sin ningún guardrail de contenido todavía. Un guardrail que revisa el plan en JSON y falla el job si intenta destruir Shipments es, específicamente, el trabajo del Módulo 6 (lección 7). Este módulo confía en que la revisión humana del plan (Módulo 3) ya atrapó cualquier cambio peligroso antes de la fusión — el Módulo 6 agrega una segunda red de seguridad, automatizada, para cuando esa revisión humana falla.
Ejercicio 3 — Predice qué corre con act push en este módulo. Antes de leer la lección 4, ¿qué esperas que pase si corres act push sobre un apply.yml recién construido, sin LOCALSTACK_AUTH_TOKEN exportado y sin LocalStack corriendo?
Ver solución
Esperarías ver el job de act correr de verdad —descargar el artefacto del plan, instalar Terraform, correr terraform init— hasta llegar al paso de terraform apply, que fallaría con un error de conexión real (parecido al Could not connect to the endpoint URL que ya viste en el Módulo 2 y el Módulo 3), después de varios segundos de reintento. El job terminaría en rojo, pero por la razón correcta: LocalStack no está corriendo, no porque el YAML esté mal escrito.
Resumen y siguiente paso
En esta lección viste el mapa completo del Módulo 5: qué falta exactamente para que el pipeline aplique infraestructura real sin intervención humana, cómo se conecta con los cuatro módulos anteriores, y la honestidad de ejecución específica de este módulo —el job de apply.yml corre de verdad con act push, el apply contra LocalStack es representativo sin un token, y drift.yml corre de punta a punta porque su plan de solo lectura no necesita esa conexión—.
Antes de avanzar deberías poder: explicar en una frase qué le falta al pipeline de los Módulos 1-4 para estar completo; nombrar las dos piezas nuevas de este módulo (apply.yml, drift.yml); y anticipar, sin sorpresas, qué vas a poder ejecutar de verdad y qué va a quedar representativo.
La lección 2 empieza por el principio: el disparador exacto que convierte "un cambio se fusionó" en "el pipeline debe aplicarlo ahora".
Recursos
- GitHub Docs — GitHub Actions — documentación oficial, base de todo este módulo.
- HashiCorp Developer — Automate Terraform with GitHub Actions — el patrón completo, ya citado desde el Módulo 3, que este módulo termina de implementar.
- Módulo 3 de esta guía (
the-iac-pipeline-fmt-validate-plan) — elci.ymlque este módulo extiende, no reemplaza. - Módulo 4 de esta guía (
secrets-environments-and-identity) — el manejo de credenciales queapply.ymlreutiliza sin cambios.