Terraform Variables e Locals: Configurazione Dinamica

Terraform variables and locals are fundamental components for creating flexible, reusable, and maintainable infrastructure as code. Understanding how to effectively use these features enables developers and DevOps professionals to build dynamic configurations that adapt to different environments and requirements while maintaining clean, organized code structures.

Understanding Terraform Variables

Variables in Terraform serve as parameters for your modules and configurations, allowing you to customize deployments without modifying the core infrastructure code. They act as input parameters that can be set at runtime or through configuration files, making your Terraform configurations more flexible and reusable across different environments.

Terraform supports several variable types including string, number, bool, list, map, set, object, and tuple. Each type serves specific use cases and provides different levels of structure and validation capabilities.

Variable Declaration Syntax

Variables are declared using the variable block with optional attributes that define their behavior and constraints:

variable "instance_type" {
  description = "EC2 instance type for the web server"
  type        = string
  default     = "t3.micro"
  
  validation {
    condition     = contains(["t3.micro", "t3.small", "t3.medium"], var.instance_type)
    error_message = "Instance type must be t3.micro, t3.small, or t3.medium."
  }
}

variable "environment" {
  description = "Deployment environment"
  type        = string
  
  validation {
    condition     = can(regex("^(dev|staging|prod)$", var.environment))
    error_message = "Environment must be dev, staging, or prod."
  }
}

variable "tags" {
  description = "Resource tags"
  type        = map(string)
  default     = {}
}

Complex Variable Types

Terraform variables excel when handling complex data structures that represent real-world infrastructure requirements:

variable "vpc_config" {
  description = "VPC configuration object"
  type = object({
    cidr_block           = string
    enable_dns_hostnames = bool
    enable_dns_support   = bool
    availability_zones   = list(string)
    public_subnets       = list(string)
    private_subnets      = list(string)
  })
  
  default = {
    cidr_block           = "10.0.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support   = true
    availability_zones   = ["us-west-2a", "us-west-2b"]
    public_subnets       = ["10.0.1.0/24", "10.0.2.0/24"]
    private_subnets      = ["10.0.3.0/24", "10.0.4.0/24"]
  }
}

variable "application_ports" {
  description = "List of application ports to expose"
  type        = list(number)
  default     = [80, 443, 8080]
}

Variable Input Methods

Terraform provides multiple ways to set variable values, each suited for different scenarios and security requirements. Understanding these methods helps you choose the appropriate approach for your deployment strategy.

Environment-Specific Configuration Files

Using terraform.tfvars files or environment-specific variable files allows you to maintain separate configurations for different deployment environments:

# dev.tfvars
environment = "dev"
instance_type = "t3.micro"

vpc_config = {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  availability_zones   = ["us-west-2a", "us-west-2b"]
  public_subnets       = ["10.0.1.0/24", "10.0.2.0/24"]
  private_subnets      = ["10.0.3.0/24", "10.0.4.0/24"]
}

tags = {
  Environment = "development"
  Project     = "web-application"
  Owner       = "development-team"
}
# prod.tfvars
environment = "prod"
instance_type = "t3.large"

vpc_config = {
  cidr_block           = "10.1.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true
  availability_zones   = ["us-west-2a", "us-west-2b", "us-west-2c"]
  public_subnets       = ["10.1.1.0/24", "10.1.2.0/24", "10.1.3.0/24"]
  private_subnets      = ["10.1.4.0/24", "10.1.5.0/24", "10.1.6.0/24"]
}

tags = {
  Environment = "production"
  Project     = "web-application"
  Owner       = "platform-team"
}

Environment Variables and Command Line

For sensitive data or CI/CD integration, environment variables and command-line arguments provide secure and automated ways to set variable values:

# Using environment variables
export TF_VAR_database_password="secure_password_123"
export TF_VAR_api_key="your_api_key_here"

# Command line variable assignment
terraform apply -var="environment=staging" -var="instance_count=3"

# Using variable files
terraform apply -var-file="staging.tfvars"

Local Values: Computed Configuration

Local values, defined using the locals block, are named values that you can use throughout your module. They are particularly useful for avoiding repetition, computing values based on other variables or resources, and creating more readable configurations.

Unlike variables, locals are computed within the module and cannot be overridden from outside. They serve as intermediate values that help organize and simplify complex expressions.

Basic Local Value Usage

locals {
  # Simple computed values
  region = data.aws_region.current.name
  
  # String interpolation and formatting
  name_prefix = "${var.environment}-${var.project_name}"
  
  # Conditional logic
  instance_count = var.environment == "prod" ? var.prod_instance_count : var.dev_instance_count
  
  # Complex computations
  availability_zones = slice(data.aws_availability_zones.available.names, 0, var.az_count)
  
  # Common tags that combine variable inputs
  common_tags = merge(var.tags, {
    Environment   = var.environment
    ManagedBy    = "terraform"
    DeployedAt   = timestamp()
    Region       = local.region
  })
}

Advanced Local Value Patterns

Local values become powerful when handling complex data transformations and conditional logic:

locals {
  # Environment-specific configurations
  environment_config = {
    dev = {
      instance_type = "t3.micro"
      min_size      = 1
      max_size      = 2
      desired_size  = 1
    }
    staging = {
      instance_type = "t3.small"
      min_size      = 2
      max_size      = 4
      desired_size  = 2
    }
    prod = {
      instance_type = "t3.large"
      min_size      = 3
      max_size      = 10
      desired_size  = 5
    }
  }
  
  # Select configuration based on environment
  current_config = local.environment_config[var.environment]
  
  # Subnet calculations
  subnet_configs = [
    for i, cidr in var.vpc_config.public_subnets : {
      cidr_block        = cidr
      availability_zone = local.availability_zones[i % length(local.availability_zones)]
      type             = "public"
      name             = "${local.name_prefix}-public-${i + 1}"
    }
  ]
  
  # Security group rules generation
  security_group_rules = flatten([
    for port in var.application_ports : [
      {
        type        = "ingress"
        from_port   = port
        to_port     = port
        protocol    = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
        description = "Allow inbound traffic on port ${port}"
      }
    ]
  ])
}

