Terraform Map Variable: A Complete Guide with Practical Examples

Terraform Map Variable: A Complete Guide with Practical Examples


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 terraform map 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 terraform map 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 dev = ‘t2.micro’ and prod = ‘m5.large’, 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 maps 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 map types in Terraform, let’s see them in action.



map(string)

Let’s start with map(string), the simplest map type, one of the common use cases for which is storing region-specific configurations. In the example below, a map(string) variable stores AMIs for different AWS regions:

variable "region_map" {
  type = map(string)
  default = {
    "us-east-1" = "ami-0c55b159cbfafe1f0"
    "us-west-1" = "ami-0bdb828fd58c52235"
  }
}
Enter fullscreen mode

Exit fullscreen mode

In this example, we define a variable named region_map with the type map(string), 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 var.region_map[“us-east-1”] to get the corresponding AMI value ‘ami-0c55b159cbfafe1f0’.  



map(object)

Building on the previous example, let’s now see how you can use map(objects) 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
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode

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 instance_type, ami, and ebs_optimized,  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 ebs_optimized value for the ‘us-east-1’ region, by using var.instance_config_map[“us-east-1”].ebs_optimized.



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"
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode

In this scenario, each environment is linked to a map 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 var.nested_map[“production”][“us-east-1”] 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 terraform map 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 map 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 map 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"
}
Enter fullscreen mode

Exit fullscreen mode



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"
  }
}
Enter fullscreen mode

Exit fullscreen mode



Step 3: Specify the environment

Next, we’ll add the environment variable to specify the environment in which you want to deploy your resources.  

variable "environment" {
  type    = string
  default = "dev"
}
Enter fullscreen mode

Exit fullscreen mode



Step 4: Create an S3 bucket

Now, let’s create the aws_s3_bucket with the bucket name using the ‘environment’ value:

resource "aws_s3_bucket" "env0-bucket01" {
  bucket = "env0-01-buck-${var.environment}"
}
Enter fullscreen mode

Exit fullscreen mode

Note that since the value for environment 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]
    }
  }
}
Enter fullscreen mode

Exit fullscreen mode

In the above code, the storage_class = var.storage_classes[var.environment] selects the storage class from the storage_classes map based on the ‘dev’ environment, which is STANDARD_IA 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 map, we would need to create five separate variables, one for each environment. Using a loop with a map 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 instance_configs variable, an object map. 

The instance_configs map will contain the ami and instance_type for different regions, such as ‘us-east-1’, and ‘us-west-2’. 

We’ll also use the lookup 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")
}
Enter fullscreen mode

Exit fullscreen mode

Here, the instance_configs 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 lookup 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 lists to map

To show how this could be done, we’ll use local 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] }
}
Enter fullscreen mode

Exit fullscreen mode

In this configuration:

  • The environments variable is a list of environments, such as development, staging, and production.
  • The cidr_blocks variable contains the CIDR blocks for development, staging, and production in that specific order.
  • In the locals block, we use for loop to create a map 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 cidr_blocks.

Now, let’s create the VPCs by iterating over the local vpc_map using the for_each loop:

resource "aws_vpc" "vpc" {
  for_each = local.vpc_map
  cidr_block = each.value
  tags = {
    Name  = "${each.key}-vpc"
    Environment = each.key
  }
}
Enter fullscreen mode

Exit fullscreen mode

In this code:

  • Using the for_each 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 terraform apply, 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.
Enter fullscreen mode

Exit fullscreen mode

You can verify that the subnets for dev, staging, and prod 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 for_each 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 lookup, merge, flatten, toset, and tolist 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 local 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 vpc_config map variable are imported , along with the aws_access_key and aws_secret_key variables from the Terraform code and converted into the appropriate format (with vpc_config 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 terraform apply.



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 vpc_ids

These values are generated using the vpc_config 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 vpc_config 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 vpc_config 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 vpc_config 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 lookup and for_each 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 flatten merges nested lists into a single, flat list.



Source link
lol

By stp2y

Leave a Reply

Your email address will not be published. Required fields are marked *

No widgets found. Go to Widget page and add the widget in Offcanvas Sidebar Widget Area.