Introducción

Los proveedores son el puente entre Terraform y el mundo exterior. Sin providers, Terraform es solo un motor de ejecución sin acceso a ningún servicio. Con ellos, puede gestionar recursos en AWS, Azure, Google Cloud, Kubernetes, GitHub, Cloudflare, bases de datos, y cientos de servicios más. Entender cómo funcionan, cómo se configuran y cómo se versionan es esencial para cualquier profesional de Terraform.


¿Qué son los providers en Terraform?

Un provider es un plugin que Terraform descarga y usa para comunicarse con una API específica. Cada provider:

  1. Expone tipos de recursos (como aws_instance, azurerm_resource_group).
  2. Expone data sources (como aws_ami, azurerm_image).
  3. Implementa las llamadas a la API del servicio correspondiente.
  4. Gestiona la autenticación con el servicio.
Tu código HCL
      |
      v
  Terraform Core
      |
      v
  Provider Plugin  <-->  API del servicio (AWS, Azure, GCP...)
      |
      v
  Infraestructura real

¿Cómo obtiene Terraform los providers?

Terraform descarga los providers automáticamente al ejecutar terraform init. Los busca en el Terraform Registry (registry.terraform.io) o en fuentes privadas si se configuran.

# Al ejecutar init, Terraform muestra los providers que descarga
terraform init

# Salida típica:
# Initializing provider plugins...
# - Finding hashicorp/aws versions matching "~> 5.0"...
# - Installing hashicorp/aws v5.31.0...
# - Installed hashicorp/aws v5.31.0 (signed by HashiCorp)

El Terraform Registry: cómo encontrar providers

El Terraform Registry (registry.terraform.io) es el repositorio público oficial de providers y módulos. Es el primer lugar donde buscar cuando necesitas interactuar con un nuevo servicio.

Estructura de un provider en el registry

Los providers se identifican por una dirección en formato:

<HOSTNAME>/<NAMESPACE>/<TIPO>
  • HOSTNAME: por defecto registry.terraform.io (se puede omitir)
  • NAMESPACE: organización que publica el provider (ej: hashicorp, aws)
  • TIPO: nombre del provider (ej: aws, azurerm, google)

Ejemplos:

  • hashicorp/aws = registry.terraform.io/hashicorp/aws
  • hashicorp/azurerm = registry.terraform.io/hashicorp/azurerm
  • hashicorp/google = registry.terraform.io/hashicorp/google

Categorías de providers

Categoría Descripción Ejemplos
Official Mantenidos por HashiCorp aws, azurerm, google, kubernetes
Partner Mantenidos por empresas verificadas datadog, newrelic, cloudflare
Community Mantenidos por la comunidad Miles de providers especializados

Configuración básica de un provider

Sintaxis mínima

# La forma más simple: Terraform usa los valores por defecto del provider
provider "aws" {
  region = "us-east-1"
}

El bloque terraform con required_providers

Siempre es buena práctica declarar los providers requeridos en el bloque terraform. Esto garantiza que Terraform descargue la versión correcta y falla con un mensaje claro si no puede.

# Bloque terraform: configuración del propio Terraform
terraform {
  # Versión mínima de Terraform requerida
  required_version = ">= 1.0"

  # Providers requeridos con sus versiones
  required_providers {
    # Provider de AWS
    aws = {
      source  = "hashicorp/aws"  # Dirección en el registry
      version = "~> 5.0"        # Restricción de versión
    }

    # Provider de Azure
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.0, < 4.0"
    }

    # Provider para archivos locales (muy útil para pruebas)
    local = {
      source  = "hashicorp/local"
      version = "~> 2.4"
    }
  }
}

# Configuración del provider AWS
provider "aws" {
  region = "us-east-1"

  # Tags que se añaden a TODOS los recursos creados por este provider
  default_tags {
    tags = {
      Gestionado = "terraform"
      Equipo     = "devops"
    }
  }
}

Operadores de versión: restricciones de versión

