Workflows Avanzados en Forgejo Actions
Capitulo 17: Workflows Avanzados
Con los conceptos basicos dominados, este capitulo cubre patrones mas sofisticados que resuelven problemas comunes en pipelines de CI/CD reales.
Matrix strategy
La estrategia de matrix permite ejecutar el mismo job con multiples combinaciones de variables. Es ideal para probar en varias versiones de un lenguaje o sistema operativo.
Ejemplo: probar en multiples versiones de Node.js
name: Tests matrix
on:
push:
branches: [main]
jobs:
test:
name: Node ${{ matrix.node-version }}
runs-on: ubuntu-latest
strategy:
matrix:
node-version: ["18", "20", "22"]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: "npm"
- run: npm ci
- run: npm test
Con esta configuracion se crean tres jobs independientes, uno por cada version de Node. Todos corren en paralelo si hay runners disponibles.
fail-fast: por defecto es true, lo que cancela todos los jobs de la matrix si uno falla. Cambiar a false permite que todos terminen independientemente.
Matrix con multiples dimensiones:
strategy:
matrix:
os: [ubuntu-latest, debian-latest]
node: ["18", "20"]
Esto genera cuatro combinaciones: ubuntu+node18, ubuntu+node20, debian+node18, debian+node20.
Excluir combinaciones especificas:
strategy:
matrix:
node: ["18", "20", "22"]
exclude:
- node: "18"
Concurrency
El campo concurrency controla que pasa cuando multiples ejecuciones del mismo workflow estan activas al mismo tiempo.
Caso de uso tipico: cuando haces varios pushes rapidos a main, no quieres que todos los deployments corran en paralelo. Solo el ultimo deberia ejecutarse.
name: Deploy
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: true
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- run: echo "Desplegando..."
Con cancel-in-progress: true, si llega una nueva ejecucion mientras la anterior esta corriendo, la anterior se cancela automaticamente.
Para PRs es util agrupar por rama:
concurrency:
group: pr-${{ github.event.pull_request.number }}
cancel-in-progress: true
Artifacts
Los artifacts permiten guardar archivos generados durante un job y compartirlos entre jobs o descargarlos desde la UI.
Subir un artifact:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm ci && npm run build
- name: Guardar artifact de build
uses: actions/upload-artifact@v4
with:
name: dist-produccion
path: dist/
retention-days: 7
Descargar el artifact en otro job:
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Descargar artifact
uses: actions/download-artifact@v4
with:
name: dist-produccion
path: ./dist
- name: Desplegar
run: rsync -av ./dist/ usuario@servidor:/var/www/html/
El needs: build garantiza que el job deploy espera a que build termine antes de ejecutarse.
Dependencias entre jobs y outputs
El campo needs define el orden de ejecucion. Tambien es posible pasar datos entre jobs usando outputs.
jobs:
version:
runs-on: ubuntu-latest
outputs:
tag: ${{ steps.get-version.outputs.version }}
steps:
- uses: actions/checkout@v4
- name: Obtener version
id: get-version
run: |
VERSION=$(node -p "require('./package.json').version")
echo "version=$VERSION" >> $GITHUB_OUTPUT
build:
runs-on: ubuntu-latest
needs: version
steps:
- uses: actions/checkout@v4
- name: Build con version ${{ needs.version.outputs.tag }}
run: npm run build
env:
APP_VERSION: ${{ needs.version.outputs.tag }}
Para que un step produzca un output, usa echo "clave=valor" >> $GITHUB_OUTPUT. Luego el job lo expone en outputs y otros jobs lo acceden con needs.nombre-job.outputs.clave.
Condicionales
El campo if permite saltar steps o jobs enteros segun condiciones.
Condicionales en jobs:
jobs:
deploy-staging:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/develop'
steps:
- run: ./deploy.sh staging
deploy-prod:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- run: ./deploy.sh production
Condicionales en steps:
steps:
- name: Notificar en Slack solo si falla
if: failure()
run: curl -X POST ${{ secrets.SLACK_WEBHOOK }} -d '{"text":"Pipeline fallido"}'
- name: Solo en ejecucion manual
if: github.event_name == 'workflow_dispatch'
run: echo "Ejecutado manualmente"
Funciones disponibles en condicionales:
success(): true si todos los steps anteriores pasaronfailure(): true si algun step anterior falloalways(): siempre true, ejecuta aunque el workflow fallecancelled(): true si el workflow fue cancelado
Workflows reutilizables
Un workflow puede ser llamado desde otro workflow usando el trigger workflow_call. Esto evita duplicar logica entre proyectos o pipelines.
Definir el workflow reutilizable (.forgejo/workflows/ci-reutilizable.yaml):
name: CI reutilizable
on:
workflow_call:
inputs:
node-version:
required: false
type: string
default: "20"
run-lint:
required: false
type: boolean
default: true
secrets:
NPM_TOKEN:
required: false
jobs:
ci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ inputs.node-version }}
- name: Instalar dependencias
run: npm ci
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Lint
if: ${{ inputs.run-lint }}
run: npm run lint
- run: npm test
Llamar al workflow reutilizable desde otro workflow:
name: Pipeline principal
on:
push:
branches: [main]
pull_request:
jobs:
ci:
uses: ./.forgejo/workflows/ci-reutilizable.yaml
with:
node-version: "22"
run-lint: true
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
Ejemplo completo: build, test con matrix y deploy
Workflow que integra todo lo visto: build inicial, pruebas en matrix de versiones, y deploy condicional solo si todo pasa:
name: Pipeline completo
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: pipeline-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
name: Build
runs-on: ubuntu-latest
outputs:
artifact-name: dist-${{ github.sha }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: "20"
cache: "npm"
- run: npm ci
- run: npm run build
- uses: actions/upload-artifact@v4
with:
name: dist-${{ github.sha }}
path: dist/
retention-days: 1
test:
name: Tests Node ${{ matrix.node }}
runs-on: ubuntu-latest
needs: build
strategy:
matrix:
node: ["18", "20", "22"]
fail-fast: false
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
cache: "npm"
- run: npm ci
- run: npm test
deploy:
name: Deploy a produccion
runs-on: ubuntu-latest
needs: [build, test]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/download-artifact@v4
with:
name: dist-${{ github.sha }}
path: ./dist
- name: Deploy
run: rsync -av --delete ./dist/ ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/var/www/app/
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
En este pipeline:
buildcompila la aplicacion y sube el artifacttestcorre en paralelo con tres versiones de Node, esperando a quebuildterminedeploysolo corre sibuildy todos los jobs detestpasaron, y ademas es un push a main (no un PR)
Esta estructura garantiza que nunca se despliega codigo que no compilo o que fallo las pruebas en alguna version soportada.
Siguiente: Capitulo 18: Volver al indice —>