Terraform Associate
Deep Dive
Count y for_each crean múltiples instancias de un recurso. Las expresiones for y los condicionales permiten transformar datos. Las funciones built-in son herramientas esenciales para el trabajo diario y el examen.
Contenido
count crea N instancias del recurso, indexadas de 0 a N-1. Cada instancia es identificada por su índice numérico.
# Crear 3 instancias EC2
resource "aws_instance" "web" {
count = 3 # número entero
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t3.micro"
tags = {
Name = "web-${count.index}" # count.index: 0, 1, 2
}
}
# Dirección de cada instancia:
# aws_instance.web[0]
# aws_instance.web[1]
# aws_instance.web[2]
# Acceso en outputs o referencias:
output "web_ids" {
value = aws_instance.web[*].id # splat: lista de todos los IDs
}
# Usar variable para el conteo:
variable "instance_count" { default = 3 }
resource "aws_instance" "app" {
count = var.instance_count
# ...
}
# Creación condicional (count = 0 = no crear, count = 1 = crear):
resource "aws_eip" "nat" {
count = var.create_nat_gateway ? 1 : 0
domain = "vpc"
}for_each crea una instancia por cada elemento de un set o map. Cada instancia es identificada por su clave (string), no por un índice numérico.
# for_each con un SET de strings
resource "aws_iam_user" "team" {
for_each = toset(["alice", "bob", "carol"]) # convierte lista a set
name = each.key # each.key == el elemento del set
}
# Dirección de cada instancia:
# aws_iam_user.team["alice"]
# aws_iam_user.team["bob"]
# aws_iam_user.team["carol"]
# for_each con un MAP (clave → objeto)
variable "buckets" {
default = {
logs = { region = "us-east-1", versioning = true }
backups = { region = "us-west-2", versioning = false }
}
}
resource "aws_s3_bucket" "storage" {
for_each = var.buckets
bucket = "empresa-${each.key}" # each.key = "logs" o "backups"
}
resource "aws_s3_bucket_versioning" "storage" {
for_each = var.buckets
bucket = aws_s3_bucket.storage[each.key].id # referencia con la misma clave
versioning_configuration {
status = each.value.versioning ? "Enabled" : "Suspended"
}
}
# Splat no funciona con for_each — usar for expression:
output "bucket_ids" {
value = { for k, v in aws_s3_bucket.storage : k => v.id }
}| Característica | count | for_each |
|---|---|---|
| Tipo de valor aceptado | number | set(string) o map(any) |
| Identificador de instancia | Índice numérico (0, 1, 2…) | Clave string ("alice", "logs"…) |
| Dirección en state | recurso[0], recurso[1] | recurso["clave"] |
| Acceso al elemento | count.index (entero) | each.key (clave) + each.value (valor) |
| Qué pasa al borrar el 2° de 3 | ⚠️ Terraform renombra el 3° al 2° y DESTRUYE+RECREA | ✅ Solo destruye el elemento borrado, los demás no cambian |
| Cuándo usar | Recursos idénticos sin identidad propia (réplicas) | Recursos con identidad única (usuarios, buckets con nombre) |
| Recomendación | Casos simples o creación condicional (0 o 1) | Preferido para la mayoría de casos de múltiples recursos |
⚠️ El problema de count con listas cambiantes
Si tienes count = 3 con instancias [0]=alice, [1]=bob, [2]=carol y eliminas bob (elemento del medio), Terraform ve: [0]=alice, [1]=carol. Como el [1] cambió de "bob" a "carol", destruye y recrea el recurso [1]. Con for_each, cada recurso tiene una clave estable y eliminar "bob" solo destruye ese recurso sin afectar a los demás.
# Expresión for — genera listas o mapas
# [for <item> in <coleccion> : <expresion>]
# {for <key>, <val> in <mapa> : <nueva_key> => <nuevo_val>}
variable "users" {
default = [
{ name = "alice", admin = true },
{ name = "bob", admin = false },
]
}
# Lista de nombres
locals {
names = [for u in var.users : u.name]
# result: ["alice", "bob"]
# Solo los admins (with if condition)
admin_names = [for u in var.users : u.name if u.admin]
# result: ["alice"]
# Mapa nombre → admin
user_map = {for u in var.users : u.name => u.admin}
# result: {alice = true, bob = false}
}
# Expresión condicional (ternario)
# condition ? valor_si_true : valor_si_false
resource "aws_instance" "web" {
instance_type = var.environment == "prod" ? "t3.large" : "t3.micro"
}💡 For expressions vs for_each
Las expresiones for ([for x in list : x.name]) transforman colecciones en locales o valores — no crean recursos. El meta-argumento for_each crea múltiples instancias de un recurso. Son conceptos diferentes aunque usan palabras similares.
Terraform incluye decenas de funciones built-in. Estas son las más importantes para el examen, organizadas por categoría.
String
format(fmt, ...args)
Formatea un string tipo printf: format("Hello, %s!", "world")
join(sep, list)
Une una lista en string: join(",", ["a","b"]) → "a,b"
split(sep, str)
Divide string en lista: split(",", "a,b") → ["a","b"]
upper(str) / lower(str)
Convierte a mayúsculas/minúsculas
trimspace(str)
Elimina espacios y saltos de línea al inicio y final
replace(str, sub, new)
Reemplaza subcadena: replace("hello", "l", "r") → "herro"
Colección
length(list|map|str)
Número de elementos: length(["a","b"]) → 2
concat(list1, list2)
Combina listas: concat(["a"],["b"]) → ["a","b"]
flatten(list)
Aplana listas anidadas: flatten([["a"],["b","c"]]) → ["a","b","c"]
merge(map1, map2)
Combina mapas (map2 sobrescribe claves de map1)
lookup(map, key, def)
Busca clave en mapa con default: lookup({a=1}, "b", 0) → 0
toset(list)
Convierte lista a set (elimina duplicados, necesario para for_each)
keys(map) / values(map)
Lista de claves o valores de un mapa
contains(list, val)
True si la lista contiene el valor: contains(["a","b"],"a") → true
Codificación
jsonencode(value)
Convierte valor HCL a string JSON: jsonencode({a="b"}) → {"a":"b"}
jsondecode(str)
Parsea string JSON a tipo HCL
base64encode(str)
Codifica string en Base64
base64decode(str)
Decodifica string Base64
file(path)
Lee el contenido de un archivo como string
templatefile(path, vars)
Renderiza template .tftpl con variables
Numérico y otros
max(n1, n2, ...) / min(...)
Máximo o mínimo de los números dados
abs(n)
Valor absoluto
ceil(n) / floor(n)
Redondeo hacia arriba / hacia abajo
coalesce(v1, v2, ...)
Primer valor no nulo y no vacío de la lista
try(expr, fallback)
Evalúa expr; si falla, devuelve fallback (útil para atributos opcionales)
can(expr)
True si expr no genera error (para validación)
¿Entendiste este tema?
Pon a prueba lo que acabas de aprender
Un módulo gestiona 3 subnets con for_each usando las claves 'public-a', 'public-b' y 'private-a'. El equipo decide eliminar 'public-b' del mapa. ¿Qué ocurrirá en el siguiente terraform apply?