Las restricciones de versión permiten controlar qué versiones de un provider se pueden usar. Esto es crucial para la estabilidad: una actualización del provider podría cambiar el comportamiento.

Tabla de operadores

Operador Significado Ejemplo Versiones que acepta
= Exactamente esta versión = 5.0.0 Solo 5.0.0
!= No esta versión != 5.1.0 Cualquiera excepto 5.1.0
> Mayor que > 5.0 5.1, 5.2, 6.0...
>= Mayor o igual que >= 5.0 5.0, 5.1, 6.0...
< Menor que < 6.0 5.9, 5.0... pero no 6.0
<= Menor o igual que <= 5.9 5.9 y anteriores
~> Solo patch/minor (más usado) ~> 5.31 5.31.x (no 5.32)
~> Solo minor (versión mayor fija) ~> 5.0 5.x.y (no 6.x)

Casos de uso de cada operador

terraform {
  required_providers {
    # ~> 5.31: permite 5.31.0, 5.31.1, 5.31.2 pero NO 5.32.0
    # Bueno para bloquear a una versión minor específica en producción
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.31"
    }

    # ~> 5.0: permite 5.0, 5.1, 5.2... pero NO 6.0
    # Bueno para permitir actualizaciones minor pero no major
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }

    # >= 2.0, < 3.0: equivalente a ~> 2.0 pero más explícito
    google = {
      source  = "hashicorp/google"
      version = ">= 5.0, < 6.0"
    }

    # = 2.4.1: versión exacta (máximo control, mínima flexibilidad)
    # Útil cuando una versión específica tiene una corrección crítica
    local = {
      source  = "hashicorp/local"
      version = "= 2.4.1"
    }
  }
}

El archivo .terraform.lock.hcl

Cuando ejecutas terraform init, Terraform crea un archivo de lock que registra las versiones exactas descargadas:

# Archivo: .terraform.lock.hcl (generado automáticamente, NO editarlo)
# Asegura que todos los miembros del equipo usen las mismas versiones

provider "registry.terraform.io/hashicorp/aws" {
  version     = "5.31.0"  # Versión exacta instalada
  constraints = "~> 5.0"  # Restricción que especificamos nosotros
  hashes = [
    "h1:abc123...",  # Hash de verificación de integridad
  ]
}
# Actualizar las versiones respetando las restricciones
terraform init -upgrade

# Ver qué providers están instalados
terraform providers

Providers más populares

Provider Fuente Uso principal
AWS hashicorp/aws Amazon Web Services (EC2, S3, RDS, Lambda...)
Azure hashicorp/azurerm Microsoft Azure (VMs, Storage, AKS...)
Google Cloud hashicorp/google Google Cloud Platform (GCE, GCS, GKE...)
Kubernetes hashicorp/kubernetes Gestión de recursos de Kubernetes
Helm hashicorp/helm Despliegue de charts de Helm en Kubernetes
GitHub integrations/github Repositorios, teams, webhooks de GitHub
Cloudflare cloudflare/cloudflare DNS, CDN, certificados de Cloudflare
Datadog datadog/datadog Monitoreo y alertas con Datadog
Vault hashicorp/vault Gestión de secretos con HashiCorp Vault
Local hashicorp/local Archivos locales (ideal para pruebas)
Random hashicorp/random Generación de valores aleatorios únicos
TLS hashicorp/tls Generación de certificados y claves TLS
Null hashicorp/null Recursos vacíos para triggers y provisioners

Configurar múltiples instancias del mismo provider (alias)

A veces necesitas usar el mismo provider pero con diferentes configuraciones: por ejemplo, desplegar recursos en dos regiones de AWS distintas o en dos cuentas diferentes. Para esto usamos alias.

Alias de region

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

# Provider principal: región us-east-1 (Virginia)
# Al no tener alias, este es el provider "por defecto"
provider "aws" {
  region = "us-east-1"
}

# Provider secundario: región eu-west-1 (Irlanda)
# Le asignamos un alias para referenciarlo explícitamente
provider "aws" {
  alias  = "europa"   # Nombre del alias
  region = "eu-west-1"
}

