https://a.storyblok.com/f/270183/16982/0103de22ac/blog_github-desktop_1200x600.png

Configurando CI/CD com o GitHub Actions

Publicado em February 17, 2021

Tempo de leitura: 8 minutos

A integração contínua e a implantação contínua são elementos indispensáveis para organizações que desejam crescer e entregar software de alta qualidade com grande agilidade. Este artigo orienta você passo a passo pelo processo de uso do GitHub Actions para criar fluxos de CI/CD.

O que é CI/CD?

CI/CD é um processo que conecta o desenvolvimento à implantação por meio de um processo de integração automatizado. A ideia é permitir que os desenvolvedores enviem alterações no código como a única etapa necessária para implantar essas alterações.

O pipeline de CI permite que o trabalho em equipe ocorra sem contratempos, automatizando padrões e garantindo a qualidade do software. Ferramentas como linters e testes automatizados fornecem feedback com base no qual é possível integrar as alterações ao ramo principal. Essas alterações integradas acabam sendo enviadas aos usuários finais (produção).

O pipeline de CD recebe o código testado e aprovado. Ele confirma que todos os artefatos necessários estão sendo implantados no local correto. Alguns exemplos: implantar uma aplicação web em um servidor, publicar uma biblioteca no repositório de um gerenciador de pacotes ou publicar um aplicativo móvel na loja de aplicativos.

A automação desses processos garante duas coisas importantes: o processo ocorre rapidamente e é muito menos propenso a erros.

Quer saber como as equipes aceleram seus processos de desenvolvimento, integração e implantação? Pronto para criar um novo pipeline de CI/CD? Vamos lá!

Um fluxo básico de CI/CD

  1. Envie uma alteração para um branch de recurso.

  2. Crie uma solicitação de pull para essa alteração

  3. O CI é acionado e executa o seguinte:

  4. Lint, Teste, Compilação

  5. Assim que a integração contínua (CI) for concluída, ela marca o PR como válido e dá início ao processo de revisão de código

  6. Quando o PR for aprovado, o código será incorporado ao branch master

  7. Após a fusão, o processo de CD é iniciado:

  8. Lint, Teste, Atualização de versão, Implantação

Vamos criar um CI/CD para uma aplicação simples, que você pode clonar aqui.

Como adicionar um fluxo de trabalho do GitHub ao seu projeto

Crie uma pasta .github/workflows. É nessa pasta que vamos adicionar nossas ações do GitHub. As ações são definidas em arquivos YAML, em uma estrutura bastante simples que consiste em três partes:

nome: o nome do fluxo de trabalho, no nosso caso Test and Build

sobre: o gatilho que inicia o fluxo de trabalho quando as condições são atendidas. No nosso caso, queremos que o fluxo de trabalho seja executado quando uma solicitação de pull for criada e enviada. Veremos exemplos de outros gatilhos mais adiante neste tutorial.

tarefas: os comandos propriamente ditos que são executados no fluxo. Podem ser várias tarefas executadas em paralelo ou tarefas que dependem umas das outras. Neste exemplo, há uma tarefa chamada build-testque é executado apenas no sistema operacional ubuntu-latest.

name: Test and Build
on:
 pull_request:
   branches:
     - main
jobs:
 build-test:
   runs-on: ubuntu-latest
   steps:
     - name: Checkout
       uses: actions/checkout@v2
       with:
         fetch-depth: 0
     - name: Setup NodeJS 14
       uses: actions/setup-node@v1
       with:
         node-version: 14
     - name: Install yarn
       run: npm install -g yarn
     - name: Install dependencies
       run: yarn install
     - name: Test
       run: yarn test

A primeira etapa faz o check-out do repositório, utilizando uma ação pré-definida actions/checkout@v2 do marketplace de ações. Passamos a fetch-depth: 0 variável usando o propriedade , que indica ao processo de checkout que deve buscar o repositório com todo o seu histórico. As etapas a seguir são para instalações e execução de comandos bash.

Criação de um fluxo de trabalho de integração contínua

  1. Acionar o fluxo com um Pull Request

  2. Ligue a máquina

  3. Instale o Node.js no computador

  4. Faça o checkout do repositório

  5. Instalar dependências

  6. Teste, Verificação de erros, Compilação

  7. Mostrar os resultados na página de RP

Introdução

  • Se você ainda não fez o fork e clonou o repositório, faça isso agora e crie um branch chamado “add-ci”.

  • Crie um arquivo .github/workflows/ci.yml e copie o conteúdo esse arquivo para o novo arquivo.

  • Confirme as alterações e envie-as para o novo branch. Abra uma solicitação de pull do novo branch para o branch principal.

Se você der uma olhada na solicitação de pull no GitHub, deverá ver os testes sendo executados:

running tests

Você vai perceber que o GitHub já sabe o nome do fluxo de trabalho, o nome da tarefa e o gatilho. Quando estiver concluído, deve ficar assim:

all checks have passed

Você consegue identificar algum problema nesse processo?

Adicionando regras de mesclagem

