La configuración por defecto de Atlantis monitorea los archivos de las hojas. Cuando una hoja cambia, Atlantis vuelve a planificar ese proyecto; cuando cambia un módulo compartido, no pasa nada — hasta que alguien aplica una hoja consumidora semanas después y descubre la regresión. El contrato que Atlantis anuncia ("los planes corren ante cambios de archivo") excluye silenciosamente los cambios de archivo más importantes que hacés.
Agregar modules/**/*.tf a when_modified invierte el contrato. Un PR de módulo compartido ahora vuelve a planificar a todos los consumidores en el mismo PR. Las regresiones aparecen antes del merge, no en el momento del apply. El cambio son una o dos líneas de YAML por proyecto, y es la edición de mayor impacto que hice a un setup de Atlantis en el último año.
El problema de monitorear solo las hojas
Una configuración típica de proyecto en Atlantis se ve así:
# atlantis.yaml
projects:
- name: brand-a-prod-api
dir: live/brand-a/prod/api
when_modified: ["*.tf", "*.tfvars"]
Esto monitorea los archivos propios de la hoja. No monitorea el módulo fuente que llama la hoja. Entonces, cuando corregís un bug en modules/ecs-service/main.tf, Atlantis no sabe que brand-a/staging/api y brand-b/prod/api ambos llaman ese módulo y podrían verse afectados.
El modo de fallo que sigue es mecánico. La corrección llega, el CI pasa en verde, el PR se mergea. Dos días después alguien corre atlantis apply en brand-b/prod/api por una razón no relacionada y encuentra la regresión que no detectaste. La conversación de blame es poco útil, porque nadie se propuso enviar la regresión; el tooling simplemente no la mostró.
Esta es la clase de bug más fácil de descartar como "error humano" y la más difícil de prevenir realmente por ese camino. El revisor no puede ver lo que no puede ver. El CI no puede fallar en un plan que nunca corrió.
La solución: incluir el código fuente del módulo en when_modified
Tenés dos formas razonables de expresar esto. La versión estrecha apunta a un módulo específico:
when_modified:
- "*.tf"
- "*.tfvars"
- "../../../../modules/ecs-service/**/*.tf"
La versión amplia cubre todos los módulos del repo:
when_modified:
- "*.tf"
- "*.tfvars"
- "modules/**/*.tf"
Las rutas son relativas a la raíz del repositorio, que es lo que Atlantis usa para detectar cambios. Cada hoja que lista modules/**/*.tf en su when_modified se vuelve a planificar cuando cualquier módulo cambia. La versión estrecha reduce ruido pero multiplica la cantidad de líneas de glob que tenés que mantener; la versión amplia es un glob por proyecto y dispara replans en cada PR de módulo.
Por defecto voy con el glob amplio. El costo son un puñado de planes "sin cambios" en los PRs de módulos compartidos; el beneficio es que cada consumidor queda verificado por plan, automáticamente, contra la versión nueva del módulo.
Si tu repo tiene una estructura Terragrunt por capas, la misma idea se expresa a través del layout: el when_modified de cada hoja lista la ruta del módulo, y la ruta del módulo es estable porque el layout es por capas. La combinación de "las hojas descubren módulos a través de brand.hcl" y "Atlantis re-planifica ante cambios de módulo" es lo que hace que evolucionar módulos compartidos sea seguro.
El grafo de dependencias que esto expresa
El grafo ya existe; el glob when_modified simplemente le enseña a Atlantis a recorrerlo. Cada hoja que lista modules/**/*.tf está afirmando: "dependo de la superficie de este módulo; volveme a planificar cuando se mueva". El comentario del PR se convierte en un resumen del radio de impacto: cada proyecto que llama al módulo muestra su diff de plan, y el diff que importa (un cambio de comportamiento en un consumidor) salta a la vista contra los diffs que no (ocho planes idénticos sin cambios).
Cómo cambia la revisión de PRs
Una corrección de módulo compartido que antes generaba un plan — la hoja que tocaste en el mismo PR — ahora genera planes para todos los consumidores. El comentario de Atlantis en el PR se convierte en un resumen del radio de impacto: cada proyecto que llama al módulo muestra su diff de plan. De ahí siguen tres cosas.
Primero, el revisor puede ver, en el PR, si el cambio del módulo rompió algún consumidor. Sin referencias cruzadas manuales, sin esperar que alguien ejecute terragrunt run-all plan localmente antes de mergear, sin un hilo de slack que empieza con "¿chequeaste brand-b?". La prueba está en el comentario.
Segundo, el autor del cambio de módulo es la persona que explica cualquier diff sorpresivo. Si aparece un cambio de comportamiento en un consumidor que no esperaba, le toca a él explicarlo. La responsabilidad aterriza del lado correcto del merge.
Tercero, los planes "sin cambios" se vuelven una señal positiva. Cuando el comentario es una pared de líneas "sin cambios", el revisor aprende que el cambio de módulo es genuinamente retrocompatible. Esa es exactamente la información que un revisor necesita y la más difícil de obtener por cualquier otro medio.
El trade-off: ruido de planes y presión de timeouts
La contra honesta es que esto multiplica el volumen de planes en PRs de módulos compartidos. Un repo con treinta hojas que llaman a modules/ecs-service va a postear treinta planes ante un cambio de una línea en el módulo. Eso es costo real en tres lugares: minutos de CI, carga del servidor de Atlantis y atención del revisor.
Conviví con cada uno. Los minutos de CI son baratos comparados con el costo de un único apply malo. La carga del servidor de Atlantis es manejable con parallel_plan: true y un runner del tamaño correcto. La atención del revisor es la restricción real, y la forma de manejarla es output de plan colapsado por defecto: Atlantis usa bloques <details> en los comentarios y los revisores aprenden a buscar las líneas de diff no vacías.
La preocupación por timeouts es más real. Si un plan individual tarda noventa segundos y tenés treinta serializados, te pasás del timeout por defecto de Atlantis. La solución es paralelismo, no evitarlo — activá parallel_plan: true a nivel repo y confirmá que tu runner tiene suficientes slots concurrentes.
Contraargumento: esto convierte cada PR de módulo en un release
Una contra seria es que re-planificar a todos los consumidores hace que un PR de módulo compartido se sienta como un evento de release, y los eventos de release tienen más fricción. Es cierto y es intencional. Un cambio de módulo compartido es un evento de release, porque tiene radio de impacto sobre múltiples consumidores. El setup previo escondía ese hecho y dejaba que los cambios de módulo se sintieran baratos; el setup nuevo iguala el costo social del cambio a su radio de impacto real.
Si la fricción es demasiado alta para el tipo de cambios que hacés, la respuesta no es bajar el glob when_modified — es separar "cambio de módulo" de "abstracción fina sobre inputs". Los cambios que no deberían sentirse como releases son usualmente inputs u outputs que deberían haber sido un bloque variable u output en el consumidor.
Así que
Si mantenés Atlantis en un repo con módulos compartidos, agregá modules/**/*.tf al when_modified de un proyecto hoy y abrí un PR de módulo. Mirá el output del plan. Si encontrás una regresión en un consumidor que no esperabas, ese único PR ya pagó el cambio. Si no, tenés prueba de que el cambio de módulo es seguro — y esa es una señal de merge más fuerte que cualquier cantidad de checks verdes sobre la hoja que tocaste.
El costo es ruido de planes. El beneficio es no volver a descubrir una regresión de módulo en el apply. Ese es un trade que volvería a hacer cada vez.
[VERIFY: parallel_plan de Atlantis es un setting a nivel proyecto en las versiones recientes; confirmar el nombre exacto de la clave y el umbral de versión contra los docs de Atlantis antes de citar textualmente.]
Comentarios
Enviando…