# Provider terciario: región ap-southeast-1 (Singapur)
provider "aws" {
  alias  = "asia"
  region = "ap-southeast-1"
}

# --- Uso de los providers con alias ---

# Sin especificar provider: usa el provider por defecto (us-east-1)
resource "aws_s3_bucket" "bucket_usa" {
  bucket = "mi-bucket-usa"
}

# Con provider = aws.europa: usa el provider con alias "europa"
resource "aws_s3_bucket" "bucket_europa" {
  bucket = "mi-bucket-europa"

  provider = aws.europa  # Usamos el alias
}

# Con provider = aws.asia
resource "aws_s3_bucket" "bucket_asia" {
  bucket = "mi-bucket-asia"

  provider = aws.asia
}

Alias de cuenta (multi-account)

# Provider para la cuenta de producción
provider "aws" {
  region = "us-east-1"
  # Asumimos un rol en la cuenta de producción
  assume_role {
    role_arn = "arn:aws:iam::111122223333:role/TerraformRole"
  }
}

# Provider para la cuenta de desarrollo (alias)
provider "aws" {
  alias  = "desarrollo"
  region = "us-east-1"
  assume_role {
    role_arn = "arn:aws:iam::444455556666:role/TerraformRole"
  }
}

# VPC en la cuenta de producción (provider por defecto)
resource "aws_vpc" "produccion" {
  cidr_block = "10.0.0.0/16"
  tags = { Name = "vpc-produccion" }
}

# VPC en la cuenta de desarrollo (alias)
resource "aws_vpc" "desarrollo" {
  provider   = aws.desarrollo  # Especificamos el alias
  cidr_block = "10.1.0.0/16"
  tags = { Name = "vpc-desarrollo" }
}

Provider heredado en módulos (inheritance)

Cuando usas módulos, los providers del módulo raíz se heredan automáticamente por los módulos hijos. Sin embargo, cuando usas alias, debes ser explícito.

Herencia automática (comportamiento por defecto)

# =============================================
# Archivo: main.tf (módulo raíz)
# =============================================

provider "aws" {
  region = "us-east-1"
}

# El módulo hereda automáticamente el provider "aws" de arriba
module "red" {
  source = "./modules/red"
  # No necesitamos especificar el provider: se hereda automáticamente
}
# =============================================
# Archivo: modules/red/main.tf (módulo hijo)
# =============================================

# El módulo usa "aws" sin configurarlo: hereda del módulo raíz
resource "aws_vpc" "principal" {
  cidr_block = "10.0.0.0/16"
}

Pasar providers con alias a módulos

Cuando necesitas que un módulo use un provider con alias, debes pasarlo explícitamente:

# =============================================
# Archivo: main.tf (módulo raíz)
# =============================================

provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "europa"
  region = "eu-west-1"
}

# Pasamos los providers explícitamente al módulo
module "infraestructura_europa" {
  source = "./modules/infraestructura"

  # Mapeamos qué provider de este módulo raíz
  # corresponde a qué alias dentro del módulo hijo
  providers = {
    aws = aws.europa  # En el módulo hijo, "aws" será nuestro "aws.europa"
  }
}
# =============================================
# Archivo: modules/infraestructura/main.tf
# =============================================

# Declaramos qué providers necesita este módulo
terraform {
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}

# Usamos "aws" normalmente; el módulo raíz decide qué region/cuenta usar
resource "aws_vpc" "principal" {
  cidr_block = "10.0.0.0/16"
}

Autenticación con providers

Cada provider tiene sus propios mecanismos de autenticación. Aquí los más comunes:

Autenticación con AWS

# Método 1: Variables de entorno (RECOMENDADO para CI/CD)
# export AWS_ACCESS_KEY_ID="AKIAIOSFODNN7EXAMPLE"
# export AWS_SECRET_ACCESS_KEY="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
# export AWS_DEFAULT_REGION="us-east-1"

provider "aws" {
  # No necesita credenciales en el código: las lee de las variables de entorno
  region = "us-east-1"
}

