Today marks the Day 7 of 30 Days Terraform challenge and today we will deep dive into the Terraform Type Constraints and how we have multiple type constraints in Terraform based on the value.
Variables Based on Values in Terraform
Terraform divides the variables based on the values they hold. They are divided into 3 types such as Primitive, Complex, Any type.
As the name indicates Primitive types indicates simple data value type whereas Complex constraint type indicates complex data values.
Primitive Type Constraint:
Primitive types are the simplest and most commonly used variable types in Terraform. Primitive types are powerful because they enforce strict validation and avoid unexpected results especially when performing comparisons or arithmetic operations.There are exactly three of them: Strings, Numbers and Bool.
1. String Constraint:
A string is simply text. Anything inside quotes i.e. "hello", "production", "ap-south-1" is considered a string. It’s one of the most frequently used types in Terraform because so much of our infrastructure depends on names, IDs, regions, and descriptions… all of which are text.
Used for text-based values. Examnple, we can use this to set the AWS Provider configuration and apply a basic naming convention to S3 or any other resources.
# string type
variable "environment" {
type = string
description = "the environment type"
default = "dev"
}
provider "aws" {
region = var.environment
}
resource "aws_s3_bucket" "example" {
bucket = "my-tf-test-bucket-${var.environment}"
tags = {
Name = "My bucket"
Environment = var.environment
}
}
2: Number Constraint:
A number is exactly what it sounds like i.e. a numeric value like 10, 1, or 500. It seems simple, but defining it correctly in Terraform is important, because it tells Terraform what kind of value to expect.
variable "instance_count" {
type = number
description = "the number of Ec22 instances to create"
default = 1
}
resource "aws_instance" "example" {
ami = "ami-005e54dee72cc1d00"
instance_type = "t3.micro"
count = var.instance_count
tags = {
Environment = var.environment
}
}
3. Boolean:
Booleans are one of those things that look almost too small to matter i.e. they can only ever be true or false. That’s it. Just two possibilities. If strings are like words and numbers are like quantities, then booleans are like the “yes” and “no” signals that help Terraform understand what we want.
Think of a boolean like a simple switch. It’s either on or off.
Examples: Should the EC2 instance have a public IP, or should S3 have an encryption enabled.
variable "associate_public_ip" {
type = bool
description = "associate public ip to ec2 instance"
default = true
}
resource "aws_instance" "example" {
ami = "ami-005e54dee72cc1d00"
instance_type = "t3.micro"
count = var.instance_count
associate_public_ip_address = var.associate_public_ip
tags = {
Environment = var.environment
}
}
If associate_public_ip variable sets it to true, Terraform will happily give the instance a public IP.
If it stays false, the instance stays private and quiet in the VPC.
Complex Type Constraints:
So far, we’ve talked about primitive types. i.e. numbers, strings, and booleans. These are simple because they hold just one value at a time. But as we start working with real-world Terraform configurations, we’ll notice that many things come in groups. And when we want to store multiple values together in a clean, predictable way, Terraform gives us something called complex types.
Complex types are again divided into 5 types such as List, Set, Tuple, Maps and Dictionary.
1. Lists:
A list is exactly what it sounds like i.e. a small collection of items arranged in a specific order. Think of it like writing down your goals for the week or the number of days in a week.
Each item is written one after another, and the order matters because it helps us keep track of what comes first and what follows. Terraform treats lists the same way. All the items inside a list must be of the same type i.e. all strings, or all numbers, or all booleans. Elements in the list are referenced with a index starting with 0. List can contain duplicates too.
variable "allowed_cidr_blocks" {
type = list(string)
description = "list of allowed cidr blocks for security group"
default = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]
}
variable "allowed_instance_types" {
type = list(string)
description = "list of allowed ec2 instance types"
default = ["t2.micro", "t2.small", "t3.micro"]
# Order matters: index 0 = t2.micro, index 1 = t2.small
}
main.tf:
cidr_block = var.allowed_cidr_blocks[0]
instance_type = var.allowed_instance_types[0]
2. Sets:
A set is exactly what it sounds like i.e. a small collection of items arranged in a specific order. Think of it like writing down your goals for the week or the number of days in a week. It is same as set but with the limitation that id doesn't contain duplicates, so no duplication is allowed.
variable "allowed_instance_types" {
type = set(string)
description = "list of allowed ec2 instance types"
default = ["t2.micro", "t2.small", "t3.micro"]
}
main.tf:
resource "aws_instance" "example" {
ami = "ami-005e54dee72cc1d00"
instance_type = tolist(var.allowed_instance_types[0])
}
We cannot directly reference by index in set, we need to convert the set into list first and then should reference them.
3. Maps:
Maps is a collection of multiple key-value pairs. Instead of a single key or value, we will have a collection of key-value pairs, Example for tags in a EC2 instance, we have multiple tags, so we can use these in those places for simple and clean usage.
variable "tags" {
type = map(string)
description = "list of tags for EC2 instances"
default = {
Environment = "dev"
Name = "dev-instance"
}
In main.tf:
resource "aws_instance" "example" {
ami = "ami-005e54dee72cc1d00"
instance_type = "t3.micro"
tags = var.tags
}
4. Tuple:
A tuple is simply a way of storing multiple values of different types, all together, in a specific order.
Each item is different. But they all belong together, in a certain order. It can have multiple values and they can be of multiple data types too.
variable "ingress_values" {
type = tuple ({number, string, number })
default = [443, "tcp", 443]
}
main.tf:
resource "aws_vpc_security_group_ingress_rule" "allow_tls_ipv4" {
security_group_id = aws_security_group.allow_tls.id
cidr_ipv4 = aws_vpc.main.cidr_block
from_port = var.ingress_values[0]
ip_protocol = var.ingress_values[1]
to_port = var.ingress_values[2]
}
5. Dictionary:
It is more of a type Object. It is also a collection of multiple data types but not in the list format.
variable "tags" {
description = "list of tags for EC2 instances
type = object({
environment = string
instance_count = number
name = string
})
default = {
environment = "dev"
instance_count = 1
name = "dev-instance"
}
Under main.tf:
resource "aws_instance" "example" {
ami = "ami-005e54dee72cc1d00"
instance_type = "t3.micro"
count = var.tags.instance_count
tags = {
Environment = var.tags.environment
Name = var.tags.name
}
}
Conclusion:
Before we wind up the blog, There are few key points which you need to remember when working with Terraform Type Constraints.
- Whenever there is a key-value pair, You should always access them with a key.
- Whenever there is a List, You should always access them with index.
- Whenever there is a map, You should always access them with a key-value pair.
- Whenever there is a set, You should first convert that to a list and then access that with index.
This is a large blog compared to the previous days and it is because, we have been introduced to a large number of terraform type constraints which will be majorly used when we start building projects to keep the Terraform code clean, organized and reusable.
Below is the Youtube Video for reference: Tech Tutorials with Piyush — “Terraform Type Constraints”

Top comments (0)