Introducción

Las variables y los outputs son el mecanismo que hace que las configuraciones de Terraform sean reutilizables y parametrizables. En lugar de escribir valores fijos directamente en el código (lo que se conoce como "hardcoding"), las variables permiten que el mismo código funcione en diferentes entornos con distintas configuraciones. Los outputs, por su parte, exponen información sobre los recursos creados para que pueda ser consultada o utilizada por otros módulos.


Variables de entrada (Input Variables)

¿Qué son las variables de entrada?

Las variables de entrada son parámetros que puedes pasar a tu configuración de Terraform desde el exterior. Son similares a los argumentos de una función en programación.

Sintaxis básica

variable "nombre_variable" {
  type        = tipo_de_dato     # (opcional) tipo esperado
  description = "Descripción"   # (recomendado) qué hace esta variable
  default     = valor_por_defecto # (opcional) valor si no se especifica
}

Ejemplo básico

# Declaración de una variable simple
variable "region_aws" {
  type        = string
  description = "La región de AWS donde se crearán los recursos"
  default     = "us-east-1"
}

# Uso de la variable en un bloque provider
provider "aws" {
  region = var.region_aws  # Accedemos con var.nombre_variable
}

Variables requeridas vs opcionales

Una variable sin valor por defecto es requerida: Terraform la pedirá si no se proporciona.

# Variable REQUERIDA: no tiene default, Terraform la pedirá obligatoriamente
variable "nombre_proyecto" {
  type        = string
  description = "Nombre del proyecto. Es obligatorio especificarlo."
  # Sin default -> el usuario DEBE proporcionarlo
}

# Variable OPCIONAL: tiene un default razonable
variable "entorno" {
  type        = string
  description = "Entorno de despliegue"
  default     = "desarrollo"  # Si no se especifica, se usa "desarrollo"
}

Tipos de variables con tabla comparativa

Tipo Sintaxis Ejemplo de valor Uso típico
string type = string "us-east-1" Nombres, IDs, regiones
number type = number 3 o 2.5 Cantidades, puertos, tamaños
bool type = bool true o false Flags de activación/desactivación
list(string) type = list(string) ["a", "b"] Listas de zonas, CIDRs, nombres
list(number) type = list(number) [80, 443, 8080] Listas de puertos
map(string) type = map(string) {k = "v"} Etiquetas (tags), configuraciones clave-valor
set(string) type = set(string) ["a", "b"] Como list pero sin duplicados ni orden
object({...}) type = object({a=string}) {a="valor"} Estructuras de configuración complejas
tuple([...]) type = tuple([string, number]) ["a", 1] Raramente usado directamente
any type = any Cualquiera Cuando no quieres restringir el tipo

Ejemplos completos de cada tipo

# STRING: nombre o identificador de texto
variable "nombre_vpc" {
  type        = string
  description = "Nombre de la VPC principal"
  default     = "vpc-produccion"
}

# NUMBER: cantidad numérica
variable "max_instancias" {
  type        = number
  description = "Número máximo de instancias en el autoscaling group"
  default     = 10
}

# BOOL: activar o desactivar funcionalidades
variable "habilitar_cifrado" {
  type        = bool
  description = "Si true, habilita el cifrado en reposo para los volúmenes"
  default     = true
}

# LIST(STRING): múltiples valores de texto
variable "zonas_disponibilidad" {
  type        = list(string)
  description = "Zonas de disponibilidad donde desplegar subnets"
  default     = ["us-east-1a", "us-east-1b", "us-east-1c"]
}

# LIST(NUMBER): múltiples valores numéricos
variable "puertos_entrada" {
  type        = list(number)
  description = "Puertos que se permitirán en el security group de entrada"
  default     = [80, 443, 8080]
}

# MAP(STRING): pares clave-valor de texto
variable "etiquetas" {
  type        = map(string)
  description = "Etiquetas (tags) que se aplicarán a todos los recursos"
  default = {
    Entorno  = "produccion"
    Equipo   = "infraestructura"
    Proyecto = "tienda-online"
  }
}

