DEV Community

Felippe Deiró
Felippe Deiró

Posted on • Originally published at dev.to

CI/CD com GitHub Actions e teste local com Act

O uso de pipeline é útil para automatizar o processo de deploy, executar testes automatizados, agendar tarefas, fazer verificação de segurança e muitos outros. Qualquer coisa que você queira executar de forma automática, consegue com uma pipeline.

Vamos entender o funcionamento da pipeline do GitHub Actions, usando como base o arquivo do projeto agenda-telefonica. O que será explicado?

  • Como usar container
  • Criação do job de CI
  • Criação do job de build do Docker
  • Criação de jobs para aletar via Telegram em caso de sucesso e de falha da pipeline
  • Usando Act para simular o GitHub Actions

O arquivo completo da pipeline pode ser vista aqui.

Sumário

Workflow

Para criar uma pipeline e que ela execute no github, deve seguir esta estrutura:

.github/ └── workflows/ └── backend.yaml 
Enter fullscreen mode Exit fullscreen mode

Todos os arquivos dentro do diretório workflow/ são pipelines. Todo o código mostrado, ficará dentro de backend.yaml.

Para entender as palavras chave da pipeline, pode ser visto aqui.

Events, filters e environment

name: Pipeline CI-CD do Backend env: WORKDIR: ./backend on: push: branches: [main] pull_request: branches: [main] workflow_dispatch: 
Enter fullscreen mode Exit fullscreen mode

Primeiro definimos o nome do workflow com o name.

O env são as variáveis que serão usadas nos jobs desse workflow. Foi criado a variável WORKDIR que faz referência ao diretório que alguns comando serão executados.

Agora temos o on que define quais serão os eventos e filtros que irão dizer quando o workflow será executado. Esse bloco que dizer que o acionamento desse workflow acontecerá quando:

  • Ocorrer o evento push na branch main.
  • Ocorrer o evento pull_request na branch main.
  • Ocorrer o evento workflow_dispatch, ou seja, quando for acionado manualmente como por exemplo pelo GitHub via Browser.

A chave branches é um filtro e só permite a execução do workflow se o evento push ou o pull_request for direcionado para a branch main.

Mais sobre events e filters.

Job CI: container, testes e vulnerabilidades

jobs: ci: runs-on: ubuntu-latest strategy: matrix: version: [21.7.3] services: mysql: image: mysql:8.0 ports: - 3306:3306 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: agenda_test options: >- --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5 steps: - uses: actions/checkout@v4 - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ matrix.version }} - name: Install dependencies working-directory: ${{ env.WORKDIR }} run: | npm ci - name: Run test coverage working-directory: ${{ env.WORKDIR }} run: npm run test:cov:ci env: AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Check dependencies for vulnerabilities working-directory: ${{ env.WORKDIR }} run: npm audit --audit-level=high 
Enter fullscreen mode Exit fullscreen mode

O jobs possui outros job, que nesse caso seria o ci, build, notify-success e notify-failure. O job ci será executado em uma máquina ubuntu na ultima versão, como diz a chave e o valor runs-on: ubuntu-latest.

As chaves strategy.matrix.version com o valor 21.7.3, significa que esse job será executado 1x, mas pode ser executado mais vezes caso coloque mais versões. A versão é referente a do Nodejs que será configurado nessa máquina.

Como os testes de integração usa o MySQL, precisamos de subir um container e a chave services faz isso.

Sevices (containers)

jobs: ... ci: ... services: mysql: image: mysql:8.0 ports: - 3306:3306 env: MYSQL_ROOT_PASSWORD: root MYSQL_DATABASE: agenda_test options: >- --health-cmd "mysqladmin ping" --health-interval 10s --health-timeout 5s --health-retries 5 ... 
Enter fullscreen mode Exit fullscreen mode

A chave mysql é só um rótulo que damos para esse container. A imagem que será usada é do MySQL versão 8.0 image: mysql:8.0 e irá ouvir na porta 3306 (ports: e - 3306:3306). O env diz as variáveis de ambiente que será usado e elas precisam ser as mesmas que foi usado no docker-compose.yaml para ser acessado pela aplicação.

O options são as flags ou options usadas no comando docker container create (veja sobre aqui). Os options de healthcheck foi usado para dar continuidade na execução do job quando o container estiver pronto para uso. O que cada um significa?

  • --health-cmd "mysqladmin ping": vai executar o comando mysqladmin ping dentro do container e vai verificar se o status do mysql está como pronto ou não.
  • --health-interval 10s: irá executar o teste acima a cada 10s.
  • --health-timeout 5s: quando um teste é executado, deve esperar 5s para obter resposta dele.
  • --health-retries 5: o teste será executado no máximo 5x em caso de falhas.

Steps

jobs: ... ci: ... steps: - uses: actions/checkout@v4 - name: Use Node.js uses: actions/setup-node@v4 with: node-version: ${{ matrix.version }} - name: Install dependencies working-directory: ${{ env.WORKDIR }} run: | npm ci - name: Run test coverage working-directory: ${{ env.WORKDIR }} run: npm run test:cov:ci env: AWS_REGION: ${{ secrets.AWS_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }} AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - name: Check dependencies for vulnerabilities working-directory: ${{ env.WORKDIR }} run: npm audit --audit-level=high 
Enter fullscreen mode Exit fullscreen mode

Temos finalmente as verificações de qualidade e segurança do código que são os steps.