# Método 2: Perfil de AWS CLI (para desarrollo local)
# Configurado en ~/.aws/credentials y ~/.aws/config
provider "aws" {
  region  = "us-east-1"
  profile = "mi-perfil-produccion"  # Nombre del perfil en ~/.aws/credentials
}

# Método 3: Variables directamente en el código (NO RECOMENDADO)
# Nunca subas credenciales al repositorio de código
provider "aws" {
  region     = "us-east-1"
  access_key = "AKIAIOSFODNN7EXAMPLE"        # PELIGROSO: no hagas esto
  secret_key = "wJalrXUtnFEMI/K7MDENG/..."  # PELIGROSO: no hagas esto
}

# Método 4: Asumir un rol IAM (recomendado para multi-cuenta y CI/CD)
provider "aws" {
  region = "us-east-1"

  assume_role {
    # ARN del rol que Terraform asumirá
    role_arn     = "arn:aws:iam::123456789012:role/TerraformDeploymentRole"
    # Identificador de sesión (aparece en los logs de CloudTrail)
    session_name = "terraform-deployment"
  }
}

Autenticación con Azure

# Método 1: Azure CLI (para desarrollo local)
# Ejecutar previamente: az login
provider "azurerm" {
  features {}  # Requerido aunque esté vacío en azurerm
  # Terraform usa automáticamente las credenciales de az login
}

# Método 2: Service Principal con client_secret (para CI/CD)
# Variables de entorno:
# ARM_CLIENT_ID       = "client-id-del-service-principal"
# ARM_CLIENT_SECRET   = "secreto-del-service-principal"
# ARM_SUBSCRIPTION_ID = "id-de-la-suscripcion"
# ARM_TENANT_ID       = "id-del-tenant"

provider "azurerm" {
  features {}
  # Lee automáticamente ARM_* de las variables de entorno
}

# Método 3: Managed Identity (para recursos en Azure como VMs o AKS)
provider "azurerm" {
  features {}
  use_msi = true  # Usar Managed Service Identity
}

Autenticación con Google Cloud

# Método 1: Application Default Credentials
# Ejecutar previamente: gcloud auth application-default login

provider "google" {
  project = "mi-proyecto-gcp"
  region  = "us-central1"
  # Lee automáticamente las credenciales de gcloud
}

# Método 2: Archivo de Service Account
provider "google" {
  project     = "mi-proyecto-gcp"
  region      = "us-central1"
  credentials = file("~/.gcp/service-account.json")  # Ruta al archivo JSON
}

# Método 3: Variable de entorno (recomendado para CI/CD)
# export GOOGLE_APPLICATION_CREDENTIALS="/ruta/al/service-account.json"

provider "google" {
  project = "mi-proyecto-gcp"
  region  = "us-central1"
  # Lee GOOGLE_APPLICATION_CREDENTIALS de las variables de entorno
}

Ejemplos de código completos

Ejemplo 1: Provider AWS con el provider "hashicorp/local"

El provider local permite crear archivos en el sistema de archivos local. Es muy útil para generar archivos de configuración o para pruebas sin necesitar acceso a la nube.

terraform {
  required_providers {
    # Provider de AWS para recursos en la nube
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }

    # Provider local para crear archivos en el sistema de archivos
    local = {
      source  = "hashicorp/local"
      version = "~> 2.4"
    }

    # Provider random para generar valores únicos
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }
}

# Configuración del provider AWS
provider "aws" {
  region = "us-east-1"

  # default_tags aplica estas etiquetas a TODOS los recursos AWS
  default_tags {
    tags = {
      Gestionado = "terraform"
      Proyecto   = "ejemplo-providers"
    }
  }
}

# El provider "local" y "random" no necesitan configuración adicional

# --- Usando el provider random ---
# Generamos un sufijo único para el nombre del bucket S3
resource "random_string" "sufijo" {
  length  = 8      # 8 caracteres de longitud
  special = false  # Sin caracteres especiales
  upper   = false  # Solo minúsculas (los nombres de S3 deben ser en minúsculas)
}