# OBJECT: estructura con múltiples campos tipados
variable "configuracion_rds" {
  type = object({
    clase_instancia    = string  # ej: "db.t3.medium"
    motor              = string  # ej: "mysql", "postgres"
    version_motor      = string  # ej: "8.0", "13.4"
    almacenamiento_gb  = number  # ej: 100
    multiaz            = bool    # true = alta disponibilidad
    backup_dias        = number  # días que se guardan los backups
  })
  description = "Configuración completa de la base de datos RDS"
  default = {
    clase_instancia   = "db.t3.micro"
    motor             = "mysql"
    version_motor     = "8.0"
    almacenamiento_gb = 20
    multiaz           = false
    backup_dias       = 7
  }
}

# Uso de las variables definidas arriba
resource "aws_db_instance" "principal" {
  # Accedemos a los campos del objeto con la notación de punto
  instance_class    = var.configuracion_rds.clase_instancia
  engine            = var.configuracion_rds.motor
  engine_version    = var.configuracion_rds.version_motor
  allocated_storage = var.configuracion_rds.almacenamiento_gb
  multi_az          = var.configuracion_rds.multiaz
  backup_retention_period = var.configuracion_rds.backup_dias

  storage_encrypted = var.habilitar_cifrado  # Usamos el bool directamente

  tags = var.etiquetas  # Pasamos el mapa directamente como tags
}

Valores por defecto y variables requeridas

Cuándo usar cada opción

# SIEMPRE ponle default cuando tenga un valor razonable para la mayoría de casos
variable "tipo_instancia" {
  type    = string
  default = "t3.micro"  # Un valor que funciona para desarrollo por defecto
}

# NO pongas default cuando el valor es específico del despliegue
variable "id_cuenta_aws" {
  type        = string
  description = "ID de la cuenta de AWS (12 dígitos). Obligatorio."
  # Sin default: cada equipo tiene su propia cuenta
}

# CUIDADO con defaults que parecen seguros pero no lo son
variable "cidr_acceso_ssh" {
  type        = string
  description = "CIDR desde donde se permite SSH"
  # Evita poner "0.0.0.0/0" como default aunque sea tentador
  # No default -> el usuario debe elegir conscientemente
}

Archivo terraform.tfvars y .auto.tfvars

Los archivos .tfvars permiten definir los valores de las variables sin pasarlos en la línea de comandos.

terraform.tfvars

# Archivo: terraform.tfvars
# Terraform lo carga automáticamente si existe en el directorio

nombre_proyecto    = "mi-tienda-online"
entorno            = "produccion"
region_aws         = "eu-west-1"
max_instancias     = 20
habilitar_cifrado  = true

zonas_disponibilidad = [
  "eu-west-1a",
  "eu-west-1b",
  "eu-west-1c"
]

etiquetas = {
  Entorno   = "produccion"
  Equipo    = "devops"
  Proyecto  = "tienda"
  CostCenter = "TI-001"
}

configuracion_rds = {
  clase_instancia   = "db.t3.large"
  motor             = "postgres"
  version_motor     = "14.5"
  almacenamiento_gb = 200
  multiaz           = true
  backup_dias       = 14
}

Múltiples archivos .tfvars para distintos entornos

Es una práctica común tener un archivo por entorno:

# Estructura de archivos recomendada
terraform/
├── main.tf
├── variables.tf
├── outputs.tf
├── terraform.tfvars          # Valores por defecto (desarrollo)
├── entornos/
│   ├── desarrollo.tfvars     # Valores para desarrollo
│   ├── staging.tfvars        # Valores para staging
│   └── produccion.tfvars     # Valores para producción
# Archivo: entornos/produccion.tfvars
entorno           = "produccion"
max_instancias    = 50
habilitar_cifrado = true

configuracion_rds = {
  clase_instancia   = "db.r5.xlarge"
  motor             = "postgres"
  version_motor     = "14.5"
  almacenamiento_gb = 500
  multiaz           = true
  backup_dias       = 30
}
# Aplicar con el archivo de producción específico
terraform apply -var-file="entornos/produccion.tfvars"

# Puedes combinar múltiples archivos (el último sobreescribe)
terraform apply \
  -var-file="terraform.tfvars" \
  -var-file="entornos/produccion.tfvars"

Archivos .auto.tfvars

Los archivos con extensión .auto.tfvars se cargan automáticamente en orden alfabético, igual que terraform.tfvars:

# Estos archivos se cargan AUTOMÁTICAMENTE sin necesidad de -var-file
produccion.auto.tfvars
secretos.auto.tfvars   # Útil para credenciales (¡no subas esto a git!)

