DevOps Admin

GitHub Actions: Workflows, Jobs, Steps e Actions Reutilizáveis na Prática Já leu

GitHub Actions: Dominando Workflows, Jobs, Steps e Actions Reutilizáveis GitHub Actions é a solução nativa do GitHub para automação e integração contínua (CI/CD). Diferentemente de ferramentas externas, ela integra-se perfeitamente ao seu repositório, elimina a complexidade de configurações em servidores separados e permite que você automatize praticamente qualquer fluxo de trabalho com apenas alguns arquivos YAML. Neste artigo, vamos entender desde o conceito fundamental até a criação de actions reutilizáveis que você pode compartilhar entre projetos. O que é GitHub Actions? GitHub Actions funciona com base em eventos. Quando algo acontece no seu repositório — como um push, um pull request ou até uma publicação de release — uma "workflow" é acionada. Essa workflow contém a lógica de execução: testes, builds, deployments, notificações e muito mais. O grande diferencial é que tudo roda em máquinas virtuais fornecidas pelo GitHub, sem necessidade de infraestrutura própria. Estrutura Fundamental: Workflows, Jobs e Steps Entendendo a Hierarquia Uma workflow é um arquivo YAML armazenado

GitHub Actions: Dominando Workflows, Jobs, Steps e Actions Reutilizáveis

GitHub Actions é a solução nativa do GitHub para automação e integração contínua (CI/CD). Diferentemente de ferramentas externas, ela integra-se perfeitamente ao seu repositório, elimina a complexidade de configurações em servidores separados e permite que você automatize praticamente qualquer fluxo de trabalho com apenas alguns arquivos YAML. Neste artigo, vamos entender desde o conceito fundamental até a criação de actions reutilizáveis que você pode compartilhar entre projetos.

O que é GitHub Actions?

GitHub Actions funciona com base em eventos. Quando algo acontece no seu repositório — como um push, um pull request ou até uma publicação de release — uma "workflow" é acionada. Essa workflow contém a lógica de execução: testes, builds, deployments, notificações e muito mais. O grande diferencial é que tudo roda em máquinas virtuais fornecidas pelo GitHub, sem necessidade de infraestrutura própria.

Estrutura Fundamental: Workflows, Jobs e Steps

Entendendo a Hierarquia

Uma workflow é um arquivo YAML armazenado em .github/workflows/ que define o fluxo de automação. Dentro de uma workflow, você tem jobs — unidades independentes de trabalho que podem rodar em paralelo ou sequencial. Dentro de cada job, existem steps — as ações individuais que executam comandos ou rodam ações pré-construídas.

A hierarquia é clara:

Workflow (arquivo .yaml)
├── Job 1
│   ├── Step 1
│   ├── Step 2
│   └── Step 3
└── Job 2
    ├── Step 1
    └── Step 2

Exemplo Prático: Workflow Básica

Vamos criar uma workflow que executa testes automaticamente quando há um push na branch main. Crie o arquivo .github/workflows/ci.yaml:

name: CI Pipeline
on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Install dependencies
        run: npm ci

      - name: Run linter
        run: npm run lint

      - name: Run tests
        run: npm test -- --coverage

      - name: Upload coverage
        uses: codecov/codecov-action@v3
        if: always()

Neste exemplo, o job test é executado em uma máquina Ubuntu. Os steps seguem uma sequência: checkout do código, setup do Node.js, instalação de dependências, execução de lint e testes. O if: always() garante que a cobertura seja enviada mesmo se os testes falharem.

Executando Jobs em Paralelo e Sequência

Por padrão, jobs executam em paralelo. Se você precisar que um job dependa de outro, use a palavra-chave needs. Veja este exemplo onde o job de deploy depende do job de build:

name: Build and Deploy
on:
  push:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npm run build
      - name: Upload artifacts
        uses: actions/upload-artifact@v3
        with:
          name: build
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Download artifacts
        uses: actions/download-artifact@v3
        with:
          name: build
      - name: Deploy to production
        run: |
          echo "Deploying application..."
          # Seu script de deploy aqui

Aqui, o job deploy só começa após o sucesso do job build — isso é essencial para garantir que você só faça deploy de uma versão que foi testada.

Actions Reutilizáveis: Criando Componentes Modulares

Por Que Actions Reutilizáveis Importam

Conforme seus projetos crescem, você notará que certos passos são repetidos em múltiplas workflows. Ao invés de copiar e colar o mesmo código YAML, você pode criar uma action reutilizável — um componente que encapsula lógica e pode ser utilizado em qualquer workflow, até em repositórios diferentes.

Actions reutilizáveis funcionam como funções: recebem inputs, executam lógica, produzem outputs e podem ser compartilhadas.

Estrutura de uma Action Reutilizável

Uma action reutilizável é definida em um arquivo action.yaml e pode conter scripts em shell, JavaScript ou usar imagens Docker. Vamos criar uma action que valida a versão de um projeto:

name: 'Validate Version'
description: 'Valida se a versão no package.json segue semântica correta'
inputs:
  version-path:
    description: 'Caminho para o arquivo com a versão'
    required: false
    default: 'package.json'
outputs:
  current-version:
    description: 'Versão extraída'
    value: ${{ steps.extract.outputs.version }}
  is-valid:
    description: 'Se a versão é válida (true/false)'
    value: ${{ steps.validate.outputs.valid }}

