mozak.tech Engenharia de IA Corporativa 7%

Parte I — Operações e Infraestrutura Potencializadas por IA

1.2 — Geração e Validação de Código de Infraestrutura com IA (IaC + IA)

Objetivo da Aula

Ao concluir esta aula, você será capaz de:

Construir um agente LLM que gera código Terraform HCL a partir de linguagem natural

Implementar validação de sintaxe Terraform como tool do agente

Implementar verificação de policy OPA como tool do agente

Conectar 4 tools (gerar, validar, checar policy, estimar custo) em loop de agente

Completar os 2 TODOs do starter: validar_terraform e verificar_policy_opa

Por que isso importa

Terraform HCL tem uma curva de aprendizado alta: sintaxe de blocos, referências entre recursos (aws_instance.web.id), providers, módulos, state. Um copilot que gera HCL correto e válido a partir de linguagem natural reduz drasticamente o tempo de um dev para provisionar infra.

O problema real: LLMs geram HCL plausível mas frequentemente incorreto — chaves desbalanceadas, argumentos inexistentes na versão do provider, recursos sem tags de conformidade. A solução é um pipeline de validação automática: gerar → validar → checar policy → estimar custo, tudo antes de qualquer terraform apply.

Conceitos Fundamentais

Estrutura do Agente IaC Copilot

O agente tem 4 tools em sequência mandatória:

1. gerar_terraform    → LLM gera o HCL

2. validar_terraform  → sintaxe OK?

3. verificar_policy_opa → segurança OK?

4. estimar_custo      → quanto vai custar?

O system prompt do agente instrui explicitamente essa ordem:

system='Você é um IaC Copilot. Sempre: 1) Gere o Terraform, 2) Valide, 3) Verifique policies, 4) Estime custo.'

O LLM entende a instrução e executa as 4 tools automaticamente — sem lógica condicional no orquestrador.

TODO 1: validar_terraform(codigo_hcl)

A versão completa valida sintaxe básica sem precisar do binário terraform:

def validar_terraform(codigo_hcl: str) -> str:

    erros = []

    

    # Verificação de blocos obrigatórios

    if 'resource' not in codigo_hcl and 'module' not in codigo_hcl:

        erros.append('Nenhum bloco resource ou module encontrado')

    

    # Verificação de chaves balanceadas

    if codigo_hcl.count('{') != codigo_hcl.count('}'):

        erros.append('Chaves desbalanceadas')

    

    # Verificação de aspas (HCL usa aspas duplas)

    linhas = codigo_hcl.split('\n')

    for i, linha in enumerate(linhas, 1):

        if linha.count('"') % 2 != 0:

            erros.append(f'Linha {i}: aspas não fechadas')

    

    if erros:

        return f'INVÁLIDO: {"; ".join(erros)}'

    return 'VÁLIDO: sintaxe correta'

Para validação real (quando terraform está instalado):

import subprocess, tempfile, os



def validar_terraform_real(codigo_hcl: str) -> str:

    with tempfile.TemporaryDirectory() as tmpdir:

        with open(os.path.join(tmpdir, 'main.tf'), 'w') as f:

            f.write(codigo_hcl)

        resultado = subprocess.run(

            ['terraform', 'validate', '-json'],

            cwd=tmpdir,

            capture_output=True,

            text=True,

        )

        data = json.loads(resultado.stdout)

        if data['valid']:

            return 'VÁLIDO: terraform validate OK'

        return f"INVÁLIDO: {data['error_count']} erros"

TODO 2: verificar_policy_opa(codigo_hcl, politicas)

OPA (Open Policy Agent) verifica políticas de segurança. O mock analisa o HCL:

