Skip to main content

Command Palette

Search for a command to run...

Terraform Type Constraints

Published
4 min read

Video Link: here.


Type Constraints

Terraform variables are primarily classified in two ways:

  • Based on Purpose (how they are used) and

  • Based on Value (what kind of data they hold, defined by Type Constraints).

Classification Based on Purpose

This defines the role of the data within the configuration:

  • Input Variables (variable block): These are parameters that let us customise our configuration from the outside (e.g., region, instance count, environment name).

  • Output Values (output block): These expose information about the infrastructure that has been created (e.g., public IP addresses, DNS names, resource IDs).

  • Local Values (locals block): These are temporary, named expressions used internally within the module to avoid repeating complex values or calculations.

Classification Based on Value (Type Constraints)

This defines the specific data type a variable must accept. This is specified using the type argument in a variable block.

A. Primitive Types

These are simple, single-value types:

TypeDescription
stringA sequence of Unicode characters (e.g., "us-east-1").
numberAn integer or a floating-point value (e.g., 5, 3.14).
boolA boolean value, either true or false.

B. Complex Types

These are collection types that group multiple values:

TypeDescription
listAn ordered sequence of values, all of the same type (e.g., ["web", "app", "db"]).
setAn unordered collection of unique values, all of the same type (e.g., ["a", "b"]).
mapA collection of key-value pairs, where all values are of the same type (e.g., {name="server", env="dev"}).
objectA structure with named attributes, each having its own specific type (e.g., object({ name = string, count = number })).
tupleAn ordered, fixed-length sequence where values can be of different types (e.g., tuple([string, number, bool])).

C. Special Types

  • any: Allows the variable to accept values of any type. While flexible, this reduces type safety and is generally discouraged unless necessary.

  • null: Represents a value that is absent or undefined.


Example

1. Primitive Types

string - Text values

 "environment" ">variable "environment" {
  type    = string
  default = "dev"
}

number - Numeric values (integers or decimals)

 "instance_count" ">variable "instance_count" {
  type        = number
  description = "Number of EC2 instances to create"
}

bool - True or False

 "monitoring_enabled" ">variable "monitoring_enabled" {
  type    = bool
  default = true
}

2. Collection Types

list(type) - Ordered sequence, elements accessed by index

 "cidr_block" ">variable "cidr_block" {
  type    = list(string)
  default = ["10.0.0.0/16", "192.168.0.0/16", "172.16.0.0/16"]
}

# Access: var.cidr_block[0] → "10.0.0.0/16"

set(type) - Unordered unique values (no duplicates)

 "allowed_region" ">variable "allowed_region" {
  type    = set(string)
  default = ["us-east-1", "us-west-2", "eu-west-1"]
}

# Cannot access by index directly! Use: tolist(var.allowed_region)[0]

map(type) - Key-value pairs (keys are always strings)

 "tags" ">variable "tags" {
  type = map(string)
  default = {
    Name        = "dev-EC2-Instance"
    Environment = "dev"
    created_by  = "amal"
  }
}

# Access: var.tags["Name"] → "dev-EC2-Instance"
# Or:     var.tags.Name

3. Structural Types

tuple([types]) - Fixed-length sequence with specific types per position

 "ingress_values" ">variable "ingress_values" {
  type    = tuple([number, string, number])  # ORDER MATTERS!
  default = [443, "tcp", 443]
}

# Access: var.ingress_values[0] → 443 (number)
#         var.ingress_values[1] → "tcp" (string)
#         var.ingress_values[2] → 443 (number)

object({}) - Named attributes with specific types

 "config" ">variable "config" {
  type = object({
    region         = string,
    monitoring     = bool,
    instance_count = number
  })
  default = {
    region         = "us-east-1"
    monitoring     = true
    instance_count = 1
  }
}

# Access: var.config.region → "us-east-1"
#         var.config.monitoring → true

Usage Examples from Code

# Using list index
instance_type = var.ec2_allowed_types[1]  # Gets "t3.small"

# Using object attributes
count      = var.config.instance_count
region     = var.config.region
monitoring = var.config.monitoring

# Using tuple values for security group rule
from_port   = var.ingress_values[0]  # 443
ip_protocol = var.ingress_values[1]  # "tcp"
to_port     = var.ingress_values[2]  # 443

# Converting set to list (for index access)
region = tolist(var.allowed_region)[0]

Key Takeaways

  1. Primitive types (string, number, bool) = single values

  2. Collections (list, set, map) = multiple values of SAME type

  3. Structural (tuple, object) = multiple values of DIFFERENT types

  4. list vs tuple: list = same type, dynamic length | tuple = mixed types, fixed length

  5. map vs object: map = same value type | object = different value types per attribute

  6. set quirk: Use tolist() to access elements by index

Refer to the code here: Repo link.


Arigato!

More from this blog

Code Companions

32 posts