A primeira questão é que o botão “Mesclar solicitação de pull” já estava disponível durante o processo de CI, quando queremos que a fusão só esteja disponível se todos os fluxos de trabalho necessários tiverem sido concluídos.

Podemos corrigir isso nas configurações do repositório, acessando: Configurações > Ramos > Adicionar regra

branch settings

Aqui, vamos selecionar Exigir que as verificações de status sejam aprovadas antes da fusão e marcaremos todas as opções abaixo dela. Você verá todos os fluxos de trabalho necessários para habilitar a fusão — no nosso caso, temos apenas build-test.

require status checks

Para Padrão do nome da agência insira main e crie a regra. E se você voltar à página de pull requests, verá que nenhum pull request pode ser mesclado antes que os testes sejam aprovados, a menos, é claro, que você tenha privilégios de administrador.

cannot merge yet

Integração após a revisão de código

Antes de prosseguirmos, também precisamos decidir como lidar com a aprovação de Pull Requests. Existem três abordagens principais:

  1. Permitir que qualquer pessoa faça a integração assim que os testes forem aprovados

  2. Fazer a integração automaticamente assim que os testes forem aprovados

  3. Exigir uma revisão de código para que a fusão seja realizada

Vamos optar pela primeira opção, que é a mais comum, e que podemos alterar editando a regra do branch que acabamos de criar. No topo da lista, marque “Exigir revisões de pull request antes da fusão” e clique em “Salvar”.

É isso aí sobre integração contínua! Agora, todos os novos Pull Requests serão testados e revisados antes de serem incorporados ao branch principal! O próximo passo é a implantação.

Criação de um fluxo de trabalho de implantação contínua

A implantação pode envolver desde o envio de arquivos estáticos para o GitHub, passando pela publicação de pacotes npm, até a implantação de toda uma malha de microsserviços. Este é o processo que seguiremos para nossa aplicação assim que o código for mesclado ao repositório principal:

  1. Instalar dependências

  2. Teste

  3. Atualizar versão

  4. Construir

  5. Marque o lançamento

  6. Publicar no npm

  7. Implantar o aplicativo de demonstração

Vamos examinar as diferentes partes do arquivo YAML que controla as ações:

O Gatilho

Essas são as condições em que nosso código de implantação será executado. Ele será acionado quando uma solicitação de pull for fechada e mesclada.

name: Test, Build and Deploy

on:
 pull_request:
   types: [closed]

jobs:
 build-test-release:
   if: github.event.action == 'closed' && github.event.pull_request.merged == true
   runs-on: ubuntu-latest

Permissões do Git Actions

Na etapa de finalização da compra, vamos adicionar uma linha a mais que não estava no arquivo YAML anterior:

steps:
  - name: Checkout
    uses: actions/checkout@v2
    with:
      fetch-depth: 0
      token: ${{ secrets.CI_REPOSITORY_ACCESS_TOKEN }}

Precisamos definir explicitamente o token para que possamos realizar ações para as quais o token padrão, secrets.GITHUB_TOKENnão tem permissão para realizar. No nosso caso, queremos enviar uma alteração para o branch “main” sem revisão de código; por isso, precisamos de um token com privilégios de administrador. Definimos esse token em Configurações -> Segredos.

Atualizar as versões dos pacotes

Essa etapa varia de projeto para projeto. A ideia é atualizar a versão do pacote que você pretende publicar no repositório de pacotes. Faremos isso analisando todas as bibliotecas que sofreram alterações neste branch e atualizando a versão NPM delas:

- name: Raise version of affected libraries
  run: |
    LATEST_TAG=$(git tag -l "v*" --sort=-version:refname | head -n 1)
    LIBS=$(yarn nx affected:libs --base=$LATEST_TAG --head=HEAD --plain | awk 'NR > 2 && $1 != "Done" { print $1 }')
    for LIBRARY in $LIBS
    do
      cd ./libs/$LIBRARY
      npm version minor --no-git-tag-version --no-push
      echo "Bumping $LIBRARY"
      cd ..
      cd ..
    done
    npm version minor --no-git-tag-version --no-push

Observe que estamos usando o NX, uma estrutura de gerenciamento de monorepo, que ajuda a tornar as compilações e o desenvolvimento muito mais rápidos. Utilizamos o affected recurso, que nos informa quais bibliotecas foram “afetadas” pelo PR.

Construir

O processo de compilação também utiliza o affected recurso também. Ele compila as bibliotecas que sofreram alterações em relação ao ramo principal com as predefinições de produção:

- name: Build components
  run: yarn nx affected:build --prod --with-deps --base=main

Marque o lançamento

A marcação de liberação é feita em duas etapas:

O primeiro, get-npm-version, utiliza uma ação do marketplace que extrai a versão do pacote principal. A segunda etapa confirma as alterações feitas na Raise version etapa mencionada acima, cria uma tag com a versão do novo `package.json` e envia a alteração.

Lembra-se do CI_REPOSITORY_ACCESS_TOKEN segredo? É aqui que ele entra em ação. Como impedimos a fusão com o branch master sem a revisão de um pull request, precisamos de um token de administrador para essa parte, o que nos permitirá contornar a regra e enviar as alterações automaticamente.