Variables de entorno TF_VAR_*

Terraform también puede leer variables desde variables de entorno del sistema operativo. Son útiles para credenciales y secretos en pipelines de CI/CD.

# Las variables de entorno siguen el patrón TF_VAR_nombre_variable
# Todo en minúsculas

# Ejemplo: establecer la variable "nombre_proyecto"
export TF_VAR_nombre_proyecto="mi-proyecto-cicd"

# Ejemplo: establecer la variable "max_instancias"
export TF_VAR_max_instancias=5

# Ejemplo: para tipos complejos, usar sintaxis HCL entre comillas
export TF_VAR_etiquetas='{ Entorno = "produccion", Equipo = "devops" }'

# Luego ejecutar terraform normalmente
terraform plan
terraform apply

Prioridad de fuentes de variables

Terraform evalúa las variables en este orden (de menor a mayor prioridad):

Prioridad Fuente Descripción
1 (menor) Valores por defecto en variable {} El default en el código
2 terraform.tfvars Archivo de variables principal
3 *.auto.tfvars Archivos de variables automáticos (orden alfabético)
4 -var-file="archivo.tfvars" Archivo especificado en línea de comandos
5 -var "nombre=valor" Variable especificada en línea de comandos
6 (mayor) Variables de entorno TF_VAR_* Variables del sistema operativo

Validación de variables (validation block)

El bloque validation permite definir reglas personalizadas que los valores deben cumplir. Si la condición no se cumple, Terraform muestra el mensaje de error y detiene la ejecución.

variable "entorno" {
  type        = string
  description = "Entorno de despliegue"

  validation {
    # La condición debe ser verdadera para que la validación pase
    # contains() verifica si el valor está en la lista
    condition     = contains(["desarrollo", "staging", "produccion"], var.entorno)
    error_message = "El entorno debe ser uno de: desarrollo, staging, produccion."
  }
}

variable "puerto_aplicacion" {
  type        = number
  description = "Puerto donde escucha la aplicación"
  default     = 8080

  validation {
    # Los puertos válidos son del 1024 al 65535 (puertos no privilegiados)
    condition     = var.puerto_aplicacion >= 1024 && var.puerto_aplicacion <= 65535
    error_message = "El puerto debe estar entre 1024 y 65535."
  }
}

variable "nombre_bucket_s3" {
  type        = string
  description = "Nombre del bucket S3 (solo minúsculas, números y guiones)"

  validation {
    # regex() devuelve error si no coincide; can() captura ese error y devuelve false
    # El patrón regex verifica el formato correcto de nombre de bucket
    condition     = can(regex("^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$", var.nombre_bucket_s3))
    error_message = "El nombre del bucket debe tener entre 3 y 63 caracteres, solo minúsculas, números y guiones, y no puede empezar ni terminar con guión."
  }
}

variable "cidr_vpc" {
  type        = string
  description = "Bloque CIDR para la VPC"
  default     = "10.0.0.0/16"

  validation {
    # Verificamos que sea un CIDR IPv4 válido
    condition     = can(cidrnetmask(var.cidr_vpc))
    error_message = "El valor debe ser un bloque CIDR IPv4 válido, por ejemplo 10.0.0.0/16."
  }
}

variable "num_replicas" {
  type        = number
  description = "Número de réplicas de la aplicación"
  default     = 2

  # Podemos tener múltiples bloques validation
  validation {
    condition     = var.num_replicas >= 1
    error_message = "Debe haber al menos 1 réplica."
  }

  validation {
    condition     = var.num_replicas <= 100
    error_message = "El número máximo de réplicas es 100."
  }
}

Variables de salida (Outputs)

¿Qué son los outputs?

Los outputs son valores que Terraform muestra al finalizar apply y que pueden ser consultados posteriormente con terraform output. También son el mecanismo principal para pasar información entre módulos.

Sintaxis básica

output "nombre_output" {
  description = "Descripción de qué valor se exporta"
  value       = expresión_del_valor
}

Ejemplos de outputs

# Output simple: ID de un recurso
output "id_vpc" {
  description = "El ID único de la VPC creada"
  value       = aws_vpc.principal.id
  # Terraform mostrará esto al final del apply:
  # id_vpc = "vpc-0abc123def456"
}