def verificar_policy_opa(codigo_hcl: str, politicas: list[str] = None) -> str:

    politicas = politicas or ['no-public-s3', 'require-tags', 'no-root-iam']

    resultados = []

    

    for politica in politicas:

        if politica == 'no-public-s3':

            if 'acl = "public-read"' in codigo_hcl or 'acl = "public-read-write"' in codigo_hcl:

                resultados.append(f'FALHA: {politica} — bucket S3 público detectado')

            else:

                resultados.append(f'OK: {politica}')

        

        elif politica == 'require-tags':

            if 'tags' not in codigo_hcl:

                resultados.append(f'AVISO: {politica} — recurso sem tags obrigatórias')

            else:

                resultados.append(f'OK: {politica}')

        

        elif politica == 'no-root-iam':

            if '"arn:aws:iam::*:root"' in codigo_hcl or '/* All */' in codigo_hcl:

                resultados.append(f'FALHA: {politica} — permissão root detectada')

            else:

                resultados.append(f'OK: {politica}')

        

        else:

            resultados.append(f'OK: {politica} (política não implementada no mock)')

    

    aprovado = all('FALHA' not in r for r in resultados)

    prefixo = '✓ APROVADO' if aprovado else '✗ REPROVADO'

    return f'{prefixo}\n' + '\n'.join(resultados)

Para OPA real (com servidor OPA rodando):

import requests



def verificar_policy_opa_real(codigo_hcl: str, politica: str) -> dict:

    payload = {'input': {'resource': codigo_hcl}}

    response = requests.post(

        f'http://localhost:8181/v1/data/terraform/{politica}/deny',

        json=payload,

    )

    return response.json()

Aprofundamento Técnico

Por que o Agente Funciona sem Lógica Condicional

O orquestrador do lab é simples:

for i in range(6):

    response = client.messages.create(tools=TOOLS, messages=mensagens, ...)

    if response.stop_reason == 'end_turn':

        return resultado

    # processa tool_use, adiciona a mensagens

O LLM decide a sequência. Quando você instrui “sempre faça 1, 2, 3, 4”, ele segue. Mas e se a validação falhar? O LLM irá pedir ao agente para regenerar — automaticamente, sem você programar isso.

Trace de exemplo:

[AGENTE] → gerar_terraform(...)

         ← resource "aws_instance" com chave desbalanceada

[AGENTE] → validar_terraform(...)  

         ← INVÁLIDO: chaves desbalanceadas

[AGENTE → pensa: "preciso gerar de novo corretamente"]

[AGENTE] → gerar_terraform(...)    ← segunda tentativa

         ← resource "aws_instance" { ... } (correto)

[AGENTE] → validar_terraform(...)

         ← VÁLIDO

[AGENTE] → verificar_policy_opa(...)

[AGENTE] → estimar_custo(...)

[AGENTE] end_turn: "Terraform gerado, válido e aprovado. Custo: $8.50/mês"

estimar_custo: Parsing de HCL sem Parser

A tool de custo analisa HCL como string. Isso funciona para casos simples mas falha para HCL com variáveis (var.instance_type) ou módulos. O insight: para fins de estimativa rápida, string matching é suficiente para os tipos mais comuns.

custos = {'aws_instance': {'t3.micro': 8.5, 't3.medium': 34}, 'aws_s3': 2.5}

for recurso, tipos in custos.items():

    if recurso in codigo_hcl:

        # detectou o tipo de recurso → verifica o tipo de instância

        for tipo, custo in tipos.items():

            if tipo in codigo_hcl:

                total += custo

Para produção, use Infracost CLI: infracost breakdown --path=. gera estimativa precisa considerando regions, preços spot, reserved instances.

Exemplos Anotados

Exemplo 1: Fluxo Completo do Agente

import anthropic

import json

import os



client = anthropic.Anthropic()



# As 4 tools do pipeline

TOOLS = [

    {'name': 'gerar_terraform', ...},

    {'name': 'validar_terraform', ...},

    {'name': 'verificar_policy_opa', ...},

    {'name': 'estimar_custo', ...},

]



