Um recurso precisa de um número variável de blocos aninhados idênticos (por exemplo, regras de entrada) impulsionados por uma lista/mapa.
→Use um bloco `dynamic` cujo `for_each` itera a coleção; referencie cada elemento através do iterador de bloco (nome padrão = rótulo do bloco) dentro de `content {}`.
Por quê: Blocos dinâmicos geram blocos aninhados repetidos sem copiar e colar; o iterador mantém cada bloco gerado vinculado ao seu elemento de origem.
Referência↗
Crie um recurso por entrada em um mapa de objetos, com chave estável para que a reordenação nunca force a substituição.
→Defina `for_each = var.objects` (um mapa). Use `each.key` para a chave estável e `each.value.<attr>` para os campos. Evite `count` aqui — mudanças de índice causam instabilidade.
Por quê: As chaves do mapa são identidades estáveis no estado; os índices da lista são posicionais e mudam quando elementos são adicionados/removidos.
Decida entre count e for_each para múltiplas instâncias.
→Use `for_each` quando as instâncias têm identidades distintas (um conjunto/mapa); use `count` apenas para N cópias idênticas e insensíveis à ordem. Prefira `for_each` para qualquer coisa que possa crescer/diminuir.
Por quê: `for_each` endereça por chave (recurso["chave"]); `count` endereça por índice (recurso[0]), que se reorganiza em inserções/exclusões.
Uma variável de entrada é um objeto onde alguns atributos são opcionais e precisam de valores padrão.
→Tipifique-o como `object({ name = string, size = optional(number, 10) })`. `optional(type, default)` fornece o valor padrão quando o chamador omite o atributo.
Por quê: `optional()` com um valor padrão mantém os chamadores concisos enquanto garante um valor concreto a jusante — sem tratamento de nulo em todos os lugares.
Referência↗
Valide uma suposição sobre um recurso antes de aplicar, ou garanta um resultado depois.
→Use `lifecycle { precondition { ... } }` para afirmar entradas antes da criação/atualização, e `postcondition` para afirmar saídas depois. Ambos aceitam `condition` + `error_message`.
Por quê: Condições personalizadas falham rapidamente com uma mensagem clara, em vez de produzir uma aplicação quebrada ou um erro a jusante confuso.
Referência↗
Um recurso deve ser recriado sempre que outro recurso ou atributo muda.
→Adicione `lifecycle { replace_triggered_by = [aws_x.y.id] }`. Quando o valor referenciado muda, o Terraform força a substituição deste recurso.
Por quê: Expressa uma dependência de substituição de forma declarativa, evitando `-replace` manual em cada mudança relacionada.
A substituição de um recurso causa tempo de inatividade porque o antigo é destruído antes que o novo exista.
→Defina `lifecycle { create_before_destroy = true }` para que o Terraform provisione primeiro a substituição e depois destrua o antigo. Garanta nomes únicos/sem conflitos difíceis.
Por quê: Substituição com tempo de inatividade zero; mas fique atento a colisões de nomes e limites de cota enquanto ambos existem brevemente.
Rejeite valores de entrada inválidos precocemente (por exemplo, um ambiente que não seja dev/stage/prod).
→Adicione um bloco `validation { condition = contains(["dev","stage","prod"], var.env), error_message = "..." }` à variável.
Por quê: Detecta entradas inválidas no momento do planejamento com uma mensagem legível, em vez de falhar profundamente em uma chamada de provedor.
Referencie um valor que pode não existir sem travar o plano.
→Use `try(local.maybe.value, "default")` para fallback em erros, ou `can(expr)` para obter um booleano se uma expressão for bem-sucedida.
Por quê: Manipulação elegante de dados opcionais/variáveis; evita "Error: Unsupported attribute" em chaves ausentes.
Transforme uma lista em um mapa, ou filtre/formate uma coleção para um argumento de recurso.
→Use uma expressão `for`: `{ for u in var.users : u.name => u.role if u.active }` (mapa) ou `[for x in list : upper(x)]` (lista).
Por quê: Expressões `for` são a maneira idiomática de remodelar dados; a cláusula `if` filtra, a forma `k => v` constrói mapas.
Uma variável ou saída contém um segredo que não deve ser impresso na saída de plan/apply.
→Marque a variável como `sensitive = true` (e as saídas também). O Terraform a oculta na saída da CLI, embora ainda seja armazenada no estado.
Por quê: Previne a divulgação acidental em logs/saída de CI; o estado em si ainda deve ser protegido (backend criptografado, controle de acesso).
Gerencie recursos em duas regiões/contas dentro de uma única configuração.
→Declare provedores apelidados (`provider "aws" { alias = "west" region = "us-west-2" }`) e defina `provider = aws.west` nos recursos ou passe para os módulos.
Por quê: Aliases permitem que uma configuração vise múltiplas instâncias de provedor; os módulos os recebem explicitamente via o argumento `providers`.
Uma dependência oculta (não expressa por meio de referências) causa problemas de ordenação.
→Adicione `depends_on = [aws_iam_role_policy.x]` para forçar a ordenação. Use com moderação — prefira dependências implícitas via referências de atributos.
Por quê: `depends_on` explícito lida com dependências que o grafo não pode inferir, mas o uso excessivo cria planos conservadores e mais lentos.
Renderize um arquivo de configuração/user-data a partir de um template com variáveis estruturadas.
→Use `templatefile("${path.module}/tpl.tftpl", { items = local.items })`; o template usa interpolação `%{ for }` / `${}`.
Por quê: `templatefile` mantém a renderização pura/no momento do plano (ao contrário do provedor de template obsoleto) e suporta loops/condicionais.
Crie uma lista plana de cada combinação (subnet, rule) para alimentar um único `for_each`.
→Use `setproduct(var.subnets, var.rules)` para o produto cartesiano, ou `flatten([for ...])` para colapsar listas aninhadas em uma.
Por quê: Estas funções transformam dados aninhados na coleção plana e com chave única que `for_each` requer.