- name: get-npm-version
  id: package-version
  uses: martinbeentjes/npm-get-version-action@master

- name: Tag the release
  run: |
    git fetch
    git config user.email "unicorn.ci@yonatankra.com"
    git config user.name "Unicorn CI"
    git add --all
    git commit -m "update versions to ${{ steps.package-version.outputs.current-version }}"
    git push

- name: Tag release
  run: |
    git tag -a v${{ steps.package-version.outputs.current-version }} -m "tag release v${{ steps.package-version.outputs.current-version }}"
    git push --follow-tags

Implantar o aplicativo de demonstração

Por fim, compilamos nosso aplicativo e o implantamos:

- name: Build Demo
  run: yarn build:deploy

- name: Deploy 🚀
  uses: JamesIves/github-pages-deploy-action@3.7.1
  with:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    BRANCH: gh-pages # The branch the action should deploy to.
    FOLDER: dist/apps/unicorn-hunt # The folder the action should deploy.
    CLEAN: true # Automatically remove deleted files from the deploy branch

Você pode encontrar o código YAML completo aqui

Otimização do fluxo de CI/CD

Como usar o cache no GitHub Actions

Se você acompanhou os passos e criou o fluxo de CI/CD, deve ter percebido que ele está demorando um pouco para ser executado. Uma solução simples para isso é usar o cache e reduzir o tempo de instalação das dependências, o que também podemos fazer com o GitHub Actions.

O armazenamento em cache funciona verificando se há um acerto no cache e, caso seja encontrado, pulando a etapa de instalação das dependências. Veja como fica o código:

- name: Install yarn
  run: npm install -g yarn

- name: Get yarn cache directory path
  id: yarn-cache-dir-path
  run: echo "::set-output name=dir::$(yarn config get cacheFolder)"

- name: Cache yarn dependencies
  uses: actions/cache@v2
  id: yarn-cache
  with:
    path: |
      ${{ steps.yarn-cache-dir-path.outputs.dir }}
      **\node_modules
    key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
    restore-keys: |
      ${{ runner.os }}-yarn-

- name: Install dependencies
  if: steps.yarn-cache.outputs.cache-hit != 'true'
  run: yarn install

A primeira etapa instala o yarn globalmente. O segundo passo obtém o caminho da pasta de cache do Yarn. Em seguida, a etapa de armazenamento em cache propriamente dita utiliza a ação de cache, passa os caminhos para o cache (node_modules e a pasta de cache do Yarn) e atribui uma chave de cache ao arquivo yarn.lock por meio de um hash. Dessa forma, se instalarmos uma nova dependência ou atualizarmos uma dependência, isso causaria uma falha de cache. A etapa final define uma condição na install-dependencies etapa usando o variável cache-hit definida pela ação de cache.

Modularização do processo

Então, o CI/CD está em execução. Ele está usando o cache. Mencionamos anteriormente que também gostaríamos de publicar nossas duas bibliotecas na libs pasta no npm. Tecnicamente, poderíamos adicionar mais uma etapa ao nosso processo de CD, mas isso poderia torná-lo complicado e difícil de manter.

Em vez disso, dividimos o processo em fluxos de trabalho separados, que são acionados pelo processo principal do CD. O CD, então, lançaria uma nova versão, o que acionaria os outros processos, resultando na implantação e na publicação no NPM.

Além de facilitar a manutenção, isso também permite um melhor tratamento de erros. Afinal, e se a atualização da versão e a publicação forem bem-sucedidas, mas a implantação falhar? Isso nos permite simplesmente executar a implantação novamente e depurar até que ela seja bem-sucedida.

O código completo da solução pode ser encontrado aqui.

Resumo

Neste artigo, criamos um processo de CI/CD usando o GitHub Actions.

Começamos adicionando o processo de CI que executa os testes e avisa aos revisores de código que a solicitação de pull está pronta para revisão. Também aprendemos como bloquear a fusão em um branch caso os testes e a revisão não sejam aprovados. Analisamos um código de CI simplificado, mas você pode adicionar mais comandos do Bash à fase de testes ou até mesmo incluir etapas adicionais, como linting ou o `prettier`.

Em seguida, criamos um processo de CD que atualiza as versões e implanta a demonstração. Aprendemos como podemos armazenar em cache a instalação das dependências para economizar tempo e como modularizar o processo de CD para obter mais controle e facilidade de manutenção.

Há muito mais que pode ser feito com o GitHub Actions. Esperamos que este tutorial tenha ajudado você a dar os primeiros passos na criação de um processo de CI/CD que seja útil e significativo para sua equipe.

Compartilhar:

https://a.storyblok.com/f/270183/400x400/7bf76cb05c/yonatankra.png
Yonatan KraArquiteto de Software da Vonage

Yonatan já participou de alguns projetos incríveis tanto no meio acadêmico quanto no setor privado — desde C/C++, passando por Matlab, até PHP e JavaScript. Ex-CTO da Webiks e arquiteto de software na WalkMe. Atualmente, ele é arquiteto de software na Vonage e instrutor na Egghead.