# Output de múltiples atributos usando un objeto
output "info_servidor_web" {
  description = "Información completa del servidor web"
  value = {
    id         = aws_instance.web.id
    ip_publica = aws_instance.web.public_ip
    ip_privada = aws_instance.web.private_ip
    dns_publico = aws_instance.web.public_dns
  }
}

# Output de una lista (cuando se usa count)
output "ips_servidores" {
  description = "IPs públicas de todos los servidores creados"
  # [*] obtiene el atributo de todos los elementos del count
  value = aws_instance.web[*].public_ip
  # Ejemplo resultado: ["1.2.3.4", "5.6.7.8", "9.10.11.12"]
}

# Output de un mapa para cuando se usa for_each
output "ids_subnets" {
  description = "Mapa de nombre de subnet a su ID"
  # for_each crea un mapa, accedemos a todos los values con [*]
  value = { for k, v in aws_subnet.publica : k => v.id }
  # Ejemplo resultado: { "subnet-a" = "subnet-001", "subnet-b" = "subnet-002" }
}

# Output calculado con una expresión
output "url_aplicacion" {
  description = "URL completa para acceder a la aplicación"
  # Construimos la URL a partir del DNS público del balanceador
  value = "https://${aws_lb.principal.dns_name}"
}

# Output con condición
output "ip_publica_o_mensaje" {
  description = "IP pública si existe, o mensaje indicando que es privado"
  value = aws_instance.app.public_ip != "" ? aws_instance.app.public_ip : "Sin IP pública (instancia privada)"
}

Outputs sensibles (sensitive = true)

Cuando un output contiene información confidencial (contraseñas, tokens, claves), marca el output como sensitive = true. Terraform ocultará el valor en los logs y en la salida de apply.

# Output normal: se muestra en la consola
output "endpoint_base_datos" {
  description = "Endpoint de conexión a la base de datos"
  value       = aws_db_instance.principal.endpoint
}

# Output sensible: el valor se oculta en la consola
output "password_base_datos" {
  description = "Contraseña generada para la base de datos"
  value       = aws_db_instance.principal.password
  sensitive   = true  # Terraform mostrará: password_base_datos = <sensitive>
}

output "cadena_conexion_db" {
  description = "String de conexión completo (incluye credenciales)"
  value = "postgresql://${aws_db_instance.principal.username}:${aws_db_instance.principal.password}@${aws_db_instance.principal.endpoint}/${aws_db_instance.principal.db_name}"
  sensitive = true  # Oculto porque contiene la contraseña
}
# Para ver el valor de un output sensible debes hacerlo explícitamente
terraform output password_base_datos

# O ver todos los outputs incluyendo sensibles en formato JSON
terraform output -json

Importante: sensitive = true solo oculta el valor en la salida de Terraform. El valor sigue almacenado en texto plano en el archivo de estado (terraform.tfstate). Protege tu archivo de estado.


Referenciar outputs entre módulos

Los outputs son fundamentales cuando usas módulos. Un módulo expone sus valores con outputs, y el módulo padre los referencia como module.nombre_modulo.nombre_output.

# =============================================
# Archivo: modules/red/main.tf
# Módulo que crea la infraestructura de red
# =============================================

resource "aws_vpc" "principal" {
  cidr_block = var.cidr_vpc
}

resource "aws_subnet" "publica" {
  count             = length(var.zonas)
  vpc_id            = aws_vpc.principal.id
  cidr_block        = cidrsubnet(var.cidr_vpc, 8, count.index)
  availability_zone = var.zonas[count.index]
}

# Outputs del módulo de red
output "id_vpc" {
  description = "ID de la VPC creada"
  value       = aws_vpc.principal.id
}

output "ids_subnets_publicas" {
  description = "Lista de IDs de las subnets públicas"
  value       = aws_subnet.publica[*].id
}
# =============================================
# Archivo: main.tf (módulo raíz)
# Usa el módulo de red y referencia sus outputs
# =============================================

# Llamamos al módulo de red
module "red" {
  source   = "./modules/red"
  cidr_vpc = "10.0.0.0/16"
  zonas    = ["us-east-1a", "us-east-1b"]
}

# Módulo de servidores que necesita la red
module "servidores" {
  source = "./modules/servidores"

