Lanzando GitHub Actions entre distintos repositorios
Introducción
En este post veremos cómo desde un workflow de GitHub Actions en un repositorio podemos invocar otro workflow de un repositorio distinto. La primera parte muestra los distintos tipos de eventos que pueden desencadenar un workflow, centrándose en el evento repository_dispatch
. La segunda parte es un ejemplo práctico mostrando cómo dos repositorios diferentes pueden desencadenar/iniciar sus workflows mutuamente.
Eventos que desencadenan workflows
Como probablemente sepas, hay diferentes tipos de eventos que pueden iniciar la ejecución de un workflow de GitHub Actions. Los eventos más conocidos para desencadenar esta ejecución son push
, pull_request
y schedule
. Estos eventos cubren la mayoría de casos de uso, ya que normalmente queremos que los workflows se ejecuten cuando ciertos eventos se producen dentro del propio repositorio. Sin embargo, en ocasiones esto no es suficiente y es necesario que un workflow se desencadene a raíz de un evento externo (chatbot, CI externo, despliegue manual en producción tras QA, etc.).
Es aquí dónde el evento repository_dispatch
es útil (en esta publicación se empleará para lanzar workflows entre distintos repositorios).
Para lanzar este evento, es preciso hacer una petición HTTP POST a este endpoint https://api.github.com/repos/:owner/:repo/dispatches
.
Este endpoint espera 2 parámetros de entrada:
event_type
: El contenido de este parámetro se recibirá en el payload del evento del workflow como el campoaction
.client_payload
: Objecto JSON con la información que queremos propagar al workflow, el valor de este parámetro estará disponible en el campoclient_payload
del payload del evento.
Para poder utilizar este endpoint será necesario estar autenticado y autorizado para acceder al repositorio. Con ese propósito se puede crear un token de acceso personal.
El endpoint puede ser invocado utilizando cURL:
1curl -X POST https://api.github.com/repos/:owner/:repo/dispatches \
2-H 'Accept: application/vnd.github.everest-preview+json' \
3-u $your_user:$your_personal_access_token \
4--data '{"event_type": "$your_event", "client_payload": { "customField": "customValue" }}'
Es importante resaltar que la cabecera Accept tiene valor application/vnd.github.everest-preview+json
que no se corresponde con un media type estándar. A fecha de hoy este endpoint está en periodo de prueba y la API puede cambiar sin previo aviso.
Desencadenando eventos entre workflows de distintos repositorios
Para poder mostrar como podemos utilizar el evento repository_dispatch
para lanzar ejecuciones de un workflow desde un repositorio hacia otro, he diseñado dos “pipelines” distintos de nombre A y B:
Hay dos repositorios, A y B: El repositorio A
comenzará la ejecución de su workflow cuando reciba un evento de tipo push
o repository_dispatch
. El respositorio B
únicamente comenzará la ejecución de su workflow cuando reciba un evento de tipo repository_dispatch
.
La secuencia comienza con un push al repositorio A
(también hay una ejecución programada de modo que la secuencia se lance al menos una vez al día). Este primer evento desencadenará la ejecución inicial del workflow del repositorio A
. Éste workflow hará una petición al endpoint para el dispatch del repositorio B
con una acción de tipo ping
y finalizará su ejecución.
El repositorio B
comenzará una nueva ejecución de su workflow al recibir esta petición. Para el evento recibido de tipo ping
, el workflow está configurado para responder con una petición al repositorio A
con una acción de tipo pong
y finalizar su ejecución.
El repositorio A
comenzará una nueva ejecución de su workflow. Para el evento recibido de tipo pong
, el workflow enviará una petición al repositorio B
con una acción de tipo ack
(acknowledge) y finalizará su ejecución. Esta será la última ejecución de workflows en el repositorio A
para la secuencia.
Por último, el repositorio B
comenzará la ejecución definitiva de su workflow. Para el evento de tipo ack
, el workflow esta configurado para imprimir un mensaje informativo con el nombre del repositorio que invocó la acción. El workflow finaliza su ejecución y la secuencia concluye.
Access token
Uno de los principales requisitos para poder realizar peticiones al endpoint del repositorio cuyo workflow queremos arrancar es utilizar autenticación.
Lo primero es crear un token de acceso personal (PAT) siguiendo la guía de GitHub.
A continuación, necesitamos establecer las credenciales que hemos generado en un secreto en cada uno de los repositorios (A &B). Para cada uno de ellos, accederemos a settings->secrets page y añadiremos un nuevo secreto con el nombre ACCESS_TOKEN
. El valor del token será nuestro usuario dos puntos y el token de acceso personal obtenido en el paso anterior (username:token
).
Workflow del Repositorio A (ping)
1name: Remote Dispatch Action Initiator
2
3on:
4 push:
5 schedule:
6 - cron: '0 12 * * *'
7 repository_dispatch:
8
9jobs:
10 ping-pong:
11 runs-on: ubuntu-latest
12 steps:
13 - name: PING - Dispatch initiating repository event
14 if: github.event.action != 'pong'
15 run: |
16 curl -X POST https://api.github.com/repos/marcnuri-demo/actions-remote-dispatch-b/dispatches \
17 -H 'Accept: application/vnd.github.everest-preview+json' \
18 -u ${{ secrets.ACCESS_TOKEN }} \
19 --data '{"event_type": "ping", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
20 - name: ACK - Acknowledge pong from remote repository
21 if: github.event.action == 'pong'
22 run: |
23 echo "PONG received from '${{ github.event.client_payload.repository }}'" && \
24 curl -X POST https://api.github.com/repos/marcnuri-demo/actions-remote-dispatch-b/dispatches \
25 -H 'Accept: application/vnd.github.everest-preview+json' \
26 -u ${{ secrets.ACCESS_TOKEN }} \
27 --data '{"event_type": "ack", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
Este repositorio contiene el workflow “iniciador”. Se iniciará cuando reciba un evento de tipo repository_dispatch
, pero también cuando reciba eventos de tipo push
y schedule
. Los eventos push y schedule se emplearán para arrancar la acción “iniciadora” ping. Por otro lado, los eventos de tipo dispatch se utilizarán para arrancar la acción “acknowledge” (confirmación). Ambas acciones se pueden ver en la sección jobs
del workflow.
Este workflow contiene únicamente un job (tarea) con dos pasos. El primer paso se invocará únicamente cuando se cumpla la siguiente condición github.event.action != 'pong'
. Para este ejemplo, eso significa que este paso se iniciará para todos los eventos push
y schedule
y para cualquier evento repository_dispatch
que no sea de tipo pong
. De este modo, cada vez que un usuario suba un commit al repositorio, o cada día a las 12:00, este paso se ejecutará. La acción run
ejecuta el commando curl
descrito previamente. En este caso, el tipo para event_type
es ping
y tiene como destino el repositorio B
, en el payload se incluye el nombre del repositorio actual (disponible en la variable de entorno $GITHUB_REPOSITORY
). Es importante resaltar como se utilizan las credenciales definidas en el paso anterior empleando la expresión ${{secrets.ACCESS_TOKEN}}
.
El segundo paso en el job se arrancará únicamente cuando se cumpla la siguiente condición github.event.action == 'pong'
, lo que ocurrirá únicamente cuando se reciba un evento respository_dispatch
de tipo pong
. Este paso enviará (también usando curl) una petición de confirmación al repositorio B
.
Workflow del Repository B (pong)
1name: Remote Dispatch Action Responder
2
3on: [repository_dispatch]
4
5jobs:
6 ping-pong:
7 runs-on: ubuntu-latest
8 steps:
9 - name: Event Information
10 run: |
11 echo "Event '${{ github.event.action }}' received from '${{ github.event.client_payload.repository }}'"
12 - name: PONG - Dispatch response to received PING
13 if: github.event.action == 'ping'
14 run: |
15 curl -X POST https://api.github.com/repos/marcnuri-demo/actions-remote-dispatch-a/dispatches \
16 -H 'Accept: application/vnd.github.everest-preview+json' \
17 -u ${{ secrets.ACCESS_TOKEN }} \
18 --data '{"event_type": "pong", "client_payload": { "repository": "'"$GITHUB_REPOSITORY"'" }}'
Este repositorio contiene un workflow que únicamente podrá ser arrancado por control remoto, i.e. sólo se iniciará a partir de eventos repository_dispatch
.
El workflow contiene una única tarea (job) con 2 pasos. El primer paso (Event Information) se ejecutará siempre, lo que hace es sencillamente imprimir información acerca del evento que desencadenó la ejecución. El segundo paso (PONG) se ejecutará únicamente cuando se reciba un evento cuya acción sea ping
. En este paso se envía una petición (empleando el comando curl
ya descrito) al repositorio A
con un event_type
pong
.
Conclusión
En esta publicación hemos visto cómo podemos utilizar el evento repository_dispatch
de GitHub Actions para iniciar ejecuciones de workflows entre distintos repositorios de GitHub. El mismo procedimiento podría emplearse para lanzar ejecuciones manuales mediante control remoto desde otros entornos, otras herramientas de integración continua, chatbots, etc.
El código fuente completo de este artículo puede encontrarse en GitHub (A & B).