DEV Community

Cover image for Criando uma Composite Actions para utilizar em do CI/CD do GitHub
Marcos Vilela
Marcos Vilela

Posted on

Criando uma Composite Actions para utilizar em do CI/CD do GitHub

O GitHub Actions é uma ferramenta poderosa para orquestrar pipelines de CI/CD, mas às vezes você deseja um pouco mais de controle sobre o que acontece dentro deles. Uma composite action oferece a flexibilidade de uma etapa de fluxo de trabalho normal com a conveniência de uma ação... e, quando combinada com um coletor baseado em TypeScript, torna-se um componente reutilizável e de nível de plataforma.

Por que uma composite action?

  • Executa no mesmo runner do job: ideal para tarefas de orquestração e arquivo de contexto (nenhum contêiner separado).
  • Permite mixar shell e outras actions sem escrever JavaScript puro.
  • Fácil de versionar e publicar no marketplace; repositórios mono ou multi-repo podem reutilizá-la.

Nós escolhemos esse modelo porque queríamos:

  1. Capturar contexto do workflow (branch, run_id, status, duração).
  2. Ler artefatos JSON produzidos pelo pipeline.
  3. Agregar logs/outputs e enviar para um serviço de telemetria.
  4. Não quebrar o job se algo falhar — execução sempre com if: always().

Estrutura do repositório

.
├── action.yml               # definição da composite action
├── package.json             # dependências e scripts
├── src/
│   ├── collector.ts         # lógica em TypeScript
│   └── lib/                 # módulos auxiliares testáveis
├── dist/                    # bundle JS (commitado)
├── tests/                   # Jest unit tests
└── .github/workflows/       # workflows de demo, feature, main...
Enter fullscreen mode Exit fullscreen mode

O collector.ts é o coração: lê variáveis de ambiente, analisa diretórios de artefatos, soma tamanhos de logs, faz retries no envio HTTP e gera um payload estruturado.

Exemplo de action.yml

name: "[ORG] Metrics-Collect Composite Action"
description: "Collect and send structured pipeline metrics to an external endpoint."
author: "Platform Engineering Team"

inputs:
  endpoint_url:
    description: "HTTP endpoint to receive metrics (if empty, dry-run)"
    required: false
    default: ""
  auth_token:
    description: "Token used for auth (pass as secret)"
    required: false
    default: ""
  artifact_patterns:
    description: "Comma-separated artifact name patterns to download"
    required: false
    default: ""
  include_logs:
    description: "Whether to attempt log collection"
    required: false
    default: "true"
  working_directory:
    description: "Working directory (for monorepos)"
    required: false
    default: ""
  dry_run:
    description: "If true, do not send HTTP requests"
    required: false
    default: "false"

outputs:
  metrics_status:
    description: "Overall status: ok | partial | failed"
    value: ${{ steps.run-collector.outputs.metrics_status }}
  payload_path:
    description: "Local path to generated payload"
    value: ${{ steps.run-collector.outputs.payload_path }}

runs:
  using: "composite"
  steps:
    - name: Download artifacts (pattern)
      uses: actions/download-artifact@v4
      with:
        name: ${{ inputs.artifact_patterns }}
        path: ./artifacts
      continue-on-error: true

    - name: Run Metrics Collector
      shell: bash
      run: |
        node $GITHUB_ACTION_PATH/dist/index.js \
          --endpoint "${{ inputs.endpoint_url }}" \
          --auth-token "${{ inputs.auth_token }}" \
          --artifact-dir "./artifacts" \
          --include-logs "${{ inputs.include_logs }}" \
          --working-directory "${{ inputs.working_directory }}" \
          --dry-run "${{ inputs.dry_run }}" || true
Enter fullscreen mode Exit fullscreen mode

Collector em TypeScript

A justificativa por usar TypeScript:

  • Tipagem, autocompletar e refatorações fáceis durante o desenvolvimento.
  • Testes com Jest (busca por arquivos JSON, análise de logs, tratamento de erros).
  • Construção um bundle único com @vercel/ncc garante que o runner não precisa instalar nada.

