Módulo 2: Anatomy Of A Github Actions Workflow
6. Manos a la obra: simulando eventos con `act -e`
Descripción
Hasta ahora, cada act push o act pull_request que corriste usó el evento sintético que act genera por defecto —suficiente para confirmar qué disparador funciona, pero sin ningún detalle real de un Pull Request específico: sin número, sin rama, sin título. Esta lección te enseña a escribir el payload de un evento a mano, en un archivo JSON, y a pasárselo a act con -e — la técnica que hace posible simular un Pull Request completo sin tener una cuenta de GitHub, sin un repositorio remoto, y sin que nadie más participe. Esto también empieza a construir, dentro de andes-cargo-infra/, la carpeta .github/act-events/ que vas a seguir usando el resto de esta guía.
Conexión con el módulo
Esta es la primera lección del módulo que trabaja directamente dentro de andes-cargo-infra/ —el mismo repositorio que dejaste preparado en el Módulo 1, lección 8—, no en un laboratorio desechable. Todo lo que agregues aquí queda, permanentemente, como parte del proyecto real. La lección 7 hace lo mismo con secretos; la lección 8 —el proyecto de este módulo— junta ambas técnicas en el primer workflow que de verdad toca infraestructura.
Analogía: el guion del ensayo, no la función en vivo
Un evento real de GitHub —alguien abriendo un Pull Request en github.com— es como una función de teatro en vivo: pasa una sola vez, con actores reales, en un momento específico. act -e archivo.json es el ensayo con guion: le das al elenco (act) el texto exacto de lo que "el actor" (GitHub) diría en esa escena —quién abrió el PR, desde qué rama, con qué título—, y el elenco actúa la escena completa, con la misma seriedad que en la función real, sin necesitar que la función en vivo haya ocurrido siquiera una vez.
Paso 1 — Escribir pr-event.json a mano
Párate en andes-cargo-infra/ —el mismo proyecto del Módulo 1— y crea la carpeta donde van a vivir todos los eventos escritos a mano de esta guía:
cd andes-cargo-infra
mkdir -p .github/act-events
Un evento pull_request real de GitHub trae docenas de campos. Esta guía no necesita reproducirlos todos —solo los que un workflow típico de esta guía va a leer—: el número del PR, la rama de origen y de destino, el título, y quién lo abrió. .github/act-events/pr-event.json:
{
"action": "opened",
"number": 42,
"pull_request": {
"number": 42,
"title": "Add tags to the shipment documents bucket",
"head": {
"ref": "feature/add-shipment-tags",
"sha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
},
"base": {
"ref": "main",
"sha": "0f1e2d3c4b5a6978869fedcba0987654321fedc"
},
"html_url": "https://github.com/andes-cargo/andes-cargo-infra/pull/42",
"user": {
"login": "andes-cargo-dev"
}
},
"repository": {
"name": "andes-cargo-infra",
"full_name": "andes-cargo/andes-cargo-infra",
"default_branch": "main"
}
}
Fíjate que todos los valores son literales, escritos a mano, sin ninguna dependencia de una API externa: PR #42, rama de origen feature/add-shipment-tags, rama de destino main. Estos números y nombres van a repetirse en el resto de esta guía —cada vez que veas "PR #42" o feature/add-shipment-tags, es este mismo archivo el que los define. A diferencia de un PR real de GitHub —donde el número lo asigna el servidor de forma no determinista, según cuántos PRs existieron antes—, este archivo es completamente reproducible: tu PR #42 y el de cualquier otra persona que siga esta guía van a ser exactamente el mismo, siempre.
Paso 2 — Un workflow que solo imprime el payload
Este workflow no toca infraestructura de negocio —es, deliberadamente, la herramienta más simple posible para confirmar que el evento llegó completo. .github/workflows/print-event.yml:
name: print-event-payload
on:
pull_request:
jobs:
print-payload:
runs-on: ubuntu-latest
steps:
- name: Show key fields from the pull_request event
run: |
echo "Event name: ${{ github.event_name }}"
echo "PR number: ${{ github.event.pull_request.number }}"
echo "Head branch: ${{ github.event.pull_request.head.ref }}"
echo "Base branch: ${{ github.event.pull_request.base.ref }}"
echo "PR title: ${{ github.event.pull_request.title }}"
- name: Show the full raw payload
run: cat "$GITHUB_EVENT_PATH"
Fíjate en dos formas distintas de leer el mismo evento: github.event.pull_request.number (la sintaxis de contexto de GitHub Actions, para campos individuales dentro de una expresión ${{ }}) y $GITHUB_EVENT_PATH (una variable de entorno que apunta al archivo JSON completo del evento, en disco, dentro del runner — útil cuando necesitas procesar el payload completo con una herramienta como jq, no solo un campo aislado).
Paso 3 — act pull_request -e, con el payload propio
act -l
Qué esperar (salida literal, ejecutada para escribir esta lección):
Stage Job ID Job name Workflow name Workflow file Events
0 print-payload print-payload print-event-payload print-event.yml pull_request
act pull_request -e .github/act-events/pr-event.json
Qué esperar (salida literal, ejecutada para escribir esta lección):
[print-event-payload/print-payload] ⭐ Run Set up job
[print-event-payload/print-payload] 🚀 Start image=catthehacker/ubuntu:act-latest
[print-event-payload/print-payload] ✅ Success - Set up job
[print-event-payload/print-payload] ⭐ Run Main Show key fields from the pull_request event
[print-event-payload/print-payload] | Event name: pull_request
[print-event-payload/print-payload] | PR number: 42
[print-event-payload/print-payload] | Head branch: feature/add-shipment-tags
[print-event-payload/print-payload] | Base branch: main
[print-event-payload/print-payload] | PR title: Add tags to the shipment documents bucket
[print-event-payload/print-payload] ✅ Success - Main Show key fields from the pull_request event [68.8125ms]
[print-event-payload/print-payload] ⭐ Run Main Show the full raw payload
[print-event-payload/print-payload] | {
[print-event-payload/print-payload] | "action": "opened",
[print-event-payload/print-payload] | "number": 42,
[print-event-payload/print-payload] | "pull_request": {
[print-event-payload/print-payload] | "number": 42,
[print-event-payload/print-payload] | "title": "Add tags to the shipment documents bucket",
[print-event-payload/print-payload] | "head": {
[print-event-payload/print-payload] | "ref": "feature/add-shipment-tags",
[print-event-payload/print-payload] | "sha": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0"
[print-event-payload/print-payload] | },
[print-event-payload/print-payload] | "base": {
[print-event-payload/print-payload] | "ref": "main",
[print-event-payload/print-payload] | "sha": "0f1e2d3c4b5a6978869fedcba0987654321fedc"
[print-event-payload/print-payload] | },
[print-event-payload/print-payload] | "html_url": "https://github.com/andes-cargo/andes-cargo-infra/pull/42",
[print-event-payload/print-payload] | "user": {
[print-event-payload/print-payload] | "login": "andes-cargo-dev"
[print-event-payload/print-payload] | }
[print-event-payload/print-payload] | },
[print-event-payload/print-payload] | "repository": {
[print-event-payload/print-payload] | "name": "andes-cargo-infra",
[print-event-payload/print-payload] | "full_name": "andes-cargo/andes-cargo-infra",
[print-event-payload/print-payload] | "default_branch": "main"
[print-event-payload/print-payload] | }
[print-event-payload/print-payload] | }
[print-event-payload/print-payload] ✅ Success - Main Show the full raw payload [67.267042ms]
[print-event-payload/print-payload] 🏁 Job succeeded
Compara esta salida con la que obtendrías de act pull_request sin el flag -e (el evento sintético del Módulo 1/lección de disparadores): ahí, github.event.pull_request.number no existiría —el evento por defecto de act no incluye un objeto pull_request completo, con número, ramas y título—. El flag -e es lo que convierte una simulación genérica ("algún tipo de PR pasó") en una simulación específica ("el PR #42, de esta rama exacta, con este título exacto") — el nivel de detalle que un workflow real necesita para tomar decisiones (por ejemplo, comentar en el PR correcto, o usar el número del PR como parte de un nombre de recurso temporal).
Commitea este avance —recuerda que .github/act-events/ no es un estándar de GitHub; es una convención propia de esta guía para mantener organizados los eventos escritos a mano:
git add -A
git commit -m "Add pr-event.json and print-event.yml to practice act -e"
Profundización: -e funciona para cualquier evento, no solo pull_request
Aunque esta lección lo muestra con pull_request —el caso más útil para esta guía, porque un PR real trae mucho contexto que vale la pena simular con precisión—, el mecanismo -e archivo.json es genérico: podrías escribir un push-event.json con un mensaje de commit específico, o un workflow_dispatch-event.json con inputs personalizados para un workflow que los use. El formato exacto del JSON depende de qué evento estés simulando — la documentación oficial de GitHub Actions (Recursos, abajo) documenta la forma completa de cada tipo de evento, campo por campo, si en algún momento necesitas un campo que esta lección no incluyó.
Errores comunes
Escribir un pr-event.json con campos faltantes que un workflow real necesita (de estructura). Qué pasa: alguien escribe un evento con solo {"number": 42}, sin el objeto pull_request anidado completo, y el workflow falla al intentar leer github.event.pull_request.head.ref. Por qué pasa: no es obvio, sin ver la documentación, que GitHub anida casi todo el detalle real dentro de un objeto pull_request, no en la raíz del evento. Cómo detectarlo: un step que espera un valor de github.event.pull_request.algo lo recibe vacío, sin ningún error explícito de YAML —simplemente el valor sale en blanco—. Cómo corregirlo: compara tu evento contra la estructura real que documenta GitHub Actions para el tipo de evento que estás simulando (Recursos, abajo), o contra el pr-event.json completo de esta lección como plantilla.
Olvidar -e y preguntarse por qué el evento "no tiene detalle" (de flujo). Qué pasa: alguien corre act pull_request sin -e archivo.json, ve que el job corre, y se sorprende cuando github.event.pull_request.number sale vacío. Por qué pasa: act pull_request sin más es un comando válido —corre el evento sintético por defecto—, así que no hay ningún error que avise del olvido. Cómo detectarlo: si tus variables de contexto de PR (number, head.ref, etc.) salen vacías sin que el job falle. Cómo corregirlo: revisa que el comando incluya explícitamente -e ruta/al/evento.json — sin ese flag, act no tiene forma de saber que existe un archivo con el detalle que esperas.
Ejercicios
Ejercicio 1 — Extiende el evento con un campo nuevo. Sin mirar esta lección, agrega un campo "labels": [{"name": "infrastructure"}] dentro del objeto pull_request de tu propio pr-event.json, y escribe la expresión ${{ }} que usarías en un step para leer el nombre de la primera etiqueta.
Ver solución
La expresión sería ${{ github.event.pull_request.labels[0].name }} — labels es un array (fíjate los corchetes [] en el JSON), así que se accede por índice, empezando en 0, igual que en la mayoría de los lenguajes de programación. El patrón general: cada nivel de anidamiento del JSON del evento se refleja exactamente en la ruta de la expresión github.event....
Ejercicio 2 — Explica por qué el PR #42 es reproducible. Un colega, que siguió esta misma guía en otra máquina, te muestra su propio pr-event.json — y también dice "PR #42". ¿Coincidencia, o algo más? Explica en una frase.
Ver solución
No es coincidencia — es determinismo por diseño. A diferencia de un PR real de GitHub, donde el número lo asigna el servidor según cuántos PRs existieron antes en ese repositorio específico (algo que varía de repositorio a repositorio), el número 42 en este pr-event.json es un valor que el alumno escribió a mano, siguiendo exactamente el archivo de esta lección. Cualquier persona que siga esta guía va a escribir el mismo 42, porque no hay ninguna API externa generando ese número — está en el texto de la lección, punto.
Ejercicio 3 — Diagnostica un campo vacío. Un compañero corre su workflow con act pull_request -e pr-event.json y github.event.pull_request.title sale vacío, aunque el job corre sin errores. ¿Cuáles son las dos causas más probables, según lo que viste en "Errores comunes"?
Ver solución
Las dos causas más probables: (1) su pr-event.json no tiene el campo title dentro del objeto pull_request —un típico error de estructura incompleta—, o (2) el campo existe, pero está mal anidado —por ejemplo, en la raíz del JSON en vez de dentro de pull_request—. En ambos casos, el job no falla porque leer un campo inexistente de un objeto JSON dentro de una expresión ${{ }} simplemente produce una cadena vacía, no un error — por eso este tipo de problema pasa desapercibido si no revisas la salida con atención.
Resumen y siguiente paso
En esta lección escribiste pr-event.json a mano —PR #42, feature/add-shipment-tags → main, completamente determinista— y lo usaste con act pull_request -e para correr un workflow que imprimió cada campo del payload, confirmado con salida literal. También dejaste, dentro de andes-cargo-infra/, la primera pieza de .github/act-events/, una convención propia de esta guía que vas a seguir usando en los módulos siguientes.
Antes de avanzar deberías poder: escribir un evento pull_request mínimo pero completo, de memoria; explicar la diferencia entre act pull_request con y sin -e; y ubicar cualquier campo de un evento real dentro de la sintaxis github.event.... de una expresión.
Tienes eventos resueltos. La lección 7 cierra el par de técnicas de simulación que te faltan: pasarle secretos a act sin comprometerlos jamás en un commit.
Recursos
- nektosact.com — User Guide — documentación oficial de
act -ey la simulación de eventos personalizados. - GitHub Docs — Events that trigger workflows:
pull_requestpayload — la estructura real y completa del payload de un eventopull_request, si necesitas un campo que esta lección no incluyó. - GitHub Docs — Context and expression syntax — cómo se leen los contextos (
github.event, entre otros) dentro de una expresión${{ }}.