# --- Usando el provider AWS ---
resource "aws_s3_bucket" "mi_bucket" {
  # Usamos el sufijo aleatorio para garantizar un nombre único globalmente
  bucket = "mi-bucket-ejemplo-${random_string.sufijo.result}"
}

# --- Usando el provider local ---
# Creamos un archivo local con la información del bucket
resource "local_file" "info_bucket" {
  # El contenido del archivo (en formato JSON para ser parseable)
  content = jsonencode({
    nombre_bucket = aws_s3_bucket.mi_bucket.id
    arn_bucket    = aws_s3_bucket.mi_bucket.arn
    region        = "us-east-1"
    sufijo        = random_string.sufijo.result
  })

  # Ruta donde se creará el archivo
  filename = "${path.module}/bucket-info.json"
  # path.module es la ruta del directorio donde está este archivo .tf
}

output "nombre_bucket" {
  value = aws_s3_bucket.mi_bucket.id
}

output "ruta_archivo_info" {
  value = local_file.info_bucket.filename
}

Ejemplo 2: Configuración completa multi-provider

# =============================================
# Configuración para desplegar en AWS y Azure
# simultáneamente desde el mismo código Terraform
# =============================================

terraform {
  required_version = ">= 1.5"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }
}

# =============================================
# PROVIDER: AWS (us-east-1 - región principal)
# =============================================
provider "aws" {
  region = var.region_aws
}

# Provider AWS secundario para failover en otra región
provider "aws" {
  alias  = "dr"           # DR = Disaster Recovery
  region = "us-west-2"   # Costa oeste como región de respaldo
}

# =============================================
# PROVIDER: Azure
# =============================================
provider "azurerm" {
  features {
    # Configuración específica de azurerm para key vaults
    key_vault {
      purge_soft_delete_on_destroy = false
      recover_soft_deleted_key_vaults = true
    }
  }
  subscription_id = var.subscription_azure
}

# =============================================
# VARIABLES
# =============================================
variable "region_aws" {
  type    = string
  default = "us-east-1"
}

variable "subscription_azure" {
  type        = string
  description = "ID de la suscripción de Azure"
  default     = ""  # Se pasa como variable de entorno ARM_SUBSCRIPTION_ID
}

variable "nombre_proyecto" {
  type    = string
  default = "proyecto-multicloud"
}

# =============================================
# RECURSOS EN AWS
# =============================================