runs:
  using: 'composite'
  steps:
    - name: Extract version
      id: extract
      shell: bash
      run: |
        VERSION=$(cat ${{ inputs.version-path }} | grep '"version"' | head -1 | sed 's/.*"version": "\([^"]*\)".*/\1/')
        echo "version=$VERSION" >> $GITHUB_OUTPUT

    - name: Validate semantic versioning
      id: validate
      shell: bash
      run: |
        VERSION="${{ steps.extract.outputs.version }}"
        if [[ $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
          echo "valid=true" >> $GITHUB_OUTPUT
          echo "✓ Versão $VERSION é válida"
        else
          echo "valid=false" >> $GITHUB_OUTPUT
          echo "✗ Versão $VERSION não segue semântica semver"
          exit 1
        fi

Salve este arquivo como .github/actions/validate-version/action.yaml. Agora você pode usar essa action em qualquer workflow:

name: Validate Release
on:
  push:
    tags: [ 'v*' ]

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Validate project version
        id: version
        uses: ./.github/actions/validate-version
        with:
          version-path: 'package.json'

      - name: Show results
        run: |
          echo "Versão atual: ${{ steps.version.outputs.current-version }}"
          echo "É válida: ${{ steps.version.outputs.is-valid }}"

Usando Secrets e Variáveis em Actions

Actions reutilizáveis frequentemente precisam de dados sensíveis, como tokens ou senhas. O GitHub fornece uma forma segura de usar esses dados através de secrets. Vamos criar uma action que faz deploy usando um token:

name: 'Deploy Application'
description: 'Faz deploy da aplicação para o servidor'
inputs:
  environment:
    description: 'Ambiente de deploy (staging ou production)'
    required: true
    default: 'staging'
outputs:
  deployment-url:
    description: 'URL da aplicação deployada'
    value: ${{ steps.deploy.outputs.url }}

runs:
  using: 'composite'
  steps:
    - name: Deploy
      id: deploy
      shell: bash
      env:
        DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
        ENV: ${{ inputs.environment }}
      run: |
        # Valida se token existe
        if [ -z "$DEPLOY_TOKEN" ]; then
          echo "Erro: DEPLOY_TOKEN não configurado"
          exit 1
        fi

        # Faz deploy
        echo "Deploying para $ENV..."
        # Seu script de deploy aqui

        # Define output
        if [ "$ENV" = "production" ]; then
          echo "url=https://app.example.com" >> $GITHUB_OUTPUT
        else
          echo "url=https://staging.example.com" >> $GITHUB_OUTPUT
        fi

Use assim em sua workflow:

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Deploy to production
        uses: ./.github/actions/deploy
        with:
          environment: 'production'
        secrets:
          DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }}

Padrões Avançados e Melhores Práticas

Reutilizando Workflows Inteiras

Você também pode reutilizar workflows completas! Isso é útil quando múltiplos projetos precisam do mesmo pipeline. Crie .github/workflows/reusable-test.yaml:

name: Reusable Test Workflow
on:
  workflow_call:
    inputs:
      node-version:
        type: string
        required: false
        default: '18'
    secrets:
      npm-token:
        required: false

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: ${{ inputs.node-version }}

      - name: Install dependencies
        run: npm ci
        env:
          NPM_TOKEN: ${{ secrets.npm-token }}

      - name: Run tests
        run: npm test

Em outro repositório, você chama assim:

name: CI
on: [ push, pull_request ]

jobs:
  test:
    uses: seu-usuario/seu-repo/.github/workflows/reusable-test.yaml@main
    with:
      node-version: '20'
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

Tratamento de Erros e Condicionais

GitHub Actions oferece várias funções para controlar o fluxo baseado em resultados:

jobs:
  check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Check syntax
        id: syntax
        continue-on-error: true
        run: |
          npm run lint

      - name: Notify on failure
        if: failure()
        run: |
          echo "Pipeline falhou"
          exit 1

      - name: Report success
        if: success()
        run: echo "Tudo passou!"

      - name: Always run
        if: always()
        run: echo "Isso sempre executa, sucesso ou falha"

O if: failure() executa apenas se algum step anterior falhar. O if: success() executa apenas se todos forem bem-sucedidos. O if: always() ignora completamente o status anterior.

Cacheando Dependências para Performance

Um padrão essencial é cachear dependências para evitar re-downloads:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '18'

      - name: Cache npm dependencies
        uses: actions/cache@v3
        with:
          path: ~/.npm
          key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
          restore-keys: |
            ${{ runner.os }}-npm-

      - name: Install dependencies
        run: npm ci

      - name: Build
        run: npm run build

O cache usa um hash do package-lock.json como chave. Se o arquivo não mudou, as dependências em cache são reutilizadas, economizando tempo e largura de banda.

Conclusão

GitHub Actions transforma a forma como você automatiza seu fluxo de desenvolvimento. Os três pontos principais que você deve levar adiante são:

  1. Workflows, Jobs e Steps são blocos de construção hierárquicos: comece simples, entenda como eles se relacionam, e escale conforme necessário. Use needs para criar dependências entre jobs quando o pipeline exigir sequência.

  2. Actions reutilizáveis reduzem duplicação e aumentam manutenibilidade: encapsule lógica comum em actions e compartilhe-as. Isso não apenas economiza linhas de código, mas torna sua pipeline mais profissional e fácil de manter.

  3. Performance e segurança são responsabilidades suas: implemente caching estrategicamente, nunca hardcode secrets (use a gestão de secrets nativa), e sempre teste suas workflows antes de mergear em produção.

Referências


Artigos relacionados