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.
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.
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:
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:
- Crie um repositório na organização chamado
.github. - Dentro dele, crie uma pasta chamada
workflow-templates. - Nessa pasta, crie três arquivos:
react-workflow.yml,react-workflow.properties.jsoneicon-wechoo.svg.
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.
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.
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.
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:
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
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.
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_TOKENsó podem ser mantidas ou reduzidas ao longo da cadeia, nunca elevadas. - Variáveis definidas em
envno nível do workflow chamador não chegam ao fluxo chamado. - Secrets de environment não podem ser repassados pelo chamador, já que
workflow_callnão aceita a chaveenvironment.
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.