# Grupo de seguridad en AWS
resource "aws_security_group" "web" {
  # Sin provider especificado: usa el provider por defecto (us-east-1)
  name        = "${var.nombre_proyecto}-sg-web"
  description = "Security group para servidores web"

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

# Bucket S3 en la región de DR (con alias)
resource "aws_s3_bucket" "backups_dr" {
  provider = aws.dr  # Usamos el provider de la región de DR

  bucket = "${var.nombre_proyecto}-backups-dr-${random_string.id.result}"
}

resource "random_string" "id" {
  length  = 6
  special = false
  upper   = false
}

# =============================================
# RECURSOS EN AZURE
# =============================================

# Grupo de recursos en Azure (equivalente a un namespace)
resource "azurerm_resource_group" "principal" {
  name     = "rg-${var.nombre_proyecto}"
  location = "East US"   # Equivalente a us-east-1 en AWS

  tags = {
    Proyecto   = var.nombre_proyecto
    Gestionado = "terraform"
  }
}

# Cuenta de storage en Azure
resource "azurerm_storage_account" "datos" {
  name                     = replace("${var.nombre_proyecto}data${random_string.id.result}", "-", "")
  resource_group_name      = azurerm_resource_group.principal.name
  location                 = azurerm_resource_group.principal.location
  account_tier             = "Standard"
  account_replication_type = "GRS"  # Geo-Redundant Storage

  tags = azurerm_resource_group.principal.tags
}

# =============================================
# OUTPUTS
# =============================================

output "sg_aws_id" {
  description = "ID del security group en AWS"
  value       = aws_security_group.web.id
}

output "bucket_dr_aws" {
  description = "Nombre del bucket de DR en AWS us-west-2"
  value       = aws_s3_bucket.backups_dr.id
}

output "rg_azure_nombre" {
  description = "Nombre del resource group en Azure"
  value       = azurerm_resource_group.principal.name
}

output "storage_azure_nombre" {
  description = "Nombre de la cuenta de storage en Azure"
  value       = azurerm_storage_account.datos.name
}

Ejercicio práctico

Enunciado

Crea una configuración Terraform que:

  1. Configure tres providers: aws (región us-east-1), aws con alias secundario (región us-west-2), y el provider local.
  2. Declare las versiones en required_providers.
  3. Use el provider random para generar un ID único de 6 caracteres.
  4. Cree un bucket S3 en us-east-1 y otro en us-west-2 (réplica) usando los dos providers de AWS.
  5. Use el provider local para crear un archivo infraestructura.json con los nombres y ARNs de los buckets.
  6. Defina outputs para todos los recursos creados.

Solución detallada

# =============================================
# Archivo: terraform.tf
# Configuración de Terraform y providers
# =============================================

terraform {
  # Versión mínima de Terraform requerida
  required_version = ">= 1.5.0"

  required_providers {
    # Provider de AWS: para los buckets S3
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"  # Cualquier versión 5.x
    }

    # Provider local: para crear el archivo JSON
    local = {
      source  = "hashicorp/local"
      version = "~> 2.4"  # Cualquier versión 2.x >= 2.4
    }

    # Provider random: para generar el ID único
    random = {
      source  = "hashicorp/random"
      version = "~> 3.5"
    }
  }
}

# =============================================
# Archivo: providers.tf
# Configuración de las instancias de providers
# =============================================

# Provider AWS principal: región us-east-1 (Virginia del Norte)
# Este es el provider "por defecto" para todos los recursos sin provider=
provider "aws" {
  region = "us-east-1"

  # Etiquetas que se añaden automáticamente a todos los recursos
  default_tags {
    tags = {
      Gestionado = "terraform"
      Region     = "us-east-1"
    }
  }
}

# Provider AWS secundario: región us-west-2 (Oregón)
# Este provider se usa para la réplica del bucket
provider "aws" {
  alias  = "secundario"  # Nombre del alias para referenciar este provider
  region = "us-west-2"

  default_tags {
    tags = {
      Gestionado = "terraform"
      Region     = "us-west-2"
      Rol        = "replica"
    }
  }
}

# El provider "local" y "random" no necesitan bloque provider

# =============================================
# Archivo: main.tf
# Recursos principales
# =============================================

# Generamos un ID único para los nombres de los buckets
# Los nombres de S3 son globalmente únicos, necesitamos evitar colisiones
resource "random_string" "id_unico" {
  length  = 6      # 6 caracteres es suficiente para unicidad práctica
  special = false  # Sin caracteres especiales (no permitidos en nombres S3)
  upper   = false  # Solo minúsculas (S3 es sensible a mayúsculas en nombres)
  numeric = true   # Incluir números para más combinaciones posibles
}

# Valores locales para los nombres de los buckets
locals {
  # Nombre base compartido por ambos buckets
  nombre_base = "mi-empresa-datos-${random_string.id_unico.result}"

  # Nombre del bucket primario (sin sufijo adicional)
  nombre_bucket_primario   = "${local.nombre_base}-este"

  # Nombre del bucket de réplica (con sufijo "-oeste" para diferenciarlo)
  nombre_bucket_secundario = "${local.nombre_base}-oeste"
}

# --- Bucket S3 PRIMARIO en us-east-1 ---
resource "aws_s3_bucket" "primario" {
  # Sin provider= explícito: usa el provider por defecto (us-east-1)
  bucket = local.nombre_bucket_primario

  tags = {
    Nombre = "Bucket Primario"
    Rol    = "primario"
  }
}

