Skip to main content

Primer Ejemplo

Ahora voy a poblar el repositorio con más cosas para tener un ejemplo con código real simple para hacer más cosas.

Para ejecutar la aplicación solo hay que instalar las dependencias y ejecutar:

npm install

added 232 packages, and audited 233 packages in 5s

16 packages are looking for funding
run `npm fund` for details

11 vulnerabilities (5 moderate, 5 high, 1 critical)

To address all issues, run:
npm audit fix

Run `npm audit` for details.
5s
npm run dev

> [email protected] dev
> vite

VITE v3.0.9 ready in 181 ms

➜ Local: http://localhost:5173/
➜ Network: use --host to expose


## Para probar

npm test

> [email protected] test
> vitest run


RUN v0.22.1 /home/david/gh-first-action

(node:2305122) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead.
(Use `node --trace-deprecation ...` to show where the warning was created)
✓ src/components/MainContent.test.jsx (2)

Test Files 1 passed (1)
Tests 2 passed (2)
Start at 16:01:00
Duration 1.47s (transform 331ms, setup 65ms, collect 174ms, tests 80ms)

Ahora vamos a empezar a montar workflows para este código. Este código ya posee las pruebas para que podamos ejecutar en el workflow también.

Vamos a hablar un poco sobre los varios tipos de eventos que pueden existir. Esta es una página muy útil pues contiene las informaciones que puedes necesitar para cualquier caso de uso que tengas.

  • Eventos del repositorio

    • push
    • pull_request
    • create
    • fork
    • issues
    • issues_comment
    • watch
    • discussion
    • Varios otros
  • Otros

    • workflow_dispatch cuando ejecutamos manualmente
    • repository_dispatch cuando hacemos una solicitud api rest específica de github
    • schedule dispara de acuerdo con un cronograma.
    • workflow_call

    Estos tipos de eventos poseen sus variaciones. Por ejemplo, pull request puede tener las variaciones de opened, closed y mucho más.

Los eventos elegidos dependen de lo que necesitas hacer. En nuestro caso, vamos a ejecutar las pruebas ya desarrolladas siempre que un push sea ejecutado para el repositorio.

En el primer caso, ejecutamos comandos que no dependen de nuestro repositorio. En este caso, tendremos que clonar el repositorio en el runner. Para ganar velocidad, es interesante dividir los trabajos que dependen de los mismos archivos y crear condición en los steps. Si fuéramos a hacerlo manualmente, necesitaríamos instalar Git en el runner, configurar, tener las credenciales en caso de que el repositorio sea privado para clonar el código. Ahí es donde entran las actions, que son facilidades que GitHub nos trae.

De la misma forma, necesitamos que Node.js estuviera instalado en el runner para ejecutar los comandos de instalar las dependencias y después probar el código.

name: Test Project

on: push # Podríamos elegir hasta en qué rama vamos a ejecutar, en este caso está ejecutando en cualquier rama.

jobs: # en plural siempre
first-job:

runs-on: ubuntu-latest
steps:

- name: Get Code # Hacer la descarga del código
run: git clone ... # Continúa en la explicación

- name: Install dependencies
run: npm install

- name: Test Code
run: npm run test

Node.js ya está instalado dentro de ubuntu-latest por lo que podemos ejecutar directamente un comando.

Action

Las actions son aplicaciones personalizadas o de terceros que ejecutan tareas repetitivas. Por ejemplo, el hecho de clonar un repositorio será prácticamente algo que siempre necesitarás hacer.

En lugar de usar el comando run que estará esperando un comando de shell, usamos action. Las actions pueden ser desarrolladas en caso de algo personalizado o distribuidas a través de un marketplace oficial o comunitario.

Vamos a usar la action checkout en lugar de crear todo un script para hacer el get code. Esta action es mantenida por el equipo de github. En la documentación muestra cómo usar esta action.

alt text

Siempre es bueno analizar las actions del creador y verificar esto.

alt text

Este emblema puede darte más confianza de que esta acción no está haciendo nada malicioso, al fin y al cabo estamos hablando de código fuente que creará tu aplicación. Más adelante se abordará sobre seguridad y las acciones de github y algunas cosas de las que debemos tener cuidado.

Crea este workflow en el repositorio en .github/workflow en un nuevo archivo yaml test.yaml.

name: Test Project

on: push # Podríamos elegir hasta en qué rama vamos a ejecutar, en este caso está ejecutando en cualquier rama.

jobs: # en plural siempre
Test:

runs-on: ubuntu-latest
steps:

