Criando workflows reutilizáveis com GitHub Actions

Workflows reutilizáveis no GitHub Actions eliminam o velho hábito de copiar e colar o mesmo pipeline em cada repositório da organização. O GitHub Actions é a ferramenta de fluxo de trabalho do GitHub que automatiza build, testes e entregas, e neste artigo vamos ver duas maneiras de criar, reaproveitar e compartilhar esses fluxos entre repositórios.

A primeira maneira usa templates de workflow da organização, também chamados de fluxos iniciais. Já a segunda usa a chave workflow_call, que transforma um arquivo YAML em uma peça chamada por outros pipelines.

Requisitos

Para acompanhar os exemplos, você precisa de pouca coisa:

  • Uma conta no GitHub.
  • Uma organização com o GitHub Actions habilitado.
  • Permissão para criar repositórios nessa organização.

Se você ainda está começando com a plataforma, vale ler antes o artigo sobre Git e GitHub, instalação, configuração e primeiros passos.

Um pouco de história

Em muitos projetos, o software passa por um processo manual até chegar à produção. Nesse caminho, destacamos etapas de instalação, testes, validação e implantação, com cópia de arquivos quando as etapas anteriores terminam com sucesso.

Nesse fluxo, muitas etapas exigem esforço manual. Por isso, a chance de erro cresce à medida que o produto recebe novas funcionalidades ou que alguém altera um arquivo de configuração.

A automação desse processo acontece por meio de uma prática chamada CI/CD, ou integração e entrega contínuas. Essa é apenas uma visão macro, suficiente para apresentar a motivação e o exemplo deste artigo.

Para um exemplo completo de pipeline com deploy, confira o artigo sobre deploy de ASP.NET com GitHub Actions e Microsoft Azure.

Por que usar workflows reutilizáveis

Dentro da sua organização, você provavelmente já precisou reaproveitar um fluxo de trabalho. E, muito provavelmente, usou o famoso CTRL+C e CTRL+V para copiar os principais comandos para outro repositório.

A partir desse cenário, surge uma dúvida: é possível reutilizar workflows? A resposta é sim, e os workflows reutilizáveis existem exatamente para isso.

Diagrama com vários repositórios da organização, cada um mantendo sua própria cópia do mesmo workflow antes do uso de workflows reutilizáveis
Cada repositório com a sua cópia do mesmo fluxo de trabalho.

O problema da cópia é a manutenção. Quando uma etapa muda, por exemplo a versão do Node.js, alguém precisa lembrar de atualizar dezenas de arquivos espalhados.

Além disso, cada cópia tende a divergir com o tempo. Assim, a organização perde o padrão e passa a ter pipelines parecidos, mas nunca iguais.

Com workflows reutilizáveis, por outro lado, existe uma única fonte de verdade. Ou seja, você corrige ou melhora o fluxo em um lugar, e todos os repositórios que o chamam recebem a mudança.

Contexto do problema

Vamos apresentar as duas maneiras de reutilizar fluxos de trabalho e como configurá-las. Antes disso, porém, usaremos o modelo simplificado abaixo como base para os dois cenários.

Fluxo de CI simplificado com as etapas de checkout, configuração do Node.js, instalação de dependências, testes e build
Pipeline de CI simplificado usado como base nos exemplos.

O workflow de exemplo atende a uma aplicação React. Ele roda em pushes e pull requests na branch master, instala as dependências, executa os testes e gera o build:

name: Sample react workflow

on:
  push:
    branches: [master]
  pull_request:
    branches: [master]

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [16.x]

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Set up Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm install

      - name: Run the tests
        run: npm test

      - name: Generate build
        run: npm run build

As versões das actions e do Node.js são as do exemplo original. Em um projeto atual, consulte as versões mais recentes de actions/checkout e actions/setup-node antes de copiar o arquivo.

Criando templates de workflow na organização

Talvez você já tenha visto os modelos de exemplo abaixo ao adicionar um novo workflow na aba Actions de um repositório:

Aba Actions do GitHub listando templates de workflow sugeridos para um novo repositório
Templates de exemplo sugeridos pelo próprio GitHub.