Combining Variables and Locals Effectively

The true power of Terraform configuration emerges when variables and locals work together to create dynamic, maintainable infrastructure code. Variables provide the input interface, while locals handle the computational logic and data transformation.

Practical Implementation Example

Here's a comprehensive example showing how variables and locals collaborate in a real-world scenario:

# Variables for input parameters
variable "environment" {
  description = "Deployment environment"
  type        = string
}

variable "project_name" {
  description = "Name of the project"
  type        = string
}

variable "application_config" {
  description = "Application configuration"
  type = object({
    ports           = list(number)
    health_check_path = string
    cpu_threshold     = number
    memory_threshold  = number
  })
}

variable "scaling_config" {
  description = "Auto-scaling configuration per environment"
  type = map(object({
    min_capacity = number
    max_capacity = number
    target_cpu   = number
  }))
}

# Locals for computed values and logic
locals {
  # Basic computed values
  full_name = "${var.project_name}-${var.environment}"
  region    = data.aws_region.current.name
  
  # Environment-specific scaling configuration
  scaling = var.scaling_config[var.environment]
  
  # Load balancer configuration
  lb_config = {
    name               = "${local.full_name}-alb"
    load_balancer_type = "application"
    subnets            = data.aws_subnets.public.ids
    security_groups    = [aws_security_group.alb.id]
  }
  
  # Target group configurations for each port
  target_groups = {
    for port in var.application_config.ports : "port-${port}" => {
      name     = "${local.full_name}-tg-${port}"
      port     = port
      protocol = "HTTP"
      health_check = {
        enabled             = true
        healthy_threshold   = 2
        unhealthy_threshold = 2
        timeout            = 5
        interval           = 30
        path               = var.application_config.health_check_path
        matcher            = "200"
      }
    }
  }
  
  # CloudWatch alarms configuration
  cloudwatch_alarms = {
    high_cpu = {
      alarm_name          = "${local.full_name}-high-cpu"
      comparison_operator = "GreaterThanThreshold"
      evaluation_periods  = "2"
      metric_name         = "CPUUtilization"
      namespace          = "AWS/ECS"
      period             = "120"
      statistic          = "Average"
      threshold          = var.application_config.cpu_threshold
      alarm_description  = "This metric monitors ecs cpu utilization"
    }
    high_memory = {
      alarm_name          = "${local.full_name}-high-memory"
      comparison_operator = "GreaterThanThreshold"
      evaluation_periods  = "2"
      metric_name         = "MemoryUtilization"
      namespace          = "AWS/ECS"
      period             = "120"
      statistic          = "Average"
      threshold          = var.application_config.memory_threshold
      alarm_description  = "This metric monitors ecs memory utilization"
    }
  }
  
  # Common resource tags
  tags = {
    Environment = var.environment
    Project     = var.project_name
    ManagedBy   = "terraform"
    Region      = local.region
  }
}

Best Practices for Variables and Locals

Following established best practices ensures your Terraform configurations remain maintainable, secure, and efficient as they scale and evolve over time.

Variable Organization and Documentation

Organize variables logically and provide comprehensive documentation to improve team collaboration and code maintenance:

# variables.tf - Group related variables together
variable "networking" {
  description = "Networking configuration for the infrastructure"
  type = object({
    vpc_cidr             = string
    availability_zones   = list(string)
    public_subnet_cidrs  = list(string)
    private_subnet_cidrs = list(string)
    enable_nat_gateway   = bool
    enable_vpn_gateway   = bool
  })
  
  validation {
    condition     = length(var.networking.availability_zones) >= 2
    error_message = "At least 2 availability zones must be specified for high availability."
  }
  
  validation {
    condition = length(var.networking.public_subnet_cidrs) == length(var.networking.private_subnet_cidrs)
    error_message = "Number of public and private subnets must match."
  }
}

Security Considerations

Handle sensitive data appropriately using Terraform's sensitive attribute and external secret management:

variable "database_password" {
  description = "Password for the database instance"
  type        = string
  sensitive   = true
  
  validation {
    condition     = length(var.database_password) >= 12
    error_message = "Database password must be at least 12 characters long."
  }
}

variable "api_keys" {
  description = "API keys for external services"
  type        = map(string)
  sensitive   = true
  default     = {}
}

# Use locals to handle sensitive computations
locals {
  # Database connection string with sensitive password
  db_connection_string = "postgresql://${var.db_username}:${var.database_password}@${aws_db_instance.main.endpoint}/${var.db_name}"
  
  # This local value will also be marked as sensitive automatically
}

Dynamic Configuration Patterns

Advanced patterns using variables and locals enable sophisticated dynamic configurations that adapt to changing requirements and environments.

Conditional Resource Creation

variable "enable_monitoring" {
  description = "Enable CloudWatch monitoring and alerting"
  type        = bool
  default     = false
}

variable "enable_backup" {
  description = "Enable automated backup for databases"
  type        = bool
  default     = true
}

locals {
  # Conditional configurations
  monitoring_config = var.enable_monitoring ? {
    dashboard_name = "${local.full_name}-dashboard"
    log_group_name = "/aws/ecs/${local.full_name}"
    retention_days = 30
  } : null
  
  backup_config = var.enable_backup ? {
    backup_window      = "03:00-04:00"
    backup_retention   = 7