def iac_copilot(requisito: str) -> str:

    mensagens = [{'role': 'user', 'content': f'Gere, valide e estime custo para: {requisito}'}]

    

    for i in range(6):

        response = client.messages.create(

            model='claude-haiku-4-5-20251001',

            max_tokens=1500,

            tools=TOOLS,

            # instrução explícita de sequência

            system='Você é um IaC Copilot. Sempre: 1) Gere o Terraform, 2) Valide, 3) Verifique policies, 4) Estime custo.',

            messages=mensagens,

        )

        

        if response.stop_reason == 'end_turn':

            return next((b.text for b in response.content if hasattr(b, 'text')), 'Concluído.')

        

        tool_blocks = [b for b in response.content if b.type == 'tool_use']

        if not tool_blocks:

            break

        

        # adiciona resposta completa (TextBlock + ToolUseBlock) ao histórico

        mensagens.append({'role': 'assistant', 'content': response.content})

        

        results = []

        for block in tool_blocks:

            resultado = executar_tool(block.name, block.input)

            results.append({

                'type': 'tool_result',

                'tool_use_id': block.id,  # usa block.id, não block.name

                'content': resultado,

            })

        mensagens.append({'role': 'user', 'content': results})

    

    return 'Loop finalizado'

Exemplo 2: HCL com Policy Violation

# Este HCL vai falhar em 'no-public-s3'

hcl_problematico = '''

resource "aws_s3_bucket" "dados" {

  bucket = "empresa-dados-publicos"

  acl    = "public-read"  # ← policy violation!

}

'''



resultado = verificar_policy_opa(hcl_problematico)

# REPROVADO

# FALHA: no-public-s3 — bucket S3 público detectado

# AVISO: require-tags — recurso sem tags obrigatórias

# OK: no-root-iam

Padrões e Armadilhas

Padrões

Padrão 1: Sequência mandatória via system prompt

system='Sempre siga esta sequência: 1) gerar_terraform 2) validar_terraform 3) verificar_policy_opa 4) estimar_custo. Nunca pule etapas.'

O LLM respeita a instrução. Para maior garantia, adicione lógica no orquestrador que rejeita end_turn sem todas as 4 tools terem sido chamadas.

Padrão 2: HCL como string no tool_result

{'type': 'tool_result', 'tool_use_id': block.id, 'content': hcl_gerado}

# O LLM recebe o HCL como texto e pode passá-lo para a próxima tool

Padrão 3: Dry-run antes de apply

kubectl_apply(manifest)  # sempre dry-run no lab

terraform_plan(config)   # equivalente Terraform: plan antes de apply

Armadilhas

⚠️ Armadilha 1: block.name vs block.id no tool_result

# ERRADO — vai retornar erro da API

results.append({'type': 'tool_result', 'tool_use_id': block.name, ...})



# CORRETO — sempre use o ID único do bloco

results.append({'type': 'tool_result', 'tool_use_id': block.id, ...})

⚠️ Armadilha 2: Não incluir response.content no histórico

# ERRADO — perde os TextBlocks antes dos ToolUseBlocks

mensagens.append({'role': 'assistant', 'content': tool_blocks})



# CORRETO — inclui tudo

mensagens.append({'role': 'assistant', 'content': response.content})

⚠️ Armadilha 3: HCL com variáveis quebra o parser de custo

resource "aws_instance" "web" {

  instance_type = var.instance_type  # string matching não detecta isso

}

O parser de custo do lab só funciona com valores literais. Para produção, use Infracost ou o plano Terraform em JSON (terraform show -json).

⚗ Laboratório prático — mozak.tech
Se não for realizar o laboratório, pule para o próximo capítulo.

Ponte para o Lab

2 TODOs no starter.py:

TODO 1 — validar_terraform(codigo_hcl): A implementação mínima já está: verifica resource/module e conta chaves. Expanda adicionando verificação de aspas não fechadas:

for i, linha in enumerate(codigo_hcl.split('\n'), 1):

    if linha.count('"') % 2 != 0:

        erros.append(f'Linha {i}: aspas não fechadas')

TODO 2 — verificar_policy_opa(codigo_hcl, politicas): Adicione mais políticas além das 3 do starter: - no-encrypted-storage: verifica se S3 tem server_side_encryption_configuration - require-backup: verifica se RDS tem backup_retention_period

Rode com python starter.py e observe o agente executar as 4 tools em sequência.

Agora você está pronto para o lab.