Agora, pense em um cenário em que vários repositórios da organização usam a mesma base de configuração. Nesse caso, podemos criar templates customizados que atendem à nossa demanda e ficam disponíveis apenas internamente.

Estrutura do repositório .github

Para adicionar um template de início para a sua organização, siga estas etapas:

  1. Crie um repositório na organização chamado .github.
  2. Dentro dele, crie uma pasta chamada workflow-templates.
  3. Nessa pasta, crie três arquivos: react-workflow.yml, react-workflow.properties.json e icon-wechoo.svg.
Repositório .github da organização com a pasta workflow-templates contendo o YAML, o arquivo de propriedades e o ícone SVG
Estrutura do repositório .github com a pasta de templates.

O arquivo de metadados, com a extensão .properties.json, precisa ter o mesmo nome do arquivo do fluxo de trabalho. Por exemplo, react-workflow.yml pede um react-workflow.properties.json.

Já a imagem SVG pode ser o ícone da sua aplicação, da sua empresa ou da organização. Ela fica na mesma pasta workflow-templates.

O arquivo de propriedades

O conteúdo do react-workflow.properties.json de exemplo será este:

{
  "name": "Wechoo Organization React Workflow",
  "description": "Wechoo Organization CI workflow template.",
  "iconName": "icon-wechoo",
  "categories": ["JavaScript"]
}

As propriedades name e description são obrigatórias, porque aparecem na lista de workflows disponíveis. Por outro lado, iconName e categories são opcionais.

A documentação também prevê a propriedade filePatterns. Com ela, o template só aparece quando o repositório tem, na raiz, um arquivo que combina com uma expressão regular, como um package.json.

Outra dica útil é o placeholder $default-branch no YAML do template. Assim, o GitHub troca o valor pela branch padrão do repositório no momento em que alguém cria o workflow.

Usando o template em outro repositório

Com essa estrutura, criamos fluxos iniciais que qualquer repositório da organização pode reaproveitar.

Template customizado da organização exibido na aba Actions ao lado dos templates do GitHub
O template da organização aparece na aba Actions.

Dessa forma, cada repositório parte de um fluxo inicial e adiciona ou altera etapas conforme a necessidade. Para ver o resultado, entre em outro repositório da organização e abra a aba Actions.

Tela de configuração de um novo workflow criado a partir do template da organização
Criando um workflow a partir do fluxo inicial da organização.

Note, no entanto, que o template copia o YAML para o repositório. A partir daí, aquela cópia segue sua própria vida e não recebe atualizações futuras do template.

Como criar workflows reutilizáveis no GitHub Actions

A segunda maneira de reaproveitar fluxos é referenciar um arquivo de outro repositório com uma única linha. Com isso, os workflows reutilizáveis reduzem ainda mais o custo de manutenção e padronizam os pipelines.

Além disso, você usa um fluxo que já pode ter sido validado e certificado pelo time de plataforma. Na imagem abaixo, diferente do primeiro cenário, a seta aponta na direção contrária.

Diagrama de workflows reutilizáveis em que vários repositórios chamam um único workflow centralizado na organização
Os repositórios chamam o fluxo centralizado, em vez de copiá-lo.

Isso significa que o fluxo não fica no repositório que o usa, e sim em um contexto da organização. Portanto, quem chama apenas aponta para ele.

O repositório de workflows reutilizáveis

Para testar essa abordagem, criei um repositório de modelo chamado workflow-templates com o fluxo de trabalho abaixo:

Arquivo YAML de workflows reutilizáveis no repositório workflow-templates com o gatilho workflow_call
O workflow centralizado no repositório workflow-templates.

Para que o fluxo de trabalho seja reutilizável, adicione a chave workflow_call ao conjunto de valores de on:

on:
  workflow_call:

O arquivo precisa ficar na pasta .github/workflows do repositório. Afinal, o GitHub não aceita subpastas para workflows reutilizáveis.

Chamando workflows reutilizáveis em outro repositório

Com o repositório pronto, vá ao repositório de destino e crie a pasta .github/workflows com um arquivo de nome livre. Para demonstração, o arquivo terá apenas a chamada ao fluxo reutilizável:

name: CI