Trecho de src/collector.ts:

async function main() {
  const inps = parseArgs();
  const ctx = await collectWorkflowContext();
  const artifactFiles = await discoverJsonFiles(inps.artifactDir);
  // ...agrega dados...
  try {
    const res = await sendWithRetry(inps.endpoint!, payload, headers, inps.retryCount!, inps.timeoutMs!);
    // ...insere status/event_id no payload...
  } catch (err) {
    console.error('Failed to send payload', err);
  }
}

main().catch(e => { console.error(e); process.exitCode = 0; });
Enter fullscreen mode Exit fullscreen mode

O código trata todos os erros localmente, grava metrics-output/payload.json e produz outputs para a composite.

Integração com CI/CD

Adicionei um workflow de exemplo (.github/workflows/feature-workflow.yml) que roda o pipeline principal e, em seguida, chama a composite para coletar métricas:

jobs:
  call-shared-ci:          # job reutilizável de CI
    uses: org/shared-workflows/backend-ci@v1

  collect-metrics:
    needs: [call-shared-ci]
    if: always()
    runs-on: ubuntu-latest
    permissions:
      contents: read
      actions: read
    steps:
      - uses: actions/checkout@v6
      - uses: org/metrics-collect@v1
        with:
          endpoint_url: ${{ secrets.METRICS_ENDPOINT }}
          auth_token: ${{ secrets.METRICS_TOKEN }}
          artifact_patterns: "step-result-*,coverage-report"
          include_logs: "true"
Enter fullscreen mode Exit fullscreen mode

A condição if: always() garante que, mesmo que a verificação de lint/test falhe, ainda coletamos dados.

Demo local com act

O repositório contém um workflow de demo que gera artefatos JSON falsos e executa a action com endpoint_url apontando para webhook.site:

on: workflow_dispatch
jobs:
  demo:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v6
      - run: mkdir artifacts; echo '{"foo":1}' > artifacts/metrics.json
      - uses: ./
        with:
          endpoint_url: ${{ secrets.INPUT_ENDPOINT_URL }}
          include_logs: "true"
Enter fullscreen mode Exit fullscreen mode

O act pode executar esse workflow offline, contanto que você passe -s INPUT_ENDPOINT_URL="https://webhook.site/...".

Casos de uso

  • Telemetry / Analytics: enviar métricas de duração, falha e outputs para um motor centralizado.
  • Auditoria: consolidar resultados de lint, testes e segurança em um único payload para relatórios de conformidade.
  • Cross-repo dashboards: equipes podem empacotar essa action e aplicar em todos os repositórios simples ou monorepos.

Arquitetura definida

  1. Composite Action (orquestração): inputs/outputs declarados, download de artefatos, execução do binário bundle.
  2. Collector TypeScript: lógica pura, agnóstica de GitHub; módulos auxiliares testáveis.
  3. Bundle dist/index.js: resultante do @vercel/ncc (commitado).
  4. Workflows: demo (para desenvolvimento), feature/staging/main (integração/prod).

Tolerância a falhas

  • Falhas no download de artefatos ou envio HTTP não quebram o job (usar continue-on-error e || true).
  • Logs e payload são persistidos localmente (metrics-output/) para inspeção.
  • Retries com backoff exponencial no módulo HTTP.

Publicando e versionando

Usamos semantic-release em uma workflow de release que dispara na conclusão da staging:

dependencies:
  - name: release-workflow
    uses: org/shared-workflows/shared-release@staging
Enter fullscreen mode Exit fullscreen mode

Cada release produz uma tag (v1, v1.1.0, etc). Consumidores referenciam org/metrics-collect@v1 para estabilidade.

Conclusão

Transformar lógica de coleta de métricas em uma composite action permite padronizar instrumentação em toda a organização. O uso de TypeScript e bundling com ncc garante código limpo, testável e que roda rapidamente nos runners.

Top comments (0)