# Habilitamos el versionado en el bucket primario
resource "aws_s3_bucket_versioning" "primario" {
  bucket = aws_s3_bucket.primario.id

  versioning_configuration {
    status = "Enabled"  # Necesario para que funcione la replicación
  }
}

# --- Bucket S3 SECUNDARIO en us-west-2 (réplica) ---
resource "aws_s3_bucket" "secundario" {
  # provider= explícito: usamos el alias "secundario" (us-west-2)
  provider = aws.secundario

  bucket = local.nombre_bucket_secundario

  tags = {
    Nombre  = "Bucket Réplica"
    Rol     = "replica"
    Replica = local.nombre_bucket_primario  # Referencia al bucket origen
  }
}

# Habilitamos versionado también en el bucket de réplica (requerido por AWS)
resource "aws_s3_bucket_versioning" "secundario" {
  provider = aws.secundario
  bucket   = aws_s3_bucket.secundario.id

  versioning_configuration {
    status = "Enabled"
  }
}

# --- Archivo local con la información de la infraestructura ---
resource "local_file" "infraestructura" {
  # jsonencode convierte un objeto HCL a formato JSON
  content = jsonencode({
    # Metadata de la infraestructura
    metadata = {
      generado_por = "Terraform"
      fecha        = "Generado durante terraform apply"
      id_unico     = random_string.id_unico.result
    }

    # Información del bucket primario
    bucket_primario = {
      nombre = aws_s3_bucket.primario.id
      arn    = aws_s3_bucket.primario.arn
      region = aws_s3_bucket.primario.region
    }

    # Información del bucket secundario (réplica)
    bucket_secundario = {
      nombre = aws_s3_bucket.secundario.id
      arn    = aws_s3_bucket.secundario.arn
      region = aws_s3_bucket.secundario.region
    }
  })

  # Ruta donde se creará el archivo
  # path.module = directorio donde está este archivo main.tf
  filename = "${path.module}/infraestructura.json"

  # Permisos del archivo (octal): 0644 = lectura/escritura para dueño, lectura para otros
  file_permission = "0644"
}

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

output "id_unico" {
  description = "ID único generado para esta infraestructura"
  value       = random_string.id_unico.result
}

output "bucket_primario_nombre" {
  description = "Nombre del bucket S3 primario (us-east-1)"
  value       = aws_s3_bucket.primario.id
}

output "bucket_primario_arn" {
  description = "ARN del bucket S3 primario"
  value       = aws_s3_bucket.primario.arn
}

output "bucket_secundario_nombre" {
  description = "Nombre del bucket S3 secundario - réplica (us-west-2)"
  value       = aws_s3_bucket.secundario.id
}

output "bucket_secundario_arn" {
  description = "ARN del bucket S3 secundario"
  value       = aws_s3_bucket.secundario.arn
}

output "archivo_json_ruta" {
  description = "Ruta del archivo JSON con la información de la infraestructura"
  value       = local_file.infraestructura.filename
}

output "resumen_completo" {
  description = "Resumen de toda la infraestructura creada"
  value = {
    id_unico   = random_string.id_unico.result
    us_east_1  = aws_s3_bucket.primario.id
    us_west_2  = aws_s3_bucket.secundario.id
    archivo    = local_file.infraestructura.filename
  }
}

Para ejecutar el ejercicio:

# Paso 1: Inicializar (descarga los providers)
terraform init

# Verás:
# - Installing hashicorp/aws v5.x.x
# - Installing hashicorp/local v2.x.x
# - Installing hashicorp/random v3.x.x

# Paso 2: Ver el plan
terraform plan

# El plan mostrará:
# + random_string.id_unico                  (generar ID único)
# + aws_s3_bucket.primario                  (crear bucket us-east-1)
# + aws_s3_bucket.secundario               (crear bucket us-west-2)
# + aws_s3_bucket_versioning.primario      (habilitar versionado primario)
# + aws_s3_bucket_versioning.secundario    (habilitar versionado secundario)
# + local_file.infraestructura             (crear archivo JSON)

# Paso 3: Aplicar
terraform apply