on:
  push:
    branches: [master]

jobs:
  build:
    # sintaxe: {owner}/{repo}/.github/workflows/{filename}@{ref}
    uses: your-org/workflow-templates/.github/workflows/react-workflow.yml@main
Workflow do repositório de destino chamando workflows reutilizáveis com a chave uses no nível do job
O workflow chamador precisa de poucas linhas.

Repare que a chave uses fica no nível do job, e não dentro de steps. O {ref} pode ser um SHA de commit, uma tag de release ou o nome de uma branch.

Se uma tag e uma branch tiverem o mesmo nome, a tag tem precedência. Para fluxos do mesmo repositório, também vale o caminho local ./.github/workflows/{filename}.

A execução aparece na aba Actions do repositório, como qualquer outro workflow.

Aba Actions exibindo a execução de workflows reutilizáveis chamados pelo repositório de destino
Execução do fluxo reutilizável na aba Actions.

Inputs, secrets e outputs

Um fluxo fixo funciona bem para começar, mas raramente atende todos os projetos. Por isso, os workflows reutilizáveis aceitam parâmetros de entrada, segredos e valores de saída.

Parâmetros em workflows reutilizáveis

Os inputs aceitam os tipos string, number e boolean. No exemplo abaixo, a versão do Node.js deixa de ser fixa e passa a vir de quem chama:

on:
  workflow_call:
    inputs:
      node-version:
        description: "Node.js version"
        required: false
        type: string
        default: "16.x"
    secrets:
      npm-token:
        required: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-node@v1
        with:
          node-version: ${{ inputs.node-version }}
      # o token só existe dentro deste workflow, nunca no log
      - run: npm install
        env:
          NODE_AUTH_TOKEN: ${{ secrets.npm-token }}

Do lado de quem chama, os valores entram pelas chaves with e secrets:

jobs:
  build:
    uses: your-org/workflow-templates/.github/workflows/react-workflow.yml@main
    with:
      node-version: "16.x"
    secrets:
      npm-token: ${{ secrets.NPM_TOKEN }}

Se preferir repassar todos os segredos de uma vez, use secrets: inherit. Contudo, prefira declarar cada segredo quando quiser deixar claro o que o fluxo realmente usa.

Devolvendo valores

Um workflow reutilizável também pode devolver valores para o chamador. Para isso, você mapeia as saídas de um step para o job e, em seguida, do job para o workflow em on.workflow_call.outputs.

Assim, um fluxo de build pode devolver a versão gerada, e o fluxo de deploy usa essa informação no job seguinte.

Limites e cuidados com workflows reutilizáveis

Antes de centralizar tudo, conheça as regras que a documentação do GitHub Actions descreve para esse recurso:

  • É possível conectar até 10 níveis de workflows, ou seja, um chamador e até nove fluxos reutilizáveis aninhados.
  • Loops na árvore de chamadas não são permitidos.
  • As permissões do GITHUB_TOKEN só podem ser mantidas ou reduzidas ao longo da cadeia, nunca elevadas.
  • Variáveis definidas em env no nível do workflow chamador não chegam ao fluxo chamado.
  • Secrets de environment não podem ser repassados pelo chamador, já que workflow_call não aceita a chave environment.

Além disso, o acesso ao fluxo depende da visibilidade do repositório e das configurações de Actions da organização. Então, confira essas permissões quando o repositório central for privado.

Outro cuidado é o versionamento. Apontar para @main é prático, mas qualquer commit no repositório central afeta todos os pipelines imediatamente.

Por isso, em ambientes críticos, prefira tags de release ou o SHA do commit. Dessa forma, cada time decide quando adotar uma nova versão do fluxo.

Por fim, se o seu pipeline precisa de aprovação manual antes do deploy, combine os workflows reutilizáveis com environments. O artigo sobre etapa de aprovação no GitHub Actions mostra como configurar isso.

Boas práticas para o repositório central

Quando vários times dependem do mesmo fluxo, o repositório central vira um produto interno. Portanto, ele merece o mesmo cuidado que qualquer biblioteca compartilhada.