- name: Get Code # Hacer la descarga del código
uses: actions/checkout@v4
# El bloque de abajo recibe las configuraciones necesarias para la ejecución
# Cogí algunos parámetros por defecto solo para mostrar cómo podríamos referenciar un valor a ser pasado y cómo apuntar
with:
# No es necesario pasar nada abajo, lo mostré para ver cómo usamos valores a los que el actions tiene acceso.
repository: ${{ github.repository }} # Este es el valor por defecto
token: ${{ github.token }} # Este es el valor por defecto

- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '16'

- name: Test NodeJS Version
run: node --version

- name: Install dependencies
run: npm ci # también instala las dependencias pero es más seguro que npm install

- name: Run Tests
run: npm test

En la imagen ubuntu-latest podemos observar lo que viene preinstalado, no necesitaba instalar, pero siempre es bueno garantizar la versión de aquello que quieres.

Haz el commit y vamos a ver qué pasa cuando damos un push. Aprovechemos y pongámoslo en una nueva rama antes

git checkout -b feature/app-tested
git add -A
git commit -m "tu mensaje"
git push origin feature/app-tested

alt text

alt text

Observa que nuestro evento funcionó pues hicimos un push. Si hacemos un merge en la rama main tendremos otra ejecución?

alt text

Tendremos pues sería lo mismo que crear mergear el código localmente y hacer push.

Entró en el repositorio es push! Sabiendo esto podemos bloquear el push para personas específicas, solamente los propietarios del repositorio pueden forzar un push o cambiar para pull request.

¿Qué pasa si un step falla? Vamos a alterar un test intencionadamente y comitear en el repositorio.

alt text

alt text

alt text

Múltiples Trabajos

Vamos a alterar ahora este workflow para deployment y añadir un nuevo trabajo (deploy), siendo que el primero será mantenido el test.

name: Deployment

on: push

jobs:
test:

runs-on: ubuntu-latest
steps:

- name: Get Code
uses: actions/checkout@v4

- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Run Tests
run: npm test

deploy:
needs: test
runs-on: ubuntu-latest
steps:
## Observa que fue necesario coger el código de la misma manera pues son máquinas diferentes.
- name: Get Code
uses: actions/checkout@v4

- name: Install NodeJS
uses: actions/setup-node@v4
with:
node-version: '16'

- name: Install dependencies
run: npm ci

- name: Build project
run: npm run build

- name: Deploy
run: echo "Deploying somewhere..." # Imagina este paso

alt text

alt text

En este caso los trabajos ejecutaron en paralelo y el deploy solo debería suceder si el test tuviera éxito.

Para ejecutar uno tras otro existen diferentes maneras pero la más común es usar la etiqueta needs que es una lista de trabajos de los cuales este depende. Entonces vamos a alterar esta parte.

  deploy:
needs: [test]
# Este método de declaración de lista no funciona
# needs:
# - test
runs-on: ubuntu-latest

alt text

Si observas el tiempo verás que también tardó más una vez que el segundo ambiente solamente fue montado después de que el primero terminó.

¿Es posible montar todos los ambientes de una vez y ordenar la ejecución para ganar velocidad?

Podemos romper nuevamente la parte de test y probar qué pasa.

alt text

Ni siquiera ejecutó el deploy como esperado.

Múltiples Disparadores

Hasta ahora usamos solamente 2 activadores. Podemos ponerlos juntos.

...
on: [push, workflow_dispatch]
...

Con esto liberamos el botón para ejecutar manualmente.

alt text

Contexto del Workflow

Algunas veces necesitamos utilizar los runners con algunos metadatos de ambiente.

Dentro de los runners tenemos algo que llamo contexto. El contexto carga una serie de informaciones del repositorio que no están de hecho en el código para dentro del workflow que permite trabajar por ejemplo con variables de entorno y otras cosas más.

Para acceder a estas informaciones usamos una sintaxis específica ${{ <context> }}. Tenemos palabras reservadas que nos permiten acceder a estos valores.

También tenemos las expresiones que combinan funciones con contextos.

Vamos a montar un pipeline simple para entender esto. Vamos a usar una función toJSON en una expresión para convertir el contexto github en un json para ver todo a lo que tenemos acceso.

name: Output Information

on: workflow_dispatch

jobs:
info:
runs-on: ubuntu-latest
steps:

- name: Show Context
# Estamos queriendo coger todo el contexto de github y mostrar en json
run: echo "${{ toJSON(github) }}"

Guarda este workflow y vamos a ejecutar manualmente. Vamos a analizar el log y ver todo lo que tenemos.

alt text

Vamos más a fondo en el log y observemos el objeto del contexto de github.

Por ejemplo podríamos acceder al nombre del propietario del repositorio usando ${{ github.repository_owner}} que traería davidpuziol. Si fuéramos a usar una url del repositorio para clonar en lugar de usar el action checkout podríamos cogerlo por ${{ github.repositoryUrl}} en lugar de colocar el valor hard coded en el yaml.

