DevOps

🔄 GitHub Actions: Reusable Workflows

Los Reusable Workflows (Workflows Reutilizables) de GitHub Actions permiten evitar la duplicación de código YAML en proyectos con múltiples repositorios o microservicios. Con esta funcionalidad, puedes definir una plantilla de pipeline (por ejemplo, compilación de Docker, despliegue en Kubernetes o análisis de calidad de código) en un solo lugar y llamarla desde decenas de repositorios diferentes.

1. ¿Qué es un Reusable Workflow y por qué usarlo?

En organizaciones medianas o grandes, mantener copias idénticas de pipelines en 50 repositorios distintos conduce a la pesadilla de mantenimiento conocida como DRY violation (Don't Repeat Yourself). Si necesitas actualizar una versión de Node.js o corregir un parámetro de seguridad en Helm, tendrías que enviar 50 Pull Requests.

Concepto Clave: Un Called Workflow (workflow reutilizable/llamado) expone el disparador on: workflow_call. Un Caller Workflow (workflow invocador) llama a ese pipeline dentro de uno de sus jobs mediante la directiva uses:.

2. Reusable Workflows vs. Custom Actions

Es común confundir los Reusable Workflows con las Composite/Custom Actions. Aunque ambas promueven la reutilización de código, resuelven distintos niveles de abstracción:

CaracterísticaReusable WorkflowsComposite / Custom Actions
Nivel de JerarquíaNivel de Job completo. Puede incluir múltiples jobs en paralelo/secuencia.Nivel de Step (paso) individual dentro de un job existente.
Runners / AmbienteManeja sus propios runners (runs-on), permisos (permissions) y matriz.Se ejecuta dentro del runner ya asignado por el job que la invoca.
Matriz y ParalelismoSoporta matrices complejas y múltiples empleos coordinados.No maneja empleos independientes ni matrices de ejecución directa.
Secretos y EntornosSoporta integración nativa con environment y aprobación previa.Accede a las variables y secretos transmitidos al paso.

3. Sintaxis del Workflow Reutilizable (Called Workflow)

El archivo se guarda habitualmente dentro de .github/workflows/ (por ejemplo, _build_and_deploy.yml). La clave fundamental radica en definir el evento de activación on: workflow_call.

yaml
name: Standard Build & Deploy Pipeline

on:
  workflow_call:
    # 1. Definición de Parámetros de Entrada
    inputs:
      environment:
        description: 'Entorno de despliegue (staging, production)'
        required: true
        type: string
      node_version:
        description: 'Versión de Node.js'
        required: false
        default: '20'
        type: string
      run_tests:
        description: 'Si se deben ejecutar pruebas unitarias'
        required: false
        default: true
        type: boolean

    # 2. Definición de Secretos Requeridos
    secrets:
      AWS_ROLE_ARN:
        description: 'ARN del rol de AWS IAM mediante OIDC'
        required: true
      SLACK_WEBHOOK:
        description: 'URL de webhook para notificaciones'
        required: false

    # 3. Salidas generadas para el caller
    outputs:
      image_tag:
        description: 'Tag generado de la imagen Docker'
        value: ${{ jobs.build.outputs.docker_tag }}

jobs:
  build:
    name: Build & Test
    runs-on: ubuntu-latest
    outputs:
      docker_tag: ${{ steps.prep.outputs.tag }}
    steps:
      - name: Checkout del repositorio caller
        uses: actions/checkout@v4

      - name: Configurar Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node_version }}

      - name: Instalar Dependencias
        run: npm ci

      - name: Ejecutar Tests
        if: ${{ inputs.run_tests }}
        run: npm test

      - name: Generar Docker Tag
        id: prep
        run: echo "tag=${{ inputs.environment }}-${{ github.sha }}" >> $GITHUB_OUTPUT

  deploy:
    name: Deploy a K8s
    needs: build
    runs-on: ubuntu-latest
    environment: ${{ inputs.environment }}
    steps:
      - name: Notificar inicio
        run: echo "Desplegando imagen ${{ needs.build.outputs.docker_tag }} en ${{ inputs.environment }}"

4. Invocando el Workflow (Caller Workflow)

El repositorio que desea consumir este pipeline estandarizado crea un workflow normal (disparado por push, pull_request, etc.) y llama al reusable workflow definiendo uses: a nivel de job.

yaml
name: Production Release

on:
  push:
    branches:
      - main

jobs:
  # Llama al Reusable Workflow dentro del mismo repositorio (vía ruta relativa)
  call-pipeline:
    uses: ./.github/workflows/_build_and_deploy.yml
    with:
      environment: production
      node_version: '20'
      run_tests: true
    secrets:
      AWS_ROLE_ARN: ${{ secrets.PROD_AWS_ROLE_ARN }}
      SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK_URL }}

  # Job posterior que utiliza las salidas (outputs) del reusable workflow
  notify:
    needs: call-pipeline
    runs-on: ubuntu-latest
    steps:
      - name: Ver Tag Desplegado
        run: |
          echo "Despliegue finalizado exitosamente."
          echo "Tag publicado: ${{ needs.call-pipeline.outputs.image_tag }}"

5. Manejo Avanzado de Secretos: secrets: inherit

Pasar manualmente 10 o 15 secretos a través de la cláusula secrets: puede ser tedioso. GitHub Actions provee la directiva secrets: inherit para que el workflow reutilizable herede automáticamente todos los secretos del repositorio y de la organización.

yaml
  call-central-pipeline:
    # El llamado hereda implícitamente todos los secretos accesibles en el caller
    uses: mi-organizacion/ci-templates/.github/workflows/deploy.yml@v2
    with:
      environment: staging
    secrets: inherit

6. Centralización entre Múltiples Repositorios (Cross-Repository)

La mayor ventaja de los Reusable Workflows se desbloquea al hospedar tus plantillas en un repositorio centralizado dentro de tu organización (por ejemplo, mi-org/devops-templates).

Referencia por Referencia Git (Branch, Tag o Commit SHA):

  • Por Tag Semántico (Recomendado para producción): uses: mi-org/devops-templates/.github/workflows/docker-build.yml@v1.2.0
  • Por Rama (Desarrollo/Testing): uses: mi-org/devops-templates/.github/workflows/docker-build.yml@main
  • Por Commit SHA (Máxima seguridad e inmutabilidad): uses: mi-org/devops-templates/.github/workflows/docker-build.yml@a1b2c3d4e5f6...

Configuración de Permisos en GitHub Org: Para que otros repositorios privados de tu organización puedan invocar el workflow reutilizable hospedado en un repo privado central, debes ir a:

Repository Settings → Actions → General → Access y seleccionar "Accessible from repositories in the 'NAME' organization".

7. Buenas Prácticas y Reglas de Oro

  1. Prefijo con guion bajo: Nombra tus workflows reutilizables internos comenzando con guion bajo (ej: _terraform-pipeline.yml) para diferenciarlos visualmente de los workflows de entrada estándar.
  1. Límite de Anidación: Puedes anidar Reusable Workflows (un reusable workflow llamando a otro), pero existe un límite máximo de 4 niveles de profundidad.
  1. Estrategia de Versionado: Utiliza releases semánticos (tags v1, v1.1.0) en tu repositorio centralizado de pipelines para no romper los proyectos consumidores cuando hagas cambios retroincompatibles.
  1. Evitar dependencias ocultas: Declara explícitamente todos los inputs y secrets necesarios en el on: workflow_call para contar con documentación autocontenida.