Pipeline CI/CD Completo
Construa um pipeline CI/CD completo com GitHub Actions integrando testes, linting, formatação, verificação de tipo, varredura de segurança, portões de cobertura e deploy
Pipeline CI/CD Completo
Esta lição de capstone reúne tudo que você aprendeu — testes, cobertura, linting, formatação, verificação de tipo e varredura de segurança — em um pipeline CI/CD completo. Você construirá um workflow profissional do GitHub Actions com portões de qualidade em cada estágio.
Arquitetura do Pipeline
O Workflow Completo do GitHub Actions
1. Workflow Principal (Acionado por PR e Push)
# .github/workflows/ci.yml
name: Pipeline CI/CD
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
env:
PYTHON_VERSION: '3.12'
COVERAGE_THRESHOLD: '85'
jobs:
# ============================================================
# ESTÁGIO 1: Verificações de Qualidade (feedback rápido)
# ============================================================
quality:
name: Qualidade de Código
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Cache de dependências pip
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements*.txt') }}
restore-keys: |
${{ runner.os }}-pip-
- name: Instalar dependências
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
- name: Verificar formatação (Black)
run: |
black --check --diff --line-length 100 src/ tests/
continue-on-error: false
- name: Verificar ordem de imports (isort)
run: |
isort --check-only --diff --profile black src/ tests/
continue-on-error: false
- name: Lint com Ruff
run: |
ruff check src/ tests/
continue-on-error: false
- name: Lint com Flake8
run: |
flake8 src/ tests/ --statistics --max-line-length=100
continue-on-error: false
# ============================================================
# ESTÁGIO 2: Análise Estática
# ============================================================
static-analysis:
name: Análise Estática
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Instalar dependências
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pip install mypy types-requests types-PyYAML
- name: Verificação de tipo com mypy
run: |
mypy src/ --strict --ignore-missing-imports
continue-on-error: false
- name: Varredura de segurança com bandit
run: |
bandit -r src/ -ll -f json -o bandit_report.json
continue-on-error: false
- name: Upload relatório de segurança
if: always()
uses: actions/upload-artifact@v4
with:
name: security-reports
path: bandit_report.json
# ============================================================
# ESTÁGIO 3: Testes & Cobertura
# ============================================================
test:
name: Testes & Cobertura
runs-on: ubuntu-latest
needs: [quality, static-analysis]
services:
postgres:
image: postgres:16
env:
POSTGRES_DB: test_db
POSTGRES_USER: test_user
POSTGRES_PASSWORD: test_pass
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Instalar dependências
run: |
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pip install -e .
- name: Executar testes unitários com cobertura
run: |
pytest tests/unit/ \
--cov=src \
--cov-report=term-missing \
--cov-report=xml:coverage-unit.xml \
--cov-fail-under=${{ env.COVERAGE_THRESHOLD }} \
--junitxml=test-report-unit.xml \
-v
env:
DATABASE_URL: postgresql://test_user:test_pass@localhost:5432/test_db
- name: Executar testes de integração
run: |
pytest tests/integration/ \
--cov=src \
--cov-report=term-missing \
--cov-report=xml:coverage-integration.xml \
--cov-append \
--junitxml=test-report-integration.xml \
-v
env:
DATABASE_URL: postgresql://test_user:test_pass@localhost:5432/test_db
- name: Combinar relatórios de cobertura
run: |
pip install coverage
coverage combine
coverage report --fail-under=${{ env.COVERAGE_THRESHOLD }}
coverage xml -o coverage.xml
- name: Upload cobertura para Codecov
uses: codecov/codecov-action@v4
with:
file: ./coverage.xml
flags: unittests,integration
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}
- name: Upload relatórios de teste
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports
path: |
coverage*.xml
test-report*.xml
# ============================================================
# ESTÁGIO 4: Build & Empacotamento
# ============================================================
build:
name: Build do Pacote
runs-on: ubuntu-latest
needs: [test]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Build do pacote
run: |
pip install build
python -m build
- name: Upload artefato do build
uses: actions/upload-artifact@v4
with:
name: package
path: dist/
# ============================================================
# ESTÁGIO 5: Deploy (apenas em push para main)
# ============================================================
deploy:
name: Deploy
runs-on: ubuntu-latest
needs: [build]
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
steps:
- uses: actions/download-artifact@v4
with:
name: package
path: dist/
- name: Deploy para produção
run: |
echo "Fazendo deploy para produção..."
env:
DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
- name: Health check
run: |
echo "Executando health check..."Visualização do Pipeline
Portões de Qualidade
Portões de qualidade garantem que toda mudança atenda a um padrão mínimo antes de merge:
# Configuração de portão de qualidade
name: Portão de Qualidade
on:
pull_request_review:
types: [submitted]
jobs:
quality-gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/github-script@v7
with:
script: |
const requiredChecks = [
'Qualidade de Código',
'Análise Estática',
'Testes & Cobertura',
];
const { data: checks } = await github.rest.checks.listForRef({
owner: context.repo.owner,
repo: context.repo.repo,
ref: context.payload.pull_request.head.sha,
});
const failed = checks.check_runs
.filter(c => requiredChecks.includes(c.name))
.filter(c => c.conclusion !== 'success');
if (failed.length > 0) {
core.setFailed(
`Verificações necessárias falharam: ${failed.map(c => c.name).join(', ')}`
);
}Script de Portão de Cobertura
# scripts/check_coverage_gate.py
"""
Verifica se cobertura atende ao limiar.
Uso: python scripts/check_coverage_gate.py coverage.xml 85
"""
import sys
import xml.etree.ElementTree as ET
def check_coverage(coverage_file: str, threshold: float) -> bool:
tree = ET.parse(coverage_file)
root = tree.getroot()
total_lines = 0
covered_lines = 0
for package in root.findall(".//package"):
for cls in package.findall(".//class"):
lines = cls.find("lines")
if lines is not None:
for line in lines.findall("line"):
total_lines += 1
if line.get("hits") != "0":
covered_lines += 1
if total_lines == 0:
print("Nenhuma linha encontrada no relatório de cobertura")
return False
coverage_pct = (covered_lines / total_lines) * 100
print(f"Cobertura: {coverage_pct:.2f}% (limiar: {threshold}%)")
if coverage_pct < threshold:
print(f"❌ FALHOU: Cobertura {coverage_pct:.2f}% < {threshold}%")
return False
print(f"✅ PASSOU: Cobertura {coverage_pct:.2f}% >= {threshold}%")
return True
if __name__ == "__main__":
if len(sys.argv) != 3:
print("Uso: check_coverage_gate.py <coverage.xml> <threshold>")
sys.exit(1)
success = check_coverage(sys.argv[1], float(sys.argv[2]))
sys.exit(0 if success else 1)Requisitos de Desenvolvimento
# requirements-dev.txt
# Testes
pytest>=8.0,<9.0
pytest-cov>=5.0,<6.0
pytest-xdist>=3.0,<4.0
pytest-asyncio>=0.21,<1.0
# Linting
ruff>=0.4,<1.0
flake8>=7.0,<8.0
flake8-docstrings>=1.7,<2.0
flake8-bugbear>=24.0,<25.0
# Formatação
black>=24.0,<25.0
isort>=5.13,<6.0
# Análise estática
mypy>=1.10,<2.0
bandit>=1.7,<2.0
types-requests>=2.31
types-PyYAML>=6.0
# Pre-commit
pre-commit>=3.7,<4.0
# Cobertura
coverage>=7.0,<8.0
# Build
build>=1.0,<2.0
twine>=5.0,<6.0Makefile para Desenvolvimento Local
# Makefile
.PHONY: help setup install lint format typecheck security test coverage build clean
help:
@echo "Comandos disponíveis:"
@echo " make setup - Instalar todas as dependências"
@echo " make lint - Executar todos os linters"
@echo " make format - Formatar todo o código"
@echo " make typecheck - Executar verificação de tipo mypy"
@echo " make security - Executar varredura de segurança bandit"
@echo " make test - Executar todos os testes"
@echo " make coverage - Executar testes com relatório de cobertura"
@echo " make build - Build de pacotes de distribuição"
@echo " make clean - Remover artefatos de build"
setup:
python -m pip install --upgrade pip
pip install -r requirements-dev.txt
pre-commit install
lint:
ruff check src/ tests/
flake8 src/ tests/ --statistics --max-line-length=100
format:
isort --profile black src/ tests/
black --line-length 100 src/ tests/
format-check:
isort --check-only --profile black src/ tests/
black --check --line-length 100 src/ tests/
typecheck:
mypy src/ --strict --ignore-missing-imports
security:
bandit -r src/ -ll
test:
pytest tests/ -v --cov=src
coverage:
pytest tests/ --cov=src --cov-report=term-missing --cov-report=html --cov-fail-under=85
build:
python -m pip install --upgrade build
python -m build
clean:
rm -rf dist/ build/ *.egg-info .coverage coverage.xml coverage_html/ __pycache__
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
all: lint format-check typecheck security test coverage
@echo "✅ Todas as verificações passaram!"Workflows Específicos por Ambiente
Workflow de PR (Rápido)
# .github/workflows/pr.yml
name: Verificações de PR
on: pull_request
jobs:
quick-checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Verificações rápidas
run: |
pip install ruff flake8 black isort mypy bandit
ruff check src/
black --check --diff src/
isort --check-only --diff --profile black src/
mypy src/ --ignore-missing-imports
bandit -r src/ -llWorkflow de Branch Main (Completo)
# .github/workflows/main.yml
name: CI/CD Branch Main
on:
push:
branches: [main]
jobs:
full-pipeline:
name: Pipeline Completo
uses: ./.github/workflows/ci.yml
secrets: inherit
publish:
name: Publicar no PyPI
runs-on: ubuntu-latest
needs: [full-pipeline]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Build do pacote
run: |
pip install build
python -m build
- name: Publicar no PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
release:
name: Criar GitHub Release
runs-on: ubuntu-latest
needs: [publish]
steps:
- uses: actions/checkout@v4
- uses: softprops/action-gh-release@v1
with:
generate_release_notes: trueMétricas e Monitoramento do Pipeline
| Estágio | Duração | Taxa de Aprovação | O que Monitorar |
|---|---|---|---|
| Qualidade | ~1-2 min | > 99% | Número de avisos de lint |
| Análise Estática | ~2-3 min | > 99% | Erros mypy, achados bandit |
| Testes | ~5-15 min | > 95% | Testes instáveis, tendências de cobertura |
| Build | ~1-2 min | > 99% | Tempo de build, mudanças de dependência |
| Deploy | ~2-5 min | > 99% | Falhas de deploy, rollbacks |
Exercícios Práticos
-
Crie o Workflow Completo: Crie um
.github/workflows/ci.ymlque inclua verificações de qualidade, análise estática e testes. Envie para um repositório GitHub e verifique se executa. -
Adicione um Container de Serviço: Modifique o workflow CI para incluir um container de serviço PostgreSQL ou Redis. Atualize seus testes de integração para usar este serviço.
-
Portão de Cobertura: Adicione um portão de cobertura personalizado que falhe o build se a cobertura cair abaixo de 80%. Use o módulo Python
coveragepara analisar o relatório XML. -
Deploy Condicional: Crie um workflow que só faça deploy quando commits são enviados para a branch
mainE todos os estágios anteriores passam. Adicione uma etapa de aprovação manual usando ambientes. -
Jobs em Paralelo: Estruture seu workflow para que linting, verificação de tipo e varredura de segurança executem em paralelo, depois os testes executem após todos os três completarem. Meça a economia de tempo.
-
Detecção de Testes Instáveis: Adicione uma etapa que re-executa testes falhos uma vez para detectar testes instáveis. Se um teste passar na re-execução, marque-o como instável mas não falhe o build.
-
Matriz de Build: Crie uma matriz de build que execute testes contra Python 3.10, 3.11 e 3.12. Garanta que todas as versões passem antes do deploy.
-
Configuração Completa do Projeto: Configure um projeto Python completo com: pyproject.toml, ganchos pre-commit, Makefile, pipeline CI/CD completo e README com badges de status de build.
Resumo
- GitHub Actions fornece uma plataforma CI/CD poderosa e integrada
- Pipelines multi-estágio separam preocupações: qualidade → análise → teste → build → deploy
- Portões de qualidade em cada estágio impedem que código ruim progrida
- Containers de serviço permitem testes de integração reais no CI
- Execução paralela acelera o feedback para tarefas independentes
- Deploy condicional garante que apenas código testado e de qualidade chegue à produção
- Artefatos preservam relatórios e saídas de build entre estágios do workflow
- Paridade local via Makefile garante que desenvolvedores possam reproduzir verificações CI localmente
Você construiu um pipeline CI/CD completo. Cada commit agora passa por formatação, linting, verificação de tipo, varredura de segurança, testes, portões de cobertura e deploy automatizado. Isto é engenharia de software profissional na prática.