  # Referenciamos los outputs del módulo "red"
  # Sintaxis: module.nombre_modulo.nombre_output
  vpc_id     = module.red.id_vpc              # ID de la VPC
  subnet_ids = module.red.ids_subnets_publicas  # Lista de subnets
}

# Output del módulo raíz
output "id_vpc_final" {
  description = "VPC creada por el módulo de red"
  value       = module.red.id_vpc
}

Ejemplo completo: configuración con variables y outputs

# =============================================
# Archivo: variables.tf
# =============================================

variable "proyecto" {
  type        = string
  description = "Nombre del proyecto"

  validation {
    condition     = length(var.proyecto) >= 3 && length(var.proyecto) <= 20
    error_message = "El nombre del proyecto debe tener entre 3 y 20 caracteres."
  }
}

variable "entorno" {
  type        = string
  description = "Entorno de despliegue: desarrollo, staging, produccion"
  default     = "desarrollo"

  validation {
    condition     = contains(["desarrollo", "staging", "produccion"], var.entorno)
    error_message = "Entorno no válido. Usa: desarrollo, staging o produccion."
  }
}

variable "region" {
  type        = string
  description = "Región de AWS"
  default     = "us-east-1"
}

variable "tipo_instancia_por_entorno" {
  type = map(string)
  description = "Tipo de instancia EC2 para cada entorno"
  default = {
    desarrollo  = "t2.micro"   # El más barato para pruebas
    staging     = "t2.small"   # Un poco más para pruebas de carga
    produccion  = "t3.medium"  # Con suficiente capacidad
  }
}

variable "crear_ip_elastica" {
  type        = bool
  description = "Si se debe crear una IP elástica (estática) para el servidor"
  default     = false
}

# =============================================
# Archivo: main.tf
# =============================================

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

provider "aws" {
  region = var.region
}

locals {
  # Etiquetas comunes para todos los recursos
  etiquetas_comunes = {
    Proyecto   = var.proyecto
    Entorno    = var.entorno
    Gestionado = "terraform"
  }

  # Nombre base para recursos
  nombre_base = "${var.proyecto}-${var.entorno}"

  # Tipo de instancia según entorno (buscamos en el mapa)
  tipo_instancia = lookup(var.tipo_instancia_por_entorno, var.entorno, "t2.micro")
}

resource "aws_instance" "app" {
  ami           = "ami-0c55b159cbfafe1f0"
  instance_type = local.tipo_instancia

  tags = merge(local.etiquetas_comunes, {
    Name = "${local.nombre_base}-servidor"
  })
}

# Solo creamos la IP elástica si la variable lo indica
resource "aws_eip" "app" {
  count    = var.crear_ip_elastica ? 1 : 0
  instance = aws_instance.app.id
}

# =============================================
# Archivo: outputs.tf
# =============================================

output "id_instancia" {
  description = "ID de la instancia EC2 creada"
  value       = aws_instance.app.id
}

output "ip_privada" {
  description = "IP privada de la instancia"
  value       = aws_instance.app.private_ip
}

output "ip_publica" {
  description = "IP pública (solo disponible si se creó IP elástica)"
  # Si se creó la IP elástica (count = 1), mostramos su IP
  # Si no (count = 0), mostramos la IP pública temporal de la instancia
  value = var.crear_ip_elastica ? aws_eip.app[0].public_ip : aws_instance.app.public_ip
}

output "tipo_instancia_usado" {
  description = "Tipo de instancia EC2 que se está usando"
  value       = local.tipo_instancia
}

output "info_completa" {
  description = "Resumen completo de la infraestructura desplegada"
  value = {
    proyecto    = var.proyecto
    entorno     = var.entorno
    region      = var.region
    instancia   = aws_instance.app.id
    ip_privada  = aws_instance.app.private_ip
  }
}

Ejercicio práctico

Enunciado

Crea una configuración completa de Terraform con las siguientes características:

  1. Define las variables:

    • nombre_app: string requerido (sin default), validar que tenga entre 5 y 30 caracteres.
    • replicas: number, default 2, validar que sea entre 1 y 10.
    • configuracion: object con campos cpu (number), memoria_mb (number) y puerto (number).
    • etiquetas_extra: map(string), default vacío.
  2. Define un bloque locals que:

    • Calcule el nombre completo como "app-{nombre_app}".
    • Cree un mapa de etiquetas combinando {Aplicacion = nombre_app, Replicas = replicas} con las etiquetas_extra.
    • Calcule si los recursos son "de alto rendimiento" si cpu >= 4.
  3. Define los siguientes outputs:

    • nombre_completo: el nombre completo calculado.
    • etiquetas_finales: el mapa de etiquetas combinado.
    • es_alto_rendimiento: booleano que indica si es de alto rendimiento.
    • resumen: objeto con toda la información resumida.