{
token: ***,
job: info, // nombre del trabajo
ref: refs/heads/main,
sha: d74c26ff77283f03196c167f689941ab1893acaf,
repository: davidpuziol/gh-first-action,
repository_owner: davidpuziol,
repository_owner_id: 32808515,
repositoryUrl: git://github.com/davidpuziol/gh-first-action.git,
run_id: 10239662371,
run_number: 1,
retention_days: 90,
run_attempt: 1,
artifact_cache_size_limit: 10,
repository_visibility: public,
repo-self-hosted-runners-disabled: false,
enterprise-managed-business-id: ,
repository_id: 837317176,
actor_id: 32808515,
actor: davidpuziol,
triggering_actor: davidpuziol,
workflow: Output Information,
head_ref: ,
base_ref: ,
event_name: workflow_dispatch,
event: {
inputs: null,
ref: refs/heads/main,
repository: {
allow_forking: true,
archive_url: https://api.github.com/repos/davidpuziol/gh-first-action/{archive_format}{/ref},
archived: false,
assignees_url: https://api.github.com/repos/davidpuziol/gh-first-action/assignees{/user},
blobs_url: https://api.github.com/repos/davidpuziol/gh-first-action/git/blobs{/sha},
branches_url: https://api.github.com/repos/davidpuziol/gh-first-action/branches{/branch},
clone_url: https://github.com/davidpuziol/gh-first-action.git,
collaborators_url: https://api.github.com/repos/davidpuziol/gh-first-action/collaborators{/collaborator},
comments_url: https://api.github.com/repos/davidpuziol/gh-first-action/comments{/number},
commits_url: https://api.github.com/repos/davidpuziol/gh-first-action/commits{/sha},
compare_url: https://api.github.com/repos/davidpuziol/gh-first-action/compare/{base}...{head},
contents_url: https://api.github.com/repos/davidpuziol/gh-first-action/contents/{+path},
contributors_url: https://api.github.com/repos/davidpuziol/gh-first-action/contributors,
created_at: 2024-08-02T17:27:07Z,
default_branch: main,
deployments_url: https://api.github.com/repos/davidpuziol/gh-first-action/deployments,
description: Github Course First Repo,
disabled: false,
downloads_url: https://api.github.com/repos/davidpuziol/gh-first-action/downloads,
events_url: https://api.github.com/repos/davidpuziol/gh-first-action/events,
fork: false,
forks: 0,
forks_count: 0,
forks_url: https://api.github.com/repos/davidpuziol/gh-first-action/forks,
full_name: davidpuziol/gh-first-action,
git_commits_url: https://api.github.com/repos/davidpuziol/gh-first-action/git/commits{/sha},
git_refs_url: https://api.github.com/repos/davidpuziol/gh-first-action/git/refs{/sha},
git_tags_url: https://api.github.com/repos/davidpuziol/gh-first-action/git/tags{/sha},
git_url: git://github.com/davidpuziol/gh-first-action.git,
has_discussions: false,
has_downloads: true,
has_issues: true,
has_pages: false,
has_projects: true,
has_wiki: true,
homepage: null,
hooks_url: https://api.github.com/repos/davidpuziol/gh-first-action/hooks,
html_url: https://github.com/davidpuziol/gh-first-action,
id: 837317176,
is_template: false,
issue_comment_url: https://api.github.com/repos/davidpuziol/gh-first-action/issues/comments{/number},
issue_events_url: https://api.github.com/repos/davidpuziol/gh-first-action/issues/events{/number},
issues_url: https://api.github.com/repos/davidpuziol/gh-first-action/issues{/number},
keys_url: https://api.github.com/repos/davidpuziol/gh-first-action/keys{/key_id},
labels_url: https://api.github.com/repos/davidpuziol/gh-first-action/labels{/name},
language: JavaScript,
languages_url: https://api.github.com/repos/davidpuziol/gh-first-action/languages,
license: {
key: mit,
name: MIT License,
node_id: MDc6TGljZW5zZTEz,
spdx_id: MIT,
url: https://api.github.com/licenses/mit
},
merges_url: https://api.github.com/repos/davidpuziol/gh-first-action/merges,
milestones_url: https://api.github.com/repos/davidpuziol/gh-first-action/milestones{/number},
mirror_url: null,
name: gh-first-action,
node_id: R_kgDOMehyOA,
notifications_url: https://api.github.com/repos/davidpuziol/gh-first-action/notifications?since,
open_issues: 0,
open_issues_count: 0,
owner: {
avatar_url: https://avatars.githubusercontent.com/u/32808515?v=4,
events_url: https://api.github.com/users/davidpuziol/events{/privacy},
followers_url: https://api.github.com/users/davidpuziol/followers,
following_url: https://api.github.com/users/davidpuziol/following{/other_user},
gists_url: https://api.github.com/users/davidpuziol/gists{/gist_id},
gravatar_id: ,
html_url: https://github.com/davidpuziol,
id: 32808515,
login: davidpuziol,
node_id: MDQ6VXNlcjMyODA4NTE1,
organizations_url: https://api.github.com/users/davidpuziol/orgs,
received_events_url: https://api.github.com/users/davidpuziol/received_events,
repos_url: https://api.github.com/users/davidpuziol/repos,
site_admin: false,
starred_url: https://api.github.com/users/davidpuziol/starred{/owner}{/repo},
subscriptions_url: https://api.github.com/users/davidpuziol/subscriptions,
type: User,
url: https://api.github.com/users/davidpuziol
},
private: false,
pulls_url: https://api.github.com/repos/davidpuziol/gh-first-action/pulls{/number},
pushed_at: 2024-08-04T22:15:52Z,
releases_url: https://api.github.com/repos/davidpuziol/gh-first-action/releases{/id},
size: 75,
ssh_url: [email protected]:davidpuziol/gh-first-action.git,
stargazers_count: 0,
stargazers_url: https://api.github.com/repos/davidpuziol/gh-first-action/stargazers,
statuses_url: https://api.github.com/repos/davidpuziol/gh-first-action/statuses/{sha},
subscribers_url: https://api.github.com/repos/davidpuziol/gh-first-action/subscribers,
subscription_url: https://api.github.com/repos/davidpuziol/gh-first-action/subscription,
svn_url: https://github.com/davidpuziol/gh-first-action,
tags_url: https://api.github.com/repos/davidpuziol/gh-first-action/tags,
teams_url: https://api.github.com/repos/davidpuziol/gh-first-action/teams,
topics: [],
trees_url: https://api.github.com/repos/davidpuziol/gh-first-action/git/trees{/sha},
updated_at: 2024-08-04T22:15:55Z,
url: https://api.github.com/repos/davidpuziol/gh-first-action,
visibility: public,
watchers: 0,
watchers_count: 0,
web_commit_signoff_required: false
},
sender: {
avatar_url: https://avatars.githubusercontent.com/u/32808515?v=4,
events_url: https://api.github.com/users/davidpuziol/events{/privacy},
followers_url: https://api.github.com/users/davidpuziol/followers,
following_url: https://api.github.com/users/davidpuziol/following{/other_user},
gists_url: https://api.github.com/users/davidpuziol/gists{/gist_id},
gravatar_id: ,
html_url: https://github.com/davidpuziol,
id: 32808515,
login: davidpuziol,
node_id: MDQ6VXNlcjMyODA4NTE1,
organizations_url: https://api.github.com/users/davidpuziol/orgs,
received_events_url: https://api.github.com/users/davidpuziol/received_events,
repos_url: https://api.github.com/users/davidpuziol/repos,
site_admin: false,
starred_url: https://api.github.com/users/davidpuziol/starred{/owner}{/repo},
subscriptions_url: https://api.github.com/users/davidpuziol/subscriptions,
type: User,
url: https://api.github.com/users/davidpuziol
},
workflow: .github/workflows/output.yml
},
server_url: https://github.com,
api_url: https://api.github.com,
graphql_url: https://api.github.com/graphql,
ref_name: main,
ref_protected: false,
ref_type: branch,
secret_source: Actions,
workflow_ref: davidpuziol/gh-first-action/.github/workflows/output.yml@refs/heads/main,
workflow_sha: d74c26ff77283f03196c167f689941ab1893acaf,
workspace: /home/runner/work/gh-first-action/gh-first-action,
action: __run,
event_path: /home/runner/work/_temp/_github_workflow/event.json,
action_repository: ,
action_ref: ,
path: /home/runner/work/_temp/_runner_file_commands/add_path_82936500-cc74-4468-b613-268563dc2a2b,
env: /home/runner/work/_temp/_runner_file_commands/set_env_82936500-cc74-4468-b613-268563dc2a2b,
step_summary: /home/runner/work/_temp/_runner_file_commands/step_summary_82936500-cc74-4468-b613-268563dc2a2b,
state: /home/runner/work/_temp/_runner_file_commands/save_state_82936500-cc74-4468-b613-268563dc2a2b,
output: /home/runner/work/_temp/_runner_file_commands/set_output_82936500-cc74-4468-b613-268563dc2a2b
}

Vamos a utilizar mucho esta idea de contexto a lo largo del aprendizaje. Aquí tenemos solo un primer contacto.