# Paso 4: Ver los outputs
terraform output
terraform output resumen_completo

# Paso 5: Ver el archivo creado
cat infraestructura.json

# Paso 6: Ver los providers instalados
terraform providers

# Paso 7 (cleanup): Destruir si solo fue una prueba
terraform destroy

Errores comunes con providers

Error 1: Olvidar ejecutar terraform init después de añadir un provider

# ERROR: Provider no encontrado
# Error: Required plugins are not installed

# SOLUCIÓN: ejecutar init después de cualquier cambio en required_providers
terraform init

Error 2: Conflicto de versiones

# INCORRECTO: restricciones incompatibles
required_providers {
  aws = {
    source  = "hashicorp/aws"
    version = ">= 5.0, <= 4.0"  # Error: no puede ser >= 5.0 Y <= 4.0
  }
}

Error 3: Usar un recurso sin el provider configurado

# INCORRECTO: azurerm_resource_group requiere el provider "azurerm"
# pero no está configurado ni en required_providers
resource "azurerm_resource_group" "mi_rg" {
  name     = "mi-grupo"
  location = "East US"
  # Error: Provider "azurerm" no configurado
}

# CORRECTO: declarar el provider primero
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.0"
    }
  }
}

provider "azurerm" {
  features {}
}

Error 4: Credenciales de AWS no configuradas

# ERROR al ejecutar plan/apply sin credenciales:
# Error: No valid credential sources found
# Please see https://www.terraform.io/docs/...

# SOLUCIÓN: configurar credenciales de una de estas formas:

# Opción A: Variables de entorno
export AWS_ACCESS_KEY_ID="AKIA..."
export AWS_SECRET_ACCESS_KEY="..."
export AWS_DEFAULT_REGION="us-east-1"

# Opción B: AWS CLI
aws configure

# Opción C: AWS SSO
aws sso login --profile mi-perfil

Error 5: Dos providers sin alias cuando se necesitan dos

# INCORRECTO: dos bloques provider sin alias -> conflicto
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  region = "us-west-2"
  # Error: solo puede haber un provider sin alias por tipo
}

# CORRECTO: el segundo debe tener alias
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "oeste"
  region = "us-west-2"
}

Resumen del Módulo 2

Has completado el Módulo 2 del curso. Esto es lo que has aprendido a lo largo de los cuatro temas:

Lo aprendido en este módulo

02-01: Sintaxis HCL

  • Bloques, argumentos y expresiones son los componentes básicos de HCL.
  • HCL soporta tipos primitivos (string, number, bool) y de colección (list, set, map, object).
  • La interpolación ${}, expresiones condicionales y funciones built-in hacen el código dinámico.

02-02: Variables y Outputs

  • Las variables de entrada parametrizan las configuraciones y las hacen reutilizables.
  • terraform.tfvars organiza los valores por entorno; TF_VAR_* para CI/CD.
  • Los outputs exponen información de los recursos y conectan módulos entre sí.

02-03: Bloques Resource y Data

  • resource crea infraestructura; data lee información existente.
  • count y for_each multiplican recursos; lifecycle controla su comportamiento.
  • Las dependencias implícitas (referencias) son preferibles a depends_on.

02-04: Providers (este tema)

  • Los providers son plugins que conectan Terraform con las APIs de los servicios.
  • El bloque required_providers garantiza versiones correctas y reproducibles.
  • El operador ~> es el más usado para versioning: permite actualizaciones patch.
  • Los alias permiten usar múltiples configuraciones del mismo provider.
  • La autenticación debe hacerse mediante variables de entorno o roles IAM, nunca hardcodeando credenciales.

Próximos pasos

En el Módulo 3 estudiaremos la Gestión del Estado de Terraform: cómo Terraform rastrea los recursos que gestiona, dónde almacena esa información, cómo trabajar en equipo con estado remoto y cómo evitar conflictos con el state locking. El estado es probablemente el aspecto más crítico de Terraform en entornos de producción, y entenderlo bien te diferenciará como profesional.

© Copyright 2026. Todos los derechos reservados