Primeiro step é o uses vai executar uma action (um script) que seria o actions/checkout@v4. Essa action da acesso ao workflow, o código que está no repositorio.

Segundo step é a configuração do nodejs usando a action actions/setup-node@v4. Na chave with, declaramos o node-version passando como valor o ${{ matrix.version }}, que pega a versão do node que colocamos no strategy.matrix.version.

Terceiro step vai instalar as dependências das exatas versões que estão no package-lock.json. O working-directory diz em qual diretório que vai ser executado esse step. Na chave run, nós escrevemos o comando para ser executado, no cado o npm ci.

Quarto step é a execução do teste e da cobertura de código. O comando usado é o npm run test:cov:ci que vai setar NODE_ENV=ci para usar as configurações do banco de dados do service, que já foi pré definida no código. O env possui variáveis de configuração da AWS, porém só a região que precisa ser informada, as outras pode deixar como string vazia. A cobertura de código deve ser superior a 85%, como foi definido no jest.config.js.

O ${{ secrets.VARIAVEL }} é uma forma de não deixar dados sensíveis à mostra. Mais a diante vou explicar como fazer essa configuração no Act e no GitHub.

Quinto step é uma checagem de vulnerabilidades. Se tiver alguma vulnerabilidade com level high para cima terá um erro.

Job Build: push Docker Hub

jobs: ... build: needs: [ci] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v3 with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: context: ${{ env.WORKDIR }}/ file: ${{ env.WORKDIR }}/Dockerfile push: true tags: deirofelippe/agenda-telefonica-backend:latest 
Enter fullscreen mode Exit fullscreen mode

Nesse job será feito o build e o push da imagem docker para o docker hub. O build só será executado após o ci, essa configuração é feita através do needs. Por padrão o GitHub Actions executa os jobs em paralelo e caso queira criar dependência entre eles, torna-los sequencial, deve ser informado no needs.

Primeiro step vai configurar o QEMU que emula plataformas para fazer o build.

Segundo step configura o BuildX para fazer build da imagem.

Terceiro step faz login no docker hub, que será armazenado a imagem.

Quarto step é o que vai realmente fazer o build e o push. Dentro de with, vamos configurar o context que será a raiz do código, o file que será o caminho para o Dockerfile, o push que é setado como true, a tags que vamos informar o nome e a tag da imagem.

Job Notify Success

jobs: ... notify-success: needs: [build] runs-on: ubuntu-latest if: ${{ success() }} steps: - name: Notify Telegram If Success run: | TELEGRAM_MESSAGE='[Agenda Telefônica] Pipeline foi finalizada' CURL_DATA=$(printf '{"chat_id":"%s","text":"%s"}' "${{ secrets.TELEGRAM_CHAT_ID }}" "$TELEGRAM_MESSAGE") curl https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \ --request POST \ --header 'Content-Type: application/json' \ --data "$CURL_DATA" 
Enter fullscreen mode Exit fullscreen mode

Esse job depende do build. A chave e valor if: ${{ success() }}, permite a execução desse job somente se não houve erro no build. Caso tenha dado erro no ci, o build não será executado e por consequência o notify-success também não, pois o success() retornará falso.

Primeiro step vai notificar via Telegram que a pipeline não teve erro. Será feita uma requisição com o curl, informando a url da API do telegram, o token do bot, o id do chat e a mensagem.

Saiba mais sobre o if aqui.

Job Notify Failure

jobs: ... notify-failure: needs: [build] runs-on: ubuntu-latest if: ${{ failure() }} steps: - name: Notify Telegram If Failure run: | TELEGRAM_MESSAGE='[Agenda Telefônica] Falha na pipeline' CURL_DATA=$(printf '{"chat_id":"%s","text":"%s"}' "${{ secrets.TELEGRAM_CHAT_ID }}" "$TELEGRAM_MESSAGE") curl https://api.telegram.org/bot${{ secrets.TELEGRAM_BOT_TOKEN }}/sendMessage \ --request POST \ --header 'Content-Type: application/json' \ --data "$CURL_DATA" 
Enter fullscreen mode Exit fullscreen mode

Esse job depende do build. A chave e valor if: ${{ failure() }}, permite a execução desse job somente se HOUVER erro no build.

Primeiro step vai notificar via Telegram que a pipeline teve erro.

Simulando o GitHub Actions com Act localmente

A instalação do Act pode ser vista aqui. É preciso ter o Docker instalado.

Como fiz a instalação usando o GitHub CLI, é só executar o comando gh extension exec act --directory ./ --job ci --secret-file ./.env.act-secrets.

Vamos entender o que cada flag significa:

  • --directory ou -C é o diretório onde está o diretório .github.
  • --job ou -j é o job específico que deseja executar. Sim, se eu quiser, posso executar somente o job build, porem se tiver a keyword needs, deve ser comentada senão suas dependências serão executadas.
  • --secret-file é o arquivo no formato .env que será usado como secrets para ser acessado usando ${{ secrets.VARIAVEL }}.

Executar sem a flag --job, significa que toda a pipeline será executada.

Image description

Outras flags:

  • --graph mostra a ordem de execução dos jobs em fromato de grafo
  • --list ou -l mostra os jobs, workflows e events associados

Image description

Image description

Conclusão

Obrigado pela leitura :)

Repositório do projeto: https://github.com/deirofelippe/agenda-telefonica

Top comments (0)