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
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:
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 branchmain
. - Ocorrer o evento
pull_request
na branchmain
. - 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
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
...
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 comandomysqladmin 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
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
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"
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"
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 keywordneeds
, 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.
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
Conclusão
Obrigado pela leitura :)
Repositório do projeto: https://github.com/deirofelippe/agenda-telefonica
Top comments (0)