Objetivo da Aula
Ao concluir esta aula, você será capaz de:
Gerar workflows GitHub Actions YAML via linguagem natural usando Claude
Implementar gateQualidade() com critérios: 0 falhas + cobertura mínima
Implementar gateCusto() com 3 faixas: bloquear / avisar / aprovar silenciosamente
Usar Claude para analisar PRs e sugerir reviewers e checklist
Completar os 2 TODOs: gateQualidade e gateCusto
Por que isso importa
CI/CD é o gargalo de velocidade de equipes de engenharia. Escrever workflows GitHub Actions corretamente exige memorizar sintaxe YAML específica, actions disponíveis e sua versão, ordem de steps. Um copilot que gera isso via NL acelera o bootstrap de novos projetos.
Mais crítico ainda: gates inteligentes previnem deploys problemáticos. Um gate de qualidade que bloqueia quando cobertura cai abaixo de 80% evita regressões silenciosas. Um gate de custo que bloqueia quando infraestrutura vai custar $130/mês a mais — sem aprovação explícita — previne surpresas na fatura da AWS.
Conceitos Fundamentais
Gerador de GitHub Actions
async function gerarWorkflow(descricao: string): Promise<string> {
const response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 1500,
system: `Você é especialista em GitHub Actions. Gere um workflow YAML válido.
Sempre inclua: name, on (trigger), jobs com steps.
Use actions populares: actions/checkout@v4, actions/setup-node@v4, etc.
Retorne APENAS o YAML, sem explicação.`,
messages: [{ role: 'user', content: `Crie um GitHub Actions workflow para: ${descricao}` }],
})
return response.content[0].type === 'text' ? response.content[0].text : ''
}TODO 1: gateQualidade(resultado, cobertura_minima)
async function gateQualidade(resultado: ResultadoTestes, cobertura_minima: number = 80): Promise<string> {
const problemas: string[] = []
if (resultado.falhando > 0) {
problemas.push(`${resultado.falhando} teste(s) falhando`)
}
if (resultado.cobertura < cobertura_minima) {
problemas.push(`cobertura ${resultado.cobertura}% abaixo do mínimo de ${cobertura_minima}%`)
}
if (problemas.length > 0) {
return `❌ GATE REPROVADO:\n${problemas.map(p => ` • ${p}`).join('\n')}\n\n` +
`Resultado: ${resultado.passando}/${resultado.total} testes, ` +
`${resultado.cobertura}% cobertura, ` +
`${resultado.duracao_segundos.toFixed(1)}s`
}
return `✅ GATE APROVADO:\n` +
` • ${resultado.passando}/${resultado.total} testes passando\n` +
` • Cobertura: ${resultado.cobertura}% (mínimo: ${cobertura_minima}%)\n` +
` • Duração: ${resultado.duracao_segundos.toFixed(1)}s`
}2 critérios mandatórios: zero falhas E cobertura acima do mínimo. Ambos precisam passar.
TODO 2: gateCusto(estimativa, limite_delta_usd)
async function gateCusto(estimativa: EstimativaCusto, limite_delta_usd: number = 100): Promise<string> {
const { delta, custo_mensal_atual, custo_mensal_novo, recursos_adicionados, recursos_removidos } = estimativa
const detalhes = [
` • Custo atual: $${custo_mensal_atual.toFixed(2)}/mês`,
` • Custo novo: $${custo_mensal_novo.toFixed(2)}/mês`,
` • Delta: +$${delta.toFixed(2)}/mês`,
...recursos_adicionados.map(r => ` + ${r}`),
...recursos_removidos.map(r => ` - ${r}`),
].join('\n')
// Faixa 1: delta > limite → bloquear
if (delta > limite_delta_usd) {
return `❌ GATE DE CUSTO REPROVADO:\n${detalhes}\n\n` +
`Delta $${delta.toFixed(2)} excede o limite de $${limite_delta_usd}.\n` +
`Requer aprovação explícita de FinOps antes do deploy.`
}
// Faixa 2: delta entre 50%-100% do limite → aviso
if (delta > limite_delta_usd * 0.5) {
return `⚠️ AVISO DE CUSTO (aprovado com aviso):\n${detalhes}\n\n` +
`Delta $${delta.toFixed(2)} está em ${Math.round(delta/limite_delta_usd*100)}% do limite.`
}
// Faixa 3: delta < 50% do limite → aprovar silenciosamente
return `✅ GATE DE CUSTO APROVADO:\n${detalhes}`
}3 faixas: bloquear (>100%), avisar (50-100%), aprovar (<50%). Lógica de thresholds em cascata — if > 100 → else if > 50 → else.
Aprofundamento Técnico
Integração com Infracost Real
import { execSync } from 'child_process'
function rodarInfracost(dirTerraform: string): EstimativaCusto {
// Gerar baseline (versão atual)
execSync(`infracost breakdown --path=${dirTerraform} --format=json > baseline.json`)
// Gerar estimate para nova versão
execSync(`infracost breakdown --path=${dirTerraform} --compare-to=baseline.json --format=json > nova.json`)
const nova = JSON.parse(fs.readFileSync('nova.json', 'utf-8'))
return {
custo_mensal_atual: nova.pastTotalMonthlyCost,
custo_mensal_novo: nova.totalMonthlyCost,
delta: nova.diffTotalMonthlyCost,
recursos_adicionados: nova.projects[0].diff.resources
.filter(r => r.costCheck === 'new')
.map(r => `${r.name} (+$${r.monthlyCost}/mês)`),
recursos_removidos: [],
}
}Analisador de PR com LLM
async function analisarPR(titulo: string, descricao: string, arquivosAlterados: string[]): Promise<string> {
const response = await client.messages.create({
model: 'claude-haiku-4-5-20251001',
max_tokens: 600,
messages: [{
role: 'user',
content: `Analise este PR e forneça:
1. Tipo de mudança (feature/fix/refactor/docs)
2. Risco (baixo/médio/alto) com justificativa
3. Checklist de revisão (3-5 items)
4. Sugestão de reviewers por área
PR: ${titulo}
Descrição: ${descricao}
Arquivos: ${arquivosAlterados.join(', ')}`,
}],
})
return response.content[0].type === 'text' ? response.content[0].text : ''
}O analisador identifica que mudanças em terraform/ exigem revisor de infra, mudanças em src/db/ exigem DBA, e assim por diante.
Workflow YAML Gerado — Exemplo
# Gerado por: gerarWorkflow('Node.js 20, Jest, TypeScript, deploy ECS se main')
name: CI/CD Pipeline
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- run: npm ci
- run: npm test -- --coverage
- run: npm run build
deploy:
needs: test
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
- run: aws ecs update-service --cluster prod --service api --force-new-deploymentExemplos Anotados
Exemplo 1: Pipeline Completo com 2 Gates
async function main() {
// 1. Gerar workflow
const workflow = await gerarWorkflow('Node.js 20, Jest com cobertura 80%, deploy no ECS se main')
console.log(workflow.slice(0, 300))
// 2. Gate de qualidade
const testes = simularResultadoTestes(80)
console.log(`Testes: ${testes.passando}/${testes.total}, cobertura ${testes.cobertura}%`)
const resultadoQualidade = await gateQualidade(testes, 80)
console.log(resultadoQualidade)
// 3. Gate de custo
const custo = simularInfracost()
console.log(`Delta: +$${custo.delta}/mês`)
const resultadoCusto = await gateCusto(custo, 100)
console.log(resultadoCusto)
// 4. Se ambos aprovados, analisar PR
if (resultadoQualidade.includes('✅') && !resultadoCusto.includes('❌')) {
const analise = await analisarPR(
'feat: adicionar read replica',
'Melhoria de performance de leitura',
['terraform/rds.tf', 'src/db/connection.ts']
)
console.log(analise)
}
}Exemplo 2: TODOs 1 e 2 testando casos extremos
// TODO 1: testes falhando
const testesRuins: ResultadoTestes = { passou: false, total: 150, passando: 145, falhando: 5, cobertura: 85, duracao_segundos: 60 }
console.log(await gateQualidade(testesRuins))
// → ❌ GATE REPROVADO: 5 teste(s) falhando
// TODO 1: cobertura baixa
const testesCobertura: ResultadoTestes = { passou: false, total: 150, passando: 150, falhando: 0, cobertura: 72, duracao_segundos: 55 }
console.log(await gateQualidade(testesCobertura, 80))
// → ❌ GATE REPROVADO: cobertura 72% abaixo do mínimo de 80%
// TODO 2: delta alto
const custoAlto = { custo_mensal_atual: 1000, custo_mensal_novo: 1200, delta: 200, recursos_adicionados: ['aws_rds (+$200)'], recursos_removidos: [] }
console.log(await gateCusto(custoAlto, 100))
// → ❌ GATE DE CUSTO REPROVADO: Delta $200 excede o limite de $100
// TODO 2: delta médio
const custoMedio = { custo_mensal_atual: 1000, custo_mensal_novo: 1070, delta: 70, recursos_adicionados: ['aws_elasticache (+$70)'], recursos_removidos: [] }
console.log(await gateCusto(custoMedio, 100))
// → ⚠️ AVISO DE CUSTO: Delta $70 está em 70% do limitePadrões e Armadilhas
Padrões
Padrão 1: Gate com múltiplos critérios acumula erros
const problemas: string[] = []
if (resultado.falhando > 0) problemas.push(`${resultado.falhando} falhas`)
if (resultado.cobertura < minimo) problemas.push(`cobertura ${resultado.cobertura}%`)
// Retorna TODOS os problemas, não só o primeiro
if (problemas.length > 0) return `❌ GATE REPROVADO:\n${problemas.join('\n')}`Mostrar todos os problemas de uma vez evita ciclos de “fix one, find another”.
Padrão 2: Thresholds em cascata com else if
if (delta > limite) return `❌ BLOQUEADO`
else if (delta > limite * 0.5) return `⚠️ AVISO`
else return `✅ APROVADO`Padrão 3: Mensagem de gate inclui dados concretos
// RUIM
return '❌ Falhou'
// BOM — o desenvolvedor sabe exatamente o que precisa corrigir
return `❌ GATE REPROVADO: 3 testes falhando, cobertura 72% (mínimo: 80%)`Armadilhas
⚠️ Armadilha 1: Gate com await desnecessário
// gateQualidade não precisa ser async se não chamar nada assíncrono
async function gateQualidade(resultado: ResultadoTestes): Promise<string> {
// sem await aqui... o async é desnecessário
return resultado.falhando > 0 ? '❌' : '✅'
}
// Pode ser função síncrona — async só se usar await dentro⚠️ Armadilha 2: simularResultadoTestes com Math.random() — não é determinístico
// Em testes automatizados, use seed ou mock
const resultadoFixo: ResultadoTestes = { passou: false, falhando: 3, cobertura: 78, ... }⚠️ Armadilha 3: Gate de custo com delta negativo (redução de custo)
// Se delta < 0 (infra ficou mais barata), sempre aprovar
if (estimativa.delta <= 0) {
return `✅ APROVADO: redução de $${Math.abs(estimativa.delta).toFixed(2)}/mês`
}
// Sem esse check, delta negativo pode acionar o "aviso" incorretamenteSe não for realizar o laboratório, pule para o próximo capítulo.
Ponte para o Lab
2 TODOs no starter.ts:
TODO 1 — gateQualidade(resultado, cobertura_minima):
const problemas: string[] = []
if (resultado.falhando > 0) problemas.push(`${resultado.falhando} teste(s) falhando`)
if (resultado.cobertura < cobertura_minima) problemas.push(`cobertura ${resultado.cobertura}% < ${cobertura_minima}%`)
if (problemas.length > 0) return `❌ GATE REPROVADO:\n${problemas.map(p => ` • ${p}`).join('\n')}`
return `✅ GATE APROVADO: ${resultado.passando}/${resultado.total} testes, ${resultado.cobertura}% cobertura`TODO 2 — gateCusto(estimativa, limite_delta_usd):
const { delta } = estimativa
if (delta > limite_delta_usd) return `❌ GATE REPROVADO: delta $${delta} > limite $${limite_delta_usd}`
if (delta > limite_delta_usd * 0.5) return `⚠️ AVISO: delta $${delta} (${Math.round(delta/limite_delta_usd*100)}% do limite)`
return `✅ APROVADO: delta $${delta}/mês`Rode com npx ts-node starter.ts e observe workflow gerado, gates avaliados e análise de PR.
Agora você está pronto para o lab.