Testando infraestrutura
Código de infraestrutura tem uma propriedade desagradável: o erro só aparece quando já mudou alguma coisa real. Não existe “rodar localmente e ver se funciona” para uma regra de firewall. Por isso o teste aqui é organizado por quanto custa descobrir o problema, do mais barato para o mais caro.
A pirâmide de testes de IaC
Seção intitulada “A pirâmide de testes de IaC”| Camada | Custo | O que pega | Quando roda |
|---|---|---|---|
| Formato e lint | segundos | erro de sintaxe, antipadrão, provider desatualizado | a cada commit |
| Teste de plano | segundos | valores errados, recurso ausente, padrão inseguro | a cada PR |
| Política sobre o plano | segundos | violação de regra da organização | a cada PR |
| Estimativa de custo | segundos | surpresa de fatura | a cada PR |
| Integração real | minutos a horas | o que só a nuvem revela | merge, ou por noite |
A base larga é onde está quase todo o retorno. Muita gente pula direto para o teste de integração, gasta caro e mantém pouco.
Lint e validação estática
Seção intitulada “Lint e validação estática”terraform fmt -check -recursive # falha se alguém não formatouterraform init -backend=false # sem tocar no stateterraform validate # sintaxe e coerência de tipostflint --recursive # antipadrões e regras do providertrivy config . # configuração insegura conhecidaColoque isso em pre-commit e no CI. No pre-commit dá feedback em segundos; no CI garante que ninguém pulou o hook.
Teste de plano
Seção intitulada “Teste de plano”O terraform test nativo executa o plano em memória e afirma sobre o resultado — rápido o
bastante para rodar em todo pull request e suficiente para a maioria dos erros de módulo.
run "bucket_privado_por_padrao" { command = plan variables { nome = "teste", ambiente = "dev" }
assert { condition = aws_s3_bucket_public_access_block.este.block_public_acls error_message = "Bucket não pode permitir ACL pública por padrão." } assert { condition = aws_s3_bucket_server_side_encryption_configuration.este != null error_message = "Criptografia em repouso é obrigatória." }}
run "ambiente_invalido_falha" { command = plan variables { nome = "teste", ambiente = "producao" } # valor fora do enum expect_failures = [var.ambiente]}Teste o caminho de erro também: validação que ninguém exercita é validação que pode estar quebrada.
Política como código no plan
Seção intitulada “Política como código no plan”O que vale para toda a organização não deve ser copiado em cada módulo — vira política central aplicada sobre o JSON do plano:
terraform plan -out=plano.binterraform show -json plano.bin > plano.jsonconftest test plano.json --policy politicas/ # OPA/RegoRegras que valem o esforço: nada público sem exceção aprovada, criptografia obrigatória, tag de dono e centro de custo, retenção mínima de backup, proibição de recurso fora das regiões permitidas. Comece em modo aviso e só depois bloqueie — a mesma sequência da página de política como código.
E um teste específico de infraestrutura que salva o dia: falhe o pull request quando o plano destruir recurso com estado, exigindo aprovação explícita.
jq -e '[.resource_changes[] | select(.change.actions | index("delete")) | select(.type | test("db_instance|s3_bucket|efs_file_system"))] | length == 0' plano.jsonTestes de integração de verdade
Seção intitulada “Testes de integração de verdade”Alguma coisa só aparece na nuvem: limite de cota, comportamento real de IAM, tempo de provisionamento, incompatibilidade entre versões. Para isso, provisione de verdade em conta isolada e destrua no fim.
// Terratest — cria, verifica, destróifunc TestBucketPrivado(t *testing.T) { opts := &terraform.Options{TerraformDir: "../exemplos/basico"} defer terraform.Destroy(t, opts) // sempre com defer, mesmo se falhar terraform.InitAndApply(t, opts)
arn := terraform.Output(t, opts, "bucket_arn") assert.NotEmpty(t, arn) aws.AssertS3BucketPolicyDenyPublic(t, "sa-east-1", nomeDoBucket)}Regras para isso não virar um problema próprio: conta separada e descartável, nomes com
sufixo aleatório, limpeza agendada para o que escapar (o destroy vai falhar algum
dia), e execução por noite ou no merge — não em cada push, ou o feedback fica lento e caro.
Teste de integração cobre módulo compartilhado e caminhos críticos. Não tente cobrir toda a infraestrutura assim: o custo cresce mais rápido que o valor.
Estimativa de custo no pull request
Seção intitulada “Estimativa de custo no pull request”Infraestrutura tem uma dimensão que aplicação não tem: cada linha aprovada é uma fatura mensal. Traga o número para a revisão.
infracost breakdown --path plano.jsoninfracost diff --path plano.json --format github-commentCombine com uma política simples: acima de um limite mensal, exige aprovação de mais uma pessoa. Isso transforma discussão de fim de mês em decisão consciente no momento certo.
Um pipeline de IaC completo
Seção intitulada “Um pipeline de IaC completo”PR: fmt → validate → tflint → trivy config → terraform test → plan → conftest → infracost → comentário com o plano no PRMerge: plan salvo → aprovação → apply do plano aprovado → testes de integraçãoDiário: plan -detailed-exitcode em todos os ambientes → alerta de driftO item diário costuma ser o mais esquecido e é o que mantém o código honesto: sem detecção de drift, o repositório vira ficção em alguns meses.
Próximo passo: você fechou a trilha de IaC. Leve as decisões para o provedor em Nuvem e veja o custo no PR em Custo no pull request.