Primeiro, dê nomes claros aos arquivos, como react-build.yml ou dotnet-test.yml. Assim, quem chama entende o propósito do fluxo sem abrir o YAML.

Em seguida, documente cada fluxo em um README com os inputs, os secrets esperados e um exemplo de chamada. Afinal, ninguém deveria precisar ler o código para descobrir quais parâmetros passar.

Também vale proteger a branch principal e exigir revisão em pull requests. Dessa forma, nenhuma alteração chega a dezenas de pipelines sem que alguém a avalie antes.

Além disso, publique tags de release a cada mudança relevante e descreva o que mudou. Com isso, os times que apontam para uma tag sabem exatamente o que ganham ao atualizar a referência.

Por fim, mantenha um repositório de teste que chama os workflows reutilizáveis a cada alteração. Na prática, ele funciona como um teste de integração do próprio pipeline.

Se a sua organização também distribui pacotes internos, o artigo sobre GitHub Packages e GitHub Actions complementa bem este tema.

Templates, workflows reutilizáveis ou composite actions

O GitHub oferece ainda uma terceira forma de reaproveitamento, as composite actions. Por isso, vale comparar as três opções lado a lado.

Recurso O que reaproveita Atualização Ideal para
Template de workflow Arquivo inicial copiado Não propaga mudanças Ponto de partida flexível
Workflow reutilizável Jobs inteiros Propaga pela referência Pipelines padronizados
Composite action Conjunto de steps Propaga pela referência Etapas repetidas dentro de um job

Na prática, os workflows reutilizáveis podem conter vários jobs e usar secrets, e cada step aparece separado no log. Já a composite action roda como um único step dentro de um job e pode ser publicada no Marketplace.

Os templates, por sua vez, também podem chamar workflows reutilizáveis internamente. Assim, você combina a facilidade de criação do template com a padronização do fluxo central.

Conclusão

As duas maneiras apresentadas resolvem o reaproveitamento de fluxos de trabalho. No modelo de templates, temos liberdade para adicionar configurações e até remover etapas do processo.

A grande vantagem dos templates é unir a reutilização da configuração inicial com a flexibilidade de cada repositório. Por outro lado, cada cópia deixa de receber atualizações do modelo.

Já com workflows reutilizáveis, reaproveitamos um pipeline inteiro com três linhas de código. Com isso, ganhamos padronização, versionamento e parâmetros configuráveis.

Para ir além, leia a documentação oficial e o livro Entrega Contínua: Como Entregar Software de Forma Rápida e Confiável. Ele continua sendo uma ótima referência sobre os princípios por trás de qualquer pipeline.

FAQ

Qual a diferença entre template de workflow e workflow reutilizável?

O template copia um YAML inicial para o repositório, que depois evolui sozinho. Já o workflow reutilizável é chamado por referência, então as mudanças no fluxo central chegam a todos os chamadores.

Onde fica o arquivo de um workflow reutilizável?

Ele fica na pasta .github/workflows de um repositório e precisa declarar o gatilho workflow_call na chave on.

Posso passar secrets para um workflow reutilizável?

Sim, declarando cada segredo em on.workflow_call.secrets ou usando secrets: inherit no chamador. Secrets de environment, porém, não podem ser repassados.

Quantos níveis de workflows posso aninhar?

Até 10 níveis, contando o workflow chamador e até nove fluxos reutilizáveis, sem loops.

Qual referência devo usar ao chamar o fluxo?

Para ambientes críticos, prefira uma tag de release ou o SHA do commit, porque uma branch muda a cada novo push.

Glossário

Termo Significado
GitHub Actions Plataforma de automação e CI/CD integrada ao GitHub
Workflow Arquivo YAML que define um processo automatizado com jobs e steps
Job Conjunto de steps executado em um mesmo runner
Step Comando ou action executado dentro de um job
CI/CD Integração contínua e entrega contínua
workflow_call Gatilho que permite que um workflow seja chamado por outro
Template de workflow Modelo inicial disponibilizado pela organização na aba Actions
Composite action Action que agrupa vários steps em um único step
Ref Branch, tag ou SHA de commit usado para apontar uma versão
Secret Valor sensível armazenado de forma criptografada no GitHub

Fontes