Terraform map variable helps with managing your configuration using key-value pairs, allowing you to easily handle different environments or regions without repeating your code.
In this blog, we’ll cover what Terraform maps are, explore their use cases, and provide some practical examples. We’ll also look at some of the different types of maps, the best practices for using them, and much more.
Disclaimer: All use cases of [.code]terraform map[.code] variables discussed here work similarly in OpenTofu, the open-source Terraform alternative. However, to keep it simple and familiar for DevOps engineers, we will use “Terraform map” as a catch-all term throughout this blog post.
What is a Terraform Map Variable?
A [.code]terraform map[.code] variable is a data structure in Terraform (and OpenTofu) that stores key-value pairs, where each key is linked to a specific value.
For example, you can use a map to set instance types for different environments, like [.code]dev = ‘t2.micro’[.code] and [.code]prod = ‘m5.large’[.code], all within a single variable.
This approach simplifies the creation of variables and management of Terraform configurations, eliminating the need to create separate variables repeatedly, and aligns with the Don’t Repeat Yourself (DRY) coding best practices.
The [.code]maps[.code] variable can store different types of values, which you can define using type labels. Some of the common ones include:
- map(string): Stores values as strings (e.g., linking environment names to instance types)
- map(number): Stores number values, such as whole numbers or decimals
- map(bool): Holds true or false values
- map(list): Stores lists (arrays) as values, where each list contains items of the same type
- map(map(string)): Holds another map inside it, where each inner map contains string values (e.g., settings for different environments or regions)
- map(set): Holds sets as values, where each set has unique items of one type
- map(tuple([ ... ])): Stores a fixed list of items, where each item can be of a different type
- map(object({ ... })): Uses objects as values, which means it can have multiple fields with a set structure
Examples: How to Use Terraform Map Types
Now that we’ve covered the different [.code]map[.code] types in Terraform, let’s see them in action.
map(string)
Let’s start with [.code]map(string)[.code], the simplest [.code]map[.code] type, one of the common use cases for which is storing region-specific configurations. In the example below, a [.code]map(string)[.code] variable stores AMIs for different AWS regions:
variable "region_map" {
type = map(string)
default = {
"us-east-1" = "ami-0c55b159cbfafe1f0"
"us-west-1" = "ami-0bdb828fd58c52235"
}
}
In this example, we define a variable named [.code]region_map[.code] with the type [.code]map(string)[.code], which indicates that it is a map where both the keys (regions) and the values Amazon Machine Image (AMI) IDs are strings.
This makes it easy to reference different configurations based on the region without repeating your Terraform code.
For instance, to access the ‘ami’ value for ‘us-east-1’ region, you would use [.code]var.region_map[“us-east-1”][.code] to get the corresponding AMI value ‘ami-0c55b159cbfafe1f0’.
map(object)
Building on the previous example, let’s now see how you can use [.code]map(objects)[.code] to store multiple values for each region:
variable "instance_config_map" {
type = map(object({
instance_type = string
ami = string
ebs = bool
}))
default = {
"us-east-1" = {
instance_type = "t2.micro"
ami = "ami-0c55b159cbfafe1f0"
ebs_optimized = true
}
"us-west-1" = {
instance_type = "t3.medium"
ami = "ami-0bdb828fd58c52235"
ebs_optimized = true
}
}
}
As you can see in the example above, each key (‘us-east-1’ and ‘us-west-1,’ etc.) is associated with a complex object that contains attributes like [.code]instance_type[.code], [.code]ami[.code], and [.code]ebs_optimized[.code], each having different data types.
This data structure adds additional flexibility, enabling you to use multiple values from the map to configure EC2 instances with specific configuration needs.
For instance, you can directly access the [.code]ebs_optimized[.code] value for the ‘us-east-1’ region, by using [.code]var.instance_config_map[“us-east-1”].ebs_optimized[.code].
map(map(string))
Next, let’s see how you can use nested maps to define multiple environments, such as production and staging. For example:
variable "nested_map" {
type = map(map(string))
default = {
"production" = {
"us-east-1" = "ami-0c55b159cbfafe1f0"
"us-west-1" = "ami-0bdb828fd58c52235"
}
"staging" = {
"us-east-1" = "ami-0a12345678abcd123"
"us-west-1" = "ami-0b98765432efgh987"
}
}
}
In this scenario, each environment is linked to a [.code]map[.code] with AMI values for different regions, such as ‘us-east-1’ and ‘us-west-1’. This allows you to easily handle the environments and regions in one map.
And so, to get the AMI for the ‘production’ environment in the ‘us-east-1’ region, you would use [.code]var.nested_map[“production”][“us-east-1”][.code] in your code to get the corresponding AMI, ‘ami-0c55b159cbfafe1f0.’
Advanced Examples
Now let’s take things a step further and see how using the [.code]terraform map[.code] variable can help organize and manage related data in Terraform.
Deploying AWS S3 Bucket with Map Variable
In this example, we'll configure an S3 bucket with different storage classes for each environment using a [.code]map[.code] variable.
Here, the map variable helps manage multiple configurations for S3 buckets based on the desired environment. We’ll also look at how to access specific values within a [.code]map[.code] variable.
Step 1: Define the AWS provider
First, let's set up the AWS provider and specify the region:
provider "aws" {
region = "us-east-1"
}
Step 2: Set up S3 storage classes
Next, define a map variable called ‘storage_classes’:
variable "storage_classes" {
type = map(string)
default = {
dev = "STANDARD_IA"
staging = "ONEZONE_IA"
prod = "INTELLIGENT_TIERING"
}
}
Step 3: Specify the environment
Next, we’ll add the [.code]environment[.code] variable to specify the environment in which you want to deploy your resources.
variable "environment" {
type = string
default = "dev"
}
Step 4: Create an S3 bucket
Now, let’s create the [.code]aws_s3_bucket[.code] with the bucket name using the ‘environment’ value:
resource "aws_s3_bucket" "env0-bucket01" {
bucket = "env0-01-buck-${var.environment}"
}
Note that since the value for [.code]environment[.code] is set to ‘dev’, it will create an S3 bucket for the dev environment named ‘env0-01-buck-dev’.
Step 5: Create lifecycle configuration
Finally, create a lifecycle rule for the S3 bucket, like so:
resource "aws_s3_bucket_lifecycle_configuration" "env0-bucket01" {
bucket = aws_s3_bucket.env0-bucket01.id
rule {
id = "rule1"
status = "Enabled"
transition {
days = 30
storage_class = var.storage_classes[var.environment]
}
}
}
In the above code, the [.code]storage_class = var.storage_classes[var.environment][.code] selects the storage class from the [.code]storage_classes[.code] map based on the ‘dev’ environment, which is [.code]STANDARD_IA[.code] storage class.
Based on the value of the ‘days’ argument inside the ‘transition’ block, objects (files or collections of data) will be moved from the S3 bucket to the ‘dev’ storage class after 30 days.
This is where a map will come in handy; if we were managing five different environments without using a [.code]map[.code], we would need to create five separate variables, one for each environment. Using a [.code]loop[.code] with a [.code]map[.code] allows us to handle all configurations together, reducing unnecessary overhead.
Using Terraform Lookup Function with Map(Object)
In this example, we’ll set up an EC2 instance using the [.code]instance_configs[.code] variable, an object map.
The [.code]instance_configs[.code] map will contain the [.code]ami[.code] and [.code]instance_type[.code] for different regions, such as ‘us-east-1’, and ‘us-west-2’.
We’ll also use the [.code]lookup[.code] function to retrieve the AMI and instance type based on the selected region.
variable "instance_configs" {
type = map(object({
ami = string
instance_type = string
}))
default = {
us-east-1 = {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
us-west-2 = {
ami = "ami-01e24be29428c15b2"
instance_type = "t3.micro"
}
}
}
variable "region" {
type = string
default = "us-west-2"
}
resource "aws_instance" "env0-instance" {
ami = lookup(var.instance_configs[var.region], "ami")
instance_type = lookup(var.instance_configs[var.region], "instance_type")
}
Here, the [.code]instance_configs[.code] map helps us define region-specific configurations, such as ‘ami’ and ‘instance_type’, in a single variable, eliminating the need to repeat the configuration code for each region.
Moreover, with the help of [.code]lookup[.code] function, this will enable us to easily select desired configurations for your EC2 instance in the ‘us-west-2’ region, saving you time and effort.
Convert Terraform List to Map with Local Values
When working with Terraform values, you may need to transform values from one type to another, such as [.code]lists[.code] to [.code]map[.code].
To show how this could be done, we’ll use [.code]local[.code] values to transform simple lists into a map, where each key will be an environment corresponding to a CIDR block. Using this map, we'll create VPCs for staging, production, and development environments.
Let’s start by defining a list of environments and CIDR blocks:
variable "environments" {
type = list(string)
default = ["development", "staging", "production"]
}
variable "cidr_blocks" {
type = list(string)
default = ["10.0.0.0/16", "10.1.0.0/16", "10.2.0.0/16"]
}
locals {
vpc_map = { for index, env in var.environments : env => var.cidr_blocks[index] }
}
In this configuration:
- The [.code]environments[.code] variable is a list of environments, such as development, staging, and production.
- The [.code]cidr_blocks[.code] variable contains the CIDR blocks for development, staging, and production in that specific order.
- In the [.code]locals[.code] block, we use [.code]for[.code] loop to create a [.code]map[.code] where each environment is associated with the CIDR block at the same index. The ‘index’ variable allows us to get the index of the environment, which we use to access the corresponding CIDR block from [.code]cidr_blocks[.code].
Now, let’s create the VPCs by iterating over the local [.code]vpc_map[.code] using the [.code]for_each[.code] loop:
resource "aws_vpc" "vpc" {
for_each = local.vpc_map
cidr_block = each.value
tags = {
Name = "${each.key}-vpc"
Environment = each.key
}
}
In this code:
- Using the [.code]for_each[.code] loop, Terraform creates a VPC for the development, staging, and production environments
- The CIDR block for each VPC is pulled from the ‘vpc_map’ local value using ‘each.key’
After running [.code]terraform apply[.code], you’ll see the creation of VPCs for each environment (development, staging, and production):
aws_vpc.vpc["production"]: Creating...
aws_vpc.vpc["development"]: Creating...
aws_vpc.vpc["staging"]: Creating...
aws_vpc.vpc["production"]: Creation complete after 5s [id=vpc-0b19315fc88ef7a9f]
aws_vpc.vpc["development"]: Creation complete after 5s [id=vpc-0951666b4be405d96]
aws_vpc.vpc["staging"]: Creation complete after 5s [id=vpc-095398cfeca567cd4]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
You can verify that the subnets for [.code]dev[.code], [.code]staging[.code], and [.code]prod[.code] environments have been created successfully using the AWS console. Here, each subnet has been assigned its unique ID, reflecting the deployment of resources as planned.
Best Practices when using Terraform Map
Now, after going over the examples, let’s summarize some of the best practices you should follow while using the Terraform maps:
- Use clear and descriptive names: This helps make your code easier to read and work with, especially when you or someone else needs to check it later.
- Use loops to avoid repeating code: If you need to create multiple resources from a map, use [.code]for_each[.code] to loop through the map and create all the resources at once. This saves time and keeps your code DRY and more efficient.
- Make use of functions to handle data: Terraform provides functions such as [.code]lookup[.code], [.code]merge[.code], [.code]flatten[.code], [.code]toset[.code], and [.code]tolist[.code] to transform your map data into other data for easy organization and management.
- Use locals for complex logic: When you need to modify or organize your map data, use [.code]local[.code] values. This keeps your code clean, helps avoid duplication, and makes it easier to manage more complicated logic.
Using Terraform Maps with env0
env0 makes working with Terraform maps easier by providing a centralized platform to import and manage infrastructure configurations.
Within env0, projects are used to provide granular access control to environments, while environments are an entity representing a deployment managed by env0.
In env0’s variables section, you can import variables directly from your code, and for map variables, it converts their value into a JSON format. You can add these environment variables to the project variables within env0 and use them across all the environments, such as 'dev', 'staging', and 'prod' under the same project.
To give an example, let’s see how you can integrate a Terraform configuration with env0. We will import the map variable in the environment, add it to the project’s variable, and reuse that map variable across different environments within the same project.
Step 1: Create a new environment
First, create an environment in env0 under your project by clicking on ‘Create New Environment’.
Step 2: Connect your GitHub repository
Select ‘Github.com’ for the ‘VCS’ integration to create a new environment. Here, attach your GitHub repository link.
Step 3: Import Terraform map variable
Next, in the Variables screen, import the Terraform map variable in the variable section as an environment variable by using the ‘Load Variables From Code’ button.
Here, the [.code]vpc_config[.code] map variable are imported , along with the [.code]aws_access_key[.code] and [.code]aws_secret_key[.code] variables from the Terraform code and converted into the appropriate format (with [.code]vpc_config[.code] being in JSON format).
Step 4: Configure environment details and start deployment
Next, enter the environment and workspace name (if required) in the respective fields as shown below. If a workspace name is not provided, env0 will automatically generate one and assign it to the environment by default.
Finish the setup by clicking the ‘Done’ button to start the deployment process.
Step 5: Review and approve Terraform plan
Now, check the planned resources, and once you’ve reviewed them, approve the plan to run [.code]terraform apply[.code].
Step 6: Post-deployment logs
Once the deployment is completed, check the logs to review the changes and ensure the deployment was successful.
In the 'Outputs' section, you can also view the output variables such as the [.code]vpc_ids[.code].
These values are generated using the [.code]vpc_config[.code] map, which defines the specific VPC configurations (like CIDR blocks and availability zones) for each environment.
Step 7: Add Terraform map as a project variable
Now, if you want to use the [.code]vpc_config[.code] variable in other environments within the same project, you can add it as a project variable.
To do this, navigate to the 'Variables' section under 'Project' and add the [.code]vpc_config[.code] variable. This allows you to use this variable across all environments within the same project, ensuring consistency across all the environments.
Once added as a project variable, the [.code]vpc_config[.code] variable will automatically appear in the variable section whenever you create a new environment or deploy an existing one within this project.
This means you no longer need to do the overhead of creating the same variables in multiple environments, which saves time and reduces the risk of configuration errors.
Conclusion
By now, you should have a clear understanding of Terraform maps and how to use them efficiently for your use cases. We looked at how maps can simplify managing resource configuration for multiple environments.
With different map types and functions, such as [.code]lookup[.code] and [.code]for_each[.code] loop, you can organize your own infrastructure code more effectively and avoid repetitive code.
Frequently Asked Questions
Q. What is the difference between a map and a tuple in Terraform?
A map stores key-value pairs, while a tuple is defined as an ordered list of values without keys.
Q. Are Terraform maps ordered?
Terraform maps default values are unordered, meaning the order in which you define keys and values doesn't matter, and they may not be retrieved in the same order. This is important to keep in mind when working with maps, as Terraform prioritizes data integrity over order.
Q. What is the difference between a list and a map in Terraform?
A list stores string keys and values in order, while a map stores key-value pairs with no specific order.
Q. What does flatten do in Terraform?
In Terraform CLI [.code]flatten[.code] merges nested lists into a single, flat list.
Terraform map variable helps with managing your configuration using key-value pairs, allowing you to easily handle different environments or regions without repeating your code.
In this blog, we’ll cover what Terraform maps are, explore their use cases, and provide some practical examples. We’ll also look at some of the different types of maps, the best practices for using them, and much more.
Disclaimer: All use cases of [.code]terraform map[.code] variables discussed here work similarly in OpenTofu, the open-source Terraform alternative. However, to keep it simple and familiar for DevOps engineers, we will use “Terraform map” as a catch-all term throughout this blog post.
What is a Terraform Map Variable?
A [.code]terraform map[.code] variable is a data structure in Terraform (and OpenTofu) that stores key-value pairs, where each key is linked to a specific value.
For example, you can use a map to set instance types for different environments, like [.code]dev = ‘t2.micro’[.code] and [.code]prod = ‘m5.large’[.code], all within a single variable.
This approach simplifies the creation of variables and management of Terraform configurations, eliminating the need to create separate variables repeatedly, and aligns with the Don’t Repeat Yourself (DRY) coding best practices.
The [.code]maps[.code] variable can store different types of values, which you can define using type labels. Some of the common ones include:
- map(string): Stores values as strings (e.g., linking environment names to instance types)
- map(number): Stores number values, such as whole numbers or decimals
- map(bool): Holds true or false values
- map(list): Stores lists (arrays) as values, where each list contains items of the same type
- map(map(string)): Holds another map inside it, where each inner map contains string values (e.g., settings for different environments or regions)
- map(set): Holds sets as values, where each set has unique items of one type
- map(tuple([ ... ])): Stores a fixed list of items, where each item can be of a different type
- map(object({ ... })): Uses objects as values, which means it can have multiple fields with a set structure
Examples: How to Use Terraform Map Types
Now that we’ve covered the different [.code]map[.code] types in Terraform, let’s see them in action.
map(string)
Let’s start with [.code]map(string)[.code], the simplest [.code]map[.code] type, one of the common use cases for which is storing region-specific configurations. In the example below, a [.code]map(string)[.code] variable stores AMIs for different AWS regions:
variable "region_map" {
type = map(string)
default = {
"us-east-1" = "ami-0c55b159cbfafe1f0"
"us-west-1" = "ami-0bdb828fd58c52235"
}
}
In this example, we define a variable named [.code]region_map[.code] with the type [.code]map(string)[.code], which indicates that it is a map where both the keys (regions) and the values Amazon Machine Image (AMI) IDs are strings.
This makes it easy to reference different configurations based on the region without repeating your Terraform code.
For instance, to access the ‘ami’ value for ‘us-east-1’ region, you would use [.code]var.region_map[“us-east-1”][.code] to get the corresponding AMI value ‘ami-0c55b159cbfafe1f0’.
map(object)
Building on the previous example, let’s now see how you can use [.code]map(objects)[.code] to store multiple values for each region:
variable "instance_config_map" {
type = map(object({
instance_type = string
ami = string
ebs = bool
}))
default = {
"us-east-1" = {
instance_type = "t2.micro"
ami = "ami-0c55b159cbfafe1f0"
ebs_optimized = true
}
"us-west-1" = {
instance_type = "t3.medium"
ami = "ami-0bdb828fd58c52235"
ebs_optimized = true
}
}
}
As you can see in the example above, each key (‘us-east-1’ and ‘us-west-1,’ etc.) is associated with a complex object that contains attributes like [.code]instance_type[.code], [.code]ami[.code], and [.code]ebs_optimized[.code], each having different data types.
This data structure adds additional flexibility, enabling you to use multiple values from the map to configure EC2 instances with specific configuration needs.
For instance, you can directly access the [.code]ebs_optimized[.code] value for the ‘us-east-1’ region, by using [.code]var.instance_config_map[“us-east-1”].ebs_optimized[.code].
map(map(string))
Next, let’s see how you can use nested maps to define multiple environments, such as production and staging. For example:
variable "nested_map" {
type = map(map(string))
default = {
"production" = {
"us-east-1" = "ami-0c55b159cbfafe1f0"
"us-west-1" = "ami-0bdb828fd58c52235"
}
"staging" = {
"us-east-1" = "ami-0a12345678abcd123"
"us-west-1" = "ami-0b98765432efgh987"
}
}
}
In this scenario, each environment is linked to a [.code]map[.code] with AMI values for different regions, such as ‘us-east-1’ and ‘us-west-1’. This allows you to easily handle the environments and regions in one map.
And so, to get the AMI for the ‘production’ environment in the ‘us-east-1’ region, you would use [.code]var.nested_map[“production”][“us-east-1”][.code] in your code to get the corresponding AMI, ‘ami-0c55b159cbfafe1f0.’
Advanced Examples
Now let’s take things a step further and see how using the [.code]terraform map[.code] variable can help organize and manage related data in Terraform.
Deploying AWS S3 Bucket with Map Variable
In this example, we'll configure an S3 bucket with different storage classes for each environment using a [.code]map[.code] variable.
Here, the map variable helps manage multiple configurations for S3 buckets based on the desired environment. We’ll also look at how to access specific values within a [.code]map[.code] variable.
Step 1: Define the AWS provider
First, let's set up the AWS provider and specify the region:
provider "aws" {
region = "us-east-1"
}
Step 2: Set up S3 storage classes
Next, define a map variable called ‘storage_classes’:
variable "storage_classes" {
type = map(string)
default = {
dev = "STANDARD_IA"
staging = "ONEZONE_IA"
prod = "INTELLIGENT_TIERING"
}
}
Step 3: Specify the environment
Next, we’ll add the [.code]environment[.code] variable to specify the environment in which you want to deploy your resources.
variable "environment" {
type = string
default = "dev"
}
Step 4: Create an S3 bucket
Now, let’s create the [.code]aws_s3_bucket[.code] with the bucket name using the ‘environment’ value:
resource "aws_s3_bucket" "env0-bucket01" {
bucket = "env0-01-buck-${var.environment}"
}
Note that since the value for [.code]environment[.code] is set to ‘dev’, it will create an S3 bucket for the dev environment named ‘env0-01-buck-dev’.
Step 5: Create lifecycle configuration
Finally, create a lifecycle rule for the S3 bucket, like so:
resource "aws_s3_bucket_lifecycle_configuration" "env0-bucket01" {
bucket = aws_s3_bucket.env0-bucket01.id
rule {
id = "rule1"
status = "Enabled"
transition {
days = 30
storage_class = var.storage_classes[var.environment]
}
}
}
In the above code, the [.code]storage_class = var.storage_classes[var.environment][.code] selects the storage class from the [.code]storage_classes[.code] map based on the ‘dev’ environment, which is [.code]STANDARD_IA[.code] storage class.
Based on the value of the ‘days’ argument inside the ‘transition’ block, objects (files or collections of data) will be moved from the S3 bucket to the ‘dev’ storage class after 30 days.
This is where a map will come in handy; if we were managing five different environments without using a [.code]map[.code], we would need to create five separate variables, one for each environment. Using a [.code]loop[.code] with a [.code]map[.code] allows us to handle all configurations together, reducing unnecessary overhead.
Using Terraform Lookup Function with Map(Object)
In this example, we’ll set up an EC2 instance using the [.code]instance_configs[.code] variable, an object map.
The [.code]instance_configs[.code] map will contain the [.code]ami[.code] and [.code]instance_type[.code] for different regions, such as ‘us-east-1’, and ‘us-west-2’.
We’ll also use the [.code]lookup[.code] function to retrieve the AMI and instance type based on the selected region.
variable "instance_configs" {
type = map(object({
ami = string
instance_type = string
}))
default = {
us-east-1 = {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
us-west-2 = {
ami = "ami-01e24be29428c15b2"
instance_type = "t3.micro"
}
}
}
variable "region" {
type = string
default = "us-west-2"
}
resource "aws_instance" "env0-instance" {
ami = lookup(var.instance_configs[var.region], "ami")
instance_type = lookup(var.instance_configs[var.region], "instance_type")
}
Here, the [.code]instance_configs[.code] map helps us define region-specific configurations, such as ‘ami’ and ‘instance_type’, in a single variable, eliminating the need to repeat the configuration code for each region.
Moreover, with the help of [.code]lookup[.code] function, this will enable us to easily select desired configurations for your EC2 instance in the ‘us-west-2’ region, saving you time and effort.
Convert Terraform List to Map with Local Values
When working with Terraform values, you may need to transform values from one type to another, such as [.code]lists[.code] to [.code]map[.code].
To show how this could be done, we’ll use [.code]local[.code] values to transform simple lists into a map, where each key will be an environment corresponding to a CIDR block. Using this map, we'll create VPCs for staging, production, and development environments.
Let’s start by defining a list of environments and CIDR blocks:
variable "environments" {
type = list(string)
default = ["development", "staging", "production"]
}
variable "cidr_blocks" {
type = list(string)
default = ["10.0.0.0/16", "10.1.0.0/16", "10.2.0.0/16"]
}
locals {
vpc_map = { for index, env in var.environments : env => var.cidr_blocks[index] }
}
In this configuration:
- The [.code]environments[.code] variable is a list of environments, such as development, staging, and production.
- The [.code]cidr_blocks[.code] variable contains the CIDR blocks for development, staging, and production in that specific order.
- In the [.code]locals[.code] block, we use [.code]for[.code] loop to create a [.code]map[.code] where each environment is associated with the CIDR block at the same index. The ‘index’ variable allows us to get the index of the environment, which we use to access the corresponding CIDR block from [.code]cidr_blocks[.code].
Now, let’s create the VPCs by iterating over the local [.code]vpc_map[.code] using the [.code]for_each[.code] loop:
resource "aws_vpc" "vpc" {
for_each = local.vpc_map
cidr_block = each.value
tags = {
Name = "${each.key}-vpc"
Environment = each.key
}
}
In this code:
- Using the [.code]for_each[.code] loop, Terraform creates a VPC for the development, staging, and production environments
- The CIDR block for each VPC is pulled from the ‘vpc_map’ local value using ‘each.key’
After running [.code]terraform apply[.code], you’ll see the creation of VPCs for each environment (development, staging, and production):
aws_vpc.vpc["production"]: Creating...
aws_vpc.vpc["development"]: Creating...
aws_vpc.vpc["staging"]: Creating...
aws_vpc.vpc["production"]: Creation complete after 5s [id=vpc-0b19315fc88ef7a9f]
aws_vpc.vpc["development"]: Creation complete after 5s [id=vpc-0951666b4be405d96]
aws_vpc.vpc["staging"]: Creation complete after 5s [id=vpc-095398cfeca567cd4]
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.
You can verify that the subnets for [.code]dev[.code], [.code]staging[.code], and [.code]prod[.code] environments have been created successfully using the AWS console. Here, each subnet has been assigned its unique ID, reflecting the deployment of resources as planned.
Best Practices when using Terraform Map
Now, after going over the examples, let’s summarize some of the best practices you should follow while using the Terraform maps:
- Use clear and descriptive names: This helps make your code easier to read and work with, especially when you or someone else needs to check it later.
- Use loops to avoid repeating code: If you need to create multiple resources from a map, use [.code]for_each[.code] to loop through the map and create all the resources at once. This saves time and keeps your code DRY and more efficient.
- Make use of functions to handle data: Terraform provides functions such as [.code]lookup[.code], [.code]merge[.code], [.code]flatten[.code], [.code]toset[.code], and [.code]tolist[.code] to transform your map data into other data for easy organization and management.
- Use locals for complex logic: When you need to modify or organize your map data, use [.code]local[.code] values. This keeps your code clean, helps avoid duplication, and makes it easier to manage more complicated logic.
Using Terraform Maps with env0
env0 makes working with Terraform maps easier by providing a centralized platform to import and manage infrastructure configurations.
Within env0, projects are used to provide granular access control to environments, while environments are an entity representing a deployment managed by env0.
In env0’s variables section, you can import variables directly from your code, and for map variables, it converts their value into a JSON format. You can add these environment variables to the project variables within env0 and use them across all the environments, such as 'dev', 'staging', and 'prod' under the same project.
To give an example, let’s see how you can integrate a Terraform configuration with env0. We will import the map variable in the environment, add it to the project’s variable, and reuse that map variable across different environments within the same project.
Step 1: Create a new environment
First, create an environment in env0 under your project by clicking on ‘Create New Environment’.
Step 2: Connect your GitHub repository
Select ‘Github.com’ for the ‘VCS’ integration to create a new environment. Here, attach your GitHub repository link.
Step 3: Import Terraform map variable
Next, in the Variables screen, import the Terraform map variable in the variable section as an environment variable by using the ‘Load Variables From Code’ button.
Here, the [.code]vpc_config[.code] map variable are imported , along with the [.code]aws_access_key[.code] and [.code]aws_secret_key[.code] variables from the Terraform code and converted into the appropriate format (with [.code]vpc_config[.code] being in JSON format).
Step 4: Configure environment details and start deployment
Next, enter the environment and workspace name (if required) in the respective fields as shown below. If a workspace name is not provided, env0 will automatically generate one and assign it to the environment by default.
Finish the setup by clicking the ‘Done’ button to start the deployment process.
Step 5: Review and approve Terraform plan
Now, check the planned resources, and once you’ve reviewed them, approve the plan to run [.code]terraform apply[.code].
Step 6: Post-deployment logs
Once the deployment is completed, check the logs to review the changes and ensure the deployment was successful.
In the 'Outputs' section, you can also view the output variables such as the [.code]vpc_ids[.code].
These values are generated using the [.code]vpc_config[.code] map, which defines the specific VPC configurations (like CIDR blocks and availability zones) for each environment.
Step 7: Add Terraform map as a project variable
Now, if you want to use the [.code]vpc_config[.code] variable in other environments within the same project, you can add it as a project variable.
To do this, navigate to the 'Variables' section under 'Project' and add the [.code]vpc_config[.code] variable. This allows you to use this variable across all environments within the same project, ensuring consistency across all the environments.
Once added as a project variable, the [.code]vpc_config[.code] variable will automatically appear in the variable section whenever you create a new environment or deploy an existing one within this project.
This means you no longer need to do the overhead of creating the same variables in multiple environments, which saves time and reduces the risk of configuration errors.
Conclusion
By now, you should have a clear understanding of Terraform maps and how to use them efficiently for your use cases. We looked at how maps can simplify managing resource configuration for multiple environments.
With different map types and functions, such as [.code]lookup[.code] and [.code]for_each[.code] loop, you can organize your own infrastructure code more effectively and avoid repetitive code.
Frequently Asked Questions
Q. What is the difference between a map and a tuple in Terraform?
A map stores key-value pairs, while a tuple is defined as an ordered list of values without keys.
Q. Are Terraform maps ordered?
Terraform maps default values are unordered, meaning the order in which you define keys and values doesn't matter, and they may not be retrieved in the same order. This is important to keep in mind when working with maps, as Terraform prioritizes data integrity over order.
Q. What is the difference between a list and a map in Terraform?
A list stores string keys and values in order, while a map stores key-value pairs with no specific order.
Q. What does flatten do in Terraform?
In Terraform CLI [.code]flatten[.code] merges nested lists into a single, flat list.