Solución detallada

# =============================================
# Archivo: variables.tf - Solución del ejercicio
# =============================================

# 1a. Variable nombre_app: string requerido con validación
variable "nombre_app" {
  type        = string
  description = "Nombre de la aplicación. Entre 5 y 30 caracteres."
  # Sin default: es obligatorio que el usuario lo proporcione

  validation {
    # length() devuelve el número de caracteres del string
    # Verificamos que esté en el rango permitido
    condition     = length(var.nombre_app) >= 5 && length(var.nombre_app) <= 30
    error_message = "El nombre_app debe tener entre 5 y 30 caracteres. Actualmente tiene ${length(var.nombre_app)}."
  }
}

# 1b. Variable replicas: number con default y validación de rango
variable "replicas" {
  type        = number
  description = "Número de réplicas de la aplicación (1-10)"
  default     = 2  # Por defecto, 2 réplicas para alta disponibilidad básica

  validation {
    # Verificamos que sea al menos 1 y no más de 10
    condition     = var.replicas >= 1 && var.replicas <= 10
    error_message = "El número de réplicas debe estar entre 1 y 10. Valor recibido: ${var.replicas}."
  }
}

# 1c. Variable configuracion: object con campos de recursos
variable "configuracion" {
  type = object({
    cpu       = number  # Número de CPUs (ej: 2, 4, 8)
    memoria_mb = number  # Memoria en MB (ej: 512, 1024, 4096)
    puerto    = number  # Puerto de la aplicación (ej: 8080, 3000)
  })
  description = "Configuración de recursos de la aplicación"
  default = {
    cpu       = 2      # 2 CPUs por defecto
    memoria_mb = 1024  # 1GB de RAM por defecto
    puerto    = 8080   # Puerto estándar de aplicaciones web
  }
}

# 1d. Variable etiquetas_extra: map(string) con default vacío
variable "etiquetas_extra" {
  type        = map(string)
  description = "Etiquetas adicionales para los recursos (opcional)"
  default     = {}  # Por defecto no hay etiquetas extra
}

# =============================================
# Archivo: locals.tf - Valores calculados
# =============================================

locals {
  # 2a. Nombre completo de la aplicación
  # Interpolamos la variable con el prefijo "app-"
  nombre_completo = "app-${var.nombre_app}"
  # Ejemplo: si nombre_app = "mi-tienda", resultado = "app-mi-tienda"

  # 2b. Mapa de etiquetas combinado
  # merge() une dos mapas; si hay claves iguales, el segundo tiene prioridad
  etiquetas_finales = merge(
    {
      # Etiquetas base calculadas a partir de las variables
      Aplicacion = var.nombre_app           # Nombre de la app
      Replicas   = tostring(var.replicas)   # Convertimos number a string para el tag
      # Los tags de AWS son siempre strings, por eso convertimos con tostring()
    },
    var.etiquetas_extra  # Las etiquetas extra del usuario (pueden sobreescribir)
  )

  # 2c. Determinar si es de alto rendimiento
  # Alto rendimiento = 4 o más CPUs
  es_alto_rendimiento = var.configuracion.cpu >= 4
  # Ejemplo: cpu=2 -> false, cpu=4 -> true, cpu=8 -> true
}

# =============================================
# Archivo: outputs.tf - Valores exportados
# =============================================

# 3a. Nombre completo de la aplicación
output "nombre_completo" {
  description = "Nombre completo de la aplicación con prefijo 'app-'"
  value       = local.nombre_completo
}

# 3b. Mapa de etiquetas combinado
output "etiquetas_finales" {
  description = "Mapa completo de etiquetas que se aplicarían a los recursos"
  value       = local.etiquetas_finales
}

# 3c. Indicador de alto rendimiento
output "es_alto_rendimiento" {
  description = "true si la configuración tiene 4 o más CPUs"
  value       = local.es_alto_rendimiento
}

