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)