Módulo 3: The Iac Pipeline Fmt Validate Plan
7. Publicar el `plan` como evidencia de revisión
Descripción
Un terraform plan que solo vive en los logs de un job, enterrado entre cientos de líneas de salida de docker exec, no cumple la promesa de la lección 1: no es un artefacto que alguien pueda revisar cómodamente antes de aprobar una fusión. Esta lección cierra esa brecha con dos técnicas distintas, y con honestidad completa sobre cuál corre de verdad en esta guía y cuál solo se muestra: publicar el plan en el resumen del job ($GITHUB_STEP_SUMMARY) —ejecutado, confirmado bajo act— y el patrón real de mercado, comentar el plan directamente en el Pull Request vía actions/github-script —el mismo mecanismo citado del tutorial oficial de HashiCorp en la lección 2, mostrado en YAML completo, etiquetado como no-ejecutable sin un Pull Request real de GitHub.com—.
Conexión con el módulo
La lección 6 dejó plan-output.txt dentro del contenedor del job, con el plan completo de Andes Cargo. Esta lección toma ese archivo y lo convierte en algo que un humano puede leer sin bucear entre logs de Docker. La lección 8 —el proyecto de este módulo— corre ci.yml completo, con esta pieza incluida, de punta a punta.
Analogía: el resumen ejecutivo, no la transcripción completa de la reunión
Una transcripción completa de una reunión de dos horas contiene toda la información, técnicamente — pero nadie la lee entera para decidir algo rápido. Un buen resumen ejecutivo extrae exactamente lo que alguien necesita para decidir, en un formato que se lee en un minuto. Los logs completos de act pull_request (o de un job real de GitHub Actions) son la transcripción: todo está ahí, pero encontrar el Plan: N to add entre cientos de líneas de docker exec y descarga de providers es exactamente el tipo de fricción que hace que la gente deje de leer. $GITHUB_STEP_SUMMARY y el comentario en el PR son los dos formatos de resumen ejecutivo que esta lección construye: el mismo contenido, presentado donde alguien realmente lo va a leer antes de aprobar.
Parte 1 (EJECUTADO) — Publicando en $GITHUB_STEP_SUMMARY
$GITHUB_STEP_SUMMARY es una variable de entorno que GitHub Actions —y act— inyectan en cada step: apunta a un archivo temporal en disco donde cualquier cosa que escribas se convierte en el resumen visual de la corrida completa del job, en formato Markdown, visible en la pestaña Summary de una corrida real de GitHub Actions. Agrega este step, después de Terraform plan:
- name: Publish the plan to the job summary
run: |
{
echo "## Terraform plan — andes-cargo-infra"
echo '```'
cat plan-output.txt
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
El patrón es simple: agrupar varios echo (y un cat del archivo que la lección 6 generó) dentro de llaves { ... }, y redirigir todo ese bloque, de una sola vez, hacia $GITHUB_STEP_SUMMARY con >> (agregar al final, no sobrescribir — importante si más de un step de tu job escribe al resumen). Las comillas triples de Markdown (```) alrededor del contenido del plan hacen que, en la interfaz real de GitHub, se muestre con formato de bloque de código, no como texto corrido.
act pull_request -e .github/act-events/pr-event.json -j terraform-checks
Qué esperar (salida literal, ejecutada para escribir esta lección — fíjate en el prefijo ⚙ Summary -, la confirmación de que act sí soporta $GITHUB_STEP_SUMMARY):
[ci/terraform-checks] ⭐ Run Main Publish the plan to the job summary
[ci/terraform-checks] 🐳 docker exec cmd=[bash -e /var/run/act/workflow/9] user= workdir=
[ci/terraform-checks] ✅ Success - Main Publish the plan to the job summary [70.591959ms]
[ci/terraform-checks] ⚙ Summary - ## Terraform plan — andes-cargo-infra
data.archive_file.lambda_zip: Reading... data.archive_file.lambda_zip: Read complete after 0s [id=772c6895d44fc6c470f613203a7df0faeee06e87] data.aws_iam_policy_document.require_https: Reading... data.aws_iam_policy_document.app_server_permissions: Reading... data.aws_iam_policy_document.lambda_permissions: Reading... data.aws_iam_policy_document.lambda_trust: Reading... data.aws_iam_policy_document.ec2_trust: Reading... data.aws_iam_policy_document.lambda_permissions: Read complete after 0s [id=4087165242] data.aws_iam_policy_document.ec2_trust: Read complete after 0s [id=2851119427] data.aws_iam_policy_document.lambda_trust: Read complete after 0s [id=2690255455] data.aws_iam_policy_document.require_https: Read complete after 0s [id=4186015114] [ ... el resto del plan-output.txt completo, incluido el "Plan: 12 to add" ... ]
Esto **no** es una simulación de lo que se vería — es `act` mostrándote, con el prefijo `⚙ Summary -`, exactamente el contenido que escribió al archivo de resumen, confirmado línea por línea contra `plan-output.txt`. Es la confirmación directa —investigada y verificada, según el diseño de esta guía— de que `$GITHUB_STEP_SUMMARY` funciona bajo `act`, aunque `act` no tenga una interfaz web donde "ver la pestaña Summary": el mecanismo de escritura al archivo es el mismo, y `act` te lo muestra en la terminal en vez de en una página renderizada.
En un repositorio real de GitHub, este mismo contenido aparecería, con formato Markdown completo, en la pestaña **Summary** de la corrida del workflow — el primer lugar donde alguien que revisa un Pull Request miraría, sin tener que abrir los logs completos de ningún step.
---
## Parte 2 (REPRESENTATIVO) — Comentar el `plan` directamente en el Pull Request
El patrón que HashiCorp cita en su tutorial oficial —y que la lección 2 de este módulo ya nombró— va un paso más allá del resumen del job: publica el `plan` como un **comentario visible directamente en la conversación del Pull Request**, usando `actions/github-script`, una Action que te da acceso a la API completa de GitHub (a través de Octokit, la librería oficial) dentro de un step de JavaScript.
```yaml
- name: Comment the plan on the pull request
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const plan = fs.readFileSync('plan-output.txt', 'utf8');
const maxLength = 65000;
const truncated = plan.length > maxLength
? plan.slice(0, maxLength) + '\n... (plan truncated)'
: plan;
const body = `## Terraform plan — andes-cargo-infra
<details><summary>Show plan output</summary>
\`\`\`
${truncated}
\`\`\`
</details>
*Pushed by @${context.actor}, workflow run #${context.runNumber}*`;
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: body,
});
Por qué este step está etiquetado representativo, con la razón técnica exacta: github.rest.issues.createComment es una llamada real a la API REST de GitHub —necesita un issue_number real (el número de un Pull Request que exista de verdad en un repositorio de GitHub.com) y un token con permiso de escribir comentarios—. act no tiene ningún Pull Request real al cual publicarle nada: el pr-event.json que escribiste en el Módulo 2 simula el payload del evento (context.issue.number resolvería a 42, el número que escribiste a mano), pero no existe ningún servidor de GitHub del otro lado esperando esa llamada. Correr este step bajo act fallaría al intentar autenticarse y llamar a una API que, en esta simulación, no está ahí — no porque el YAML esté mal escrito, sino porque, por diseño, no hay Pull Request real contra el cual comentar.
Fíjate en tres piezas del script que vale la pena entender, aunque no lo ejecutes:
context.repo.owner/context.repo.repo/context.issue.number— el contexto queactions/github-scriptexpone automáticamente, derivado del evento que disparó el workflow; en un Pull Request real, estos tres valores identifican exactamente dónde publicar el comentario.<details><summary>...</summary>dentro del Markdown — un plan de Andes Cargo con 12 recursos ya es largo; en un proyecto real, con decenas o cientos de recursos, un comentario que muestra todo sin colapsar haría el Pull Request casi imposible de leer. El bloque<details>colapsa el plan por defecto, visible solo si alguien hace clic — el mismo patrón que ya usas en los Ejercicios de cada lección de esta guía.- El límite
maxLength— la API de GitHub tiene un límite real de tamaño para el cuerpo de un comentario; unplande un proyecto grande podría superarlo, así que truncar explícitamente (con un aviso claro) es más seguro que dejar que la llamada falle sin explicación.
Pointer, no construcción: el patrón real de mercado para evitar comentarios duplicados en cada nueva corrida —buscar y actualizar un comentario existente del bot, en vez de crear uno nuevo cada vez, con github.rest.issues.listComments seguido de updateComment o createComment según corresponda— es exactamente lo que muestra el tutorial de HashiCorp citado en la lección 2. Esta guía no lo construye completo aquí porque, sin un Pull Request real donde probarlo, no hay forma honesta de confirmar que funciona — lo nombra, con la referencia exacta, para quien lo necesite en un repositorio real.
Comparando las dos formas de evidencia
$GITHUB_STEP_SUMMARY (ejecutado aquí) | Comentario en el PR (mostrado, no ejecutado) | |
|---|---|---|
| Dónde vive | Pestaña Summary de la corrida del workflow | Conversación del Pull Request, junto a los demás comentarios |
| Quién lo ve primero | Alguien que entra a revisar la corrida del CI específicamente | Alguien que revisa el Pull Request en general, sin necesitar entrar a Actions |
| Se actualiza con cada push | Cada corrida genera su propio resumen nuevo | El patrón real actualiza el mismo comentario, sin acumular uno por cada push |
Corre bajo act | Sí, confirmado en esta lección | No — necesita un Pull Request real de GitHub.com |
| Requiere permisos adicionales | No — es parte del entorno estándar del job | Sí — el token necesita permiso de escritura en Issues/Pull Requests |
Ninguna de las dos reemplaza a la otra en un pipeline real: muchos equipos usan ambas, porque cubren dos momentos distintos de revisión.
Errores comunes
Usar > en vez de >> al escribir a $GITHUB_STEP_SUMMARY (de sintaxis, silencioso pero real). Qué pasa: alguien escribe echo "algo" > "$GITHUB_STEP_SUMMARY" en más de un step del mismo job, y cada uno sobrescribe lo que el step anterior había escrito, en vez de agregarse. Por qué pasa: > y >> se ven casi idénticos, y ambos "funcionan" en el sentido de que no producen ningún error. Cómo detectarlo: si tu resumen final solo muestra el contenido del último step que escribió, y el de los anteriores desapareció. Cómo corregirlo: usa siempre >> para $GITHUB_STEP_SUMMARY, salvo que tengas una razón explícita para empezar el resumen desde cero en ese punto exacto del job.
Intentar correr el step de actions/github-script con act y sorprenderse del fallo (de expectativa, el punto central de esta lección). Qué pasa: alguien copia el YAML de la "Parte 2" tal cual, lo agrega a ci.yml, y corre act pull_request -e pr-event.json, esperando ver un comentario simulado en algún lado. Cómo detectarlo: un error de autenticación o de red al intentar llamar a la API de GitHub, sin ningún Pull Request real de destino. Cómo corregirlo: este step está diseñado, en esta guía, para leerse y entenderse, no para correr bajo act — la razón técnica exacta está en la sección de arriba. Si quieres verlo funcionar de verdad, necesitas un repositorio real de GitHub con un Pull Request real abierto (tema que el Módulo 8, lección 5, retoma con honestidad final sobre qué solo se vive con una cuenta de GitHub real).
Olvidar que plan-output.txt solo existe si el step de la lección 6 corrió antes en el mismo job (de orden). Qué pasa: alguien agrega el step de publicación del resumen en un job distinto al que corrió terraform plan, y el cat plan-output.txt falla con "No such file or directory". Cómo corregirlo: recuerda que cada job de act corre en su propio contenedor, sin compartir sistema de archivos con otros jobs del mismo workflow (a menos que uses actions/upload-artifact/download-artifact, el mecanismo que el Módulo 5 usa para pasar el plan de ci.yml a apply.yml) — dentro de un mismo job, en cambio, todos los steps sí comparten el mismo sistema de archivos, por eso el tee de la lección 6 y el cat de esta lección funcionan sin ningún paso adicional.
Ejercicios
Ejercicio 1 — Explica por qué el comentario en el PR no corre bajo act, sin decir "necesita internet". Un colega te dice: "seguro falla porque act no tiene internet". Corrígelo con la razón técnica exacta de esta lección.
Ver solución
No es un problema de conectividad en general —de hecho, act sí tiene acceso a internet, como confirmaste instalando terraform, awslocal y tflocal con descargas reales en lecciones anteriores—. El problema es que github.rest.issues.createComment necesita un Pull Request real, que exista en un repositorio real de GitHub.com, identificado por un issue_number verdadero — y ese Pull Request, simplemente, no existe: pr-event.json simula el payload del evento, no crea un objeto real del lado de GitHub. La llamada fallaría porque el destino no existe, no porque falte conexión a internet.
Ejercicio 2 — Decide qué formato de evidencia usarías para cada escenario. Para cada situación, indica si usarías $GITHUB_STEP_SUMMARY, el comentario en el PR, o ambos: (a) quieres que cualquiera que abra el Pull Request vea el plan sin tener que ir a la pestaña Actions; (b) quieres una copia simple del plan, ligada a esa corrida específica, sin preocuparte por duplicados en cada push; (c) tu equipo revisa Pull Requests casi exclusivamente desde la vista de conversación, casi nunca entra a la pestaña Actions.
Ver solución
(a) El comentario en el PR — es el único de los dos que aparece directamente en la conversación del Pull Request, sin que nadie tenga que navegar a otra pestaña. (b) $GITHUB_STEP_SUMMARY — cada corrida genera su propio resumen, sin ninguna lógica adicional de buscar y actualizar un comentario existente; es la opción más simple si no te importa tener un resumen por corrida. (c) El comentario en el PR (posiblemente ambos, pero el comentario es indispensable aquí) — si el equipo casi no entra a Actions, un resumen que vive exclusivamente ahí nunca lo verían.
Ejercicio 3 — Predice el resultado de un plan muy largo. Si el plan de un proyecto futuro de Andes Cargo superara los 65,000 caracteres que el script de la Parte 2 usa como límite, ¿qué verías en el comentario del Pull Request, según el código de esta lección?
Ver solución
Verías el plan truncado exactamente en el carácter 65,000, seguido de la línea ... (plan truncated) — el script corta explícitamente el texto con .slice(0, maxLength) y agrega ese aviso, en vez de dejar que la llamada a createComment falle sin explicación por exceder el límite real de la API de GitHub para el cuerpo de un comentario. No verías un error — verías un comentario incompleto pero honesto sobre estar incompleto, publicado con éxito.
Resumen y siguiente paso
En esta lección cerraste la mitad de CI del pipeline con dos formas de convertir un plan en evidencia de revisión: $GITHUB_STEP_SUMMARY, confirmado funcionando bajo act con salida literal (el prefijo ⚙ Summary - que viste, línea por línea, coincide con plan-output.txt), y el comentario directo en el Pull Request vía actions/github-script —el patrón citado del tutorial oficial de HashiCorp, mostrado en YAML completo y explicado paso a paso, etiquetado honestamente como no-ejecutable sin un Pull Request real—.
Antes de avanzar deberías poder: escribir el patrón { echo ...; cat archivo; } >> "$GITHUB_STEP_SUMMARY" de memoria; explicar con la razón técnica exacta por qué el comentario en el PR no corre bajo act; y decidir, para un escenario dado, cuál de las dos formas de evidencia (o ambas) tiene más sentido.
La lección 8 —el proyecto de este módulo— junta todo lo construido en las lecciones 4 a 7 en un único ci.yml completo, corrido de punta a punta con act pull_request -e pr-event.json, sobre un cambio real al HCL de Andes Cargo.
Recursos
- GitHub Docs — Adding a job summary — documentación oficial de
$GITHUB_STEP_SUMMARY, usado y confirmado bajoacten esta lección. - actions/github-script — el repositorio oficial de la Action usada en la Parte 2.
- HashiCorp Developer — Automate Terraform with GitHub Actions — la fuente original del patrón de comentar el plan en el Pull Request, citada en la lección 2.
- GitHub REST API — Create an issue comment — la llamada exacta que
github.rest.issues.createCommentenvuelve, para quien quiera construirlo completo contra un repositorio real.