# 3d. Resumen completo
output "resumen" {
  description = "Resumen completo de la configuración de la aplicación"
  value = {
    nombre          = local.nombre_completo       # Nombre completo
    replicas        = var.replicas                 # Número de réplicas
    cpu             = var.configuracion.cpu        # CPUs configuradas
    memoria_mb      = var.configuracion.memoria_mb # Memoria configurada
    puerto          = var.configuracion.puerto     # Puerto de la app
    alto_rendimiento = local.es_alto_rendimiento  # ¿Es de alto rendimiento?
    num_etiquetas   = length(local.etiquetas_finales) # Cuántas etiquetas tiene
  }
}

Para probar la solución, crea un archivo terraform.tfvars:

# Archivo: terraform.tfvars para probar el ejercicio

nombre_app = "mi-tienda"  # 9 caracteres: válido (entre 5 y 30)
replicas   = 3             # Válido (entre 1 y 10)

configuracion = {
  cpu       = 4      # 4 CPUs -> es_alto_rendimiento = true
  memoria_mb = 4096  # 4GB de RAM
  puerto    = 3000   # Puerto 3000
}

etiquetas_extra = {
  Equipo       = "backend"
  CostCenter   = "TI-002"
}
# Ejecutar para ver los outputs sin crear recursos
terraform init
terraform plan

# Ver solo los outputs después de apply
terraform output
terraform output resumen
terraform output -json  # Formato JSON completo

Errores comunes con variables y outputs

Error 1: Referenciar una variable que no existe

# INCORRECTO: la variable "region_aws" no está declarada
provider "aws" {
  region = var.region_aws  # Error: variable no declarada
}

# CORRECTO: primero declara la variable
variable "region_aws" {
  type    = string
  default = "us-east-1"
}

Error 2: Tipo incorrecto en el valor por defecto

# INCORRECTO: el tipo es list(string) pero el default es un string
variable "zonas" {
  type    = list(string)
  default = "us-east-1a"  # Error: debe ser una lista

}

# CORRECTO:
variable "zonas" {
  type    = list(string)
  default = ["us-east-1a"]  # Lista con un elemento
}

Error 3: Output referenciando recurso que no existe aún

# INCORRECTO: si aws_instance.web no está definido en ningún .tf
output "ip_servidor" {
  value = aws_instance.web.public_ip  # Error: recurso no encontrado
}

Error 4: Variable sensible no marcada como tal

# INCORRECTO: la contraseña aparecerá en los logs de CI/CD
output "db_password" {
  value = aws_db_instance.main.password
  # Sin sensitive = true -> expone la contraseña en los logs
}

# CORRECTO:
output "db_password" {
  value     = aws_db_instance.main.password
  sensitive = true
}

Error 5: Confundir var. con local.

# INCORRECTO: los locales se acceden con local. (singular), no locals.
value = locals.nombre_completo  # Error

# CORRECTO:
value = local.nombre_completo   # local. (singular)

Resumen

En este módulo has aprendido todo lo necesario sobre variables y outputs en Terraform:

  • Las variables de entrada parametrizan las configuraciones y las hacen reutilizables. Se declaran con variable {} y se usan con var.nombre.
  • Los tipos de variables van desde simples (string, number, bool) hasta complejos (list, map, object), cada uno adecuado para diferentes necesidades.
  • Las variables requeridas no tienen default y obligan al usuario a especificar un valor; las opcionales tienen un valor por defecto razonable.
  • Los archivos terraform.tfvars y .auto.tfvars permiten organizar los valores por entorno sin modificar el código principal.
  • Las variables de entorno TF_VAR_* son ideales para pipelines de CI/CD y secretos.
  • El bloque validation añade validaciones personalizadas con mensajes de error claros.
  • Los outputs exportan información de los recursos y son el mecanismo para pasar datos entre módulos.
  • Los outputs sensibles ocultan valores confidenciales en la consola pero el archivo de estado sigue siendo texto plano.

Preparación para el siguiente módulo

En el próximo módulo estudiaremos los Bloques de Recursos y Datos en profundidad, incluyendo meta-argumentos como count, for_each y lifecycle. Verás que las variables que has aprendido aquí se usan constantemente para controlar cuántos recursos se crean y cómo se comportan.

© Copyright 2026. Todos los derechos reservados