Guest Post: Meta-Arguments for Terraform

Meta-arguments come in handy in different situations, such as creating multiple, identical resources with the same name. In this blog, we'll focus on everything you need to know about them.

FEB 22, 2021 | SUMEET NINAWE
undefined

Note: This is a guest post by Sumeet Ninawe from Let’s Do Tech. Sumeet has multi cloud management platform experience where he used Terraform for the orchestration of customer deployments across major cloud providers like AWS, MS Azure and GCP. You can find him on Github, Twitter, and his website.

Meta-arguments are special constructs provided for resources. In our previous blog, “An Introduction to Terraform Templates,” we explained that the resource blocks are where we declare the actual cloud resources that are to be used on our infrastructure. Often, it becomes tricky to declare resources in a way that satisfies certain requirements.

As a side note--all of the examples in this blog use an “AWS provider,” however, there should be no significant changes in the way they are used with respect to other cloud providers. Before you read through this blog, we’d also recommend you check out our blog on “Terraform-AWS: Beginner’s Guide.”

This blog post will focus on meta-arguments for Terraform--and everything you need to know about them, ranging from providers to lifecycle.

To start--what are meta-arguments? Meta-arguments come in handy in situations like creating resources in the same cloud provider, but in different regions, or when creating multiple identical resources with different names, or when we have to declare implicit dependencies in places where Terraform is not able to identify the dependency itself.

Join the InfraCode community to learn more about meta-arguments and Terraform, as well as connect with other Terraform learners and experts.

Useful Meta-Argumentslink icon

Below, you can find a number of useful meta-arguments and explanations about how/when to use them.

PROVIDER and ALIAS:link icon

The provider meta-argument could be used when our infrastructure will have multiple provider configurations. If this meta-argument is not used, Terraform automatically maps the given resource to the default provider.

Let’s consider an example, where we have defined 2 resource blocks. The first resource block does not have the provider argument, therefore, the default configuration is picked. But the second resource block, where the argument <provider>= <provider>.<alias_name> has been defined selects the alternative configuration.

Even though it is possible to write multiple provider configurations, Terraform by default would pick the same provider for AWS for creating resources. This is where the meta-argument “alias” comes into the picture. Every provider configuration can be tagged with an alias and the value of this alias is used in our provider meta-argument in the resource block to specify different provider configurations for identical resources.

In the given example, let’s duplicate the AWS provider. Modified providers with an alias should look like below in provider.tf file.


provider "aws" {
  alias = "aws_west"
  region = var.region_west
}

provider "aws" {
  alias  = "aws_east"
  region = var.region_east
}

Note that we have also modified variables for the region to represent 2 different regions — west and east. Make the corresponding changes to variables.tf file below:

variable "region_west" {
  default     = "us-west-1"
  description = "AWS West Region"
}

variable "region_east" {
  default     = "us-east-1"
  description = "AWS East Region"
}

We’ll also have to edit the main.tf file, where the provider meta-argument will have to be used to refer to the provider alias:

resource "aws_instance" "demo" {
  provider      = aws.aws_west
  ami           = var.ami
  instance_type = var.type

  tags = {
    name = "Demo System"
  }
}


LIFECYCLE
link icon

Whenever a configuration is changed and applied, Terraform operates in the sequence below:

A lifecycle meta-argument can be used to alter this default behavior. These meta-arguments are used in resource blocks similar to provider meta-arguments. There are 3 lifecycle meta-argument settings:

1. create_before_destroy: Accidental loss of infrastructure when a change is applied is always possible. To avoid this, we can update this setting to true, in which case, Terraform will first create the new resource before destroying the older resource.

2. prevent_destroy: If set to true, any attempt to destroy the resource would result in an error. This is often useful in the case of those resources where reproduction can be expensive.

3. ignore_changes: This is a list typed meta-argument which specifies the attributes of a specific resource in the form of a list. During the update operations, there is often a situation where we’d like to prevent changes caused by external factors. In those cases, it becomes essential to declare the list of attributes that should not be changed without being reviewed.

By altering the default behavior of Terraform, we can put some protection in the form of lifecycle meta-arguments for confirmed and finalized resource blocks. This becomes very useful when we are setting up very complex infrastructure.


DEPENDS_ONlink icon

Generally, Terraform is aware of dependencies while performing the creation or modification of resources and takes care of the sequence by itself. However, in certain cases Terraform cannot deduce the implicit dependencies and just moves on to creating the resources parallelly if it doesn’t see any dependency.

Let’s take, for example, a configuration for two EC2 instances enclosed in a VPC. When this configuration is applied, Terraform automatically knows that the creation of VPC should be done before spinning the EC2 instances. In situations where dependencies are not so obvious, the depends_on meta-argument comes to the rescue. It’s a list type of argument that takes in the list of resource identifiers declared in the configuration.

COUNTlink icon

As the name suggests, the count can be assigned with a whole number, to represent that multiple numbers of the same resources need to be created using the block. In our example, let’s create 3 similar EC2 instances. In your main.tf file, add an attribute count to the resource aws_instance.demo, and assign a value of 3:

resource "aws_instance" "demo" {
  count = 3
  provider = aws.aws_west
  ami = var.ami
  instance_type = var.type

  tags = {
    name = "Demo System"
  }
}


By doing this, we let Terraform know that we need to create 3 EC2 instances with the same configuration. Save the file and execute ”terraform validate.” But this would create an error saying “Missing resource instance key.” Remember in our variables.tf file, we mentioned an output variable to output the id of the created resource. Because we asked Terraform to create 3 instances, it’s unclear which of the 3 should be printed.

To get around this problem, we would use a special expression called the “splat” expression. The ideal case here would be to run a for loop over the instance set and print out the ID property. A splat expression is a better way to do the same task with fewer lines of code. All you need to do: in the variables.tf file, replace the output value code to the below:

output "instance_id" {
  value = aws_instance.demo[*].id
}

Save this file and run ”terraform validate” to see if everything is okay. Once successful, go ahead and run ”terraform plan and apply” and check your AWS management console in the us-west-1 region a.k.a aws_west.

FOR_EACHlink icon

The for_each meta-argument, just like count, is used to create multiple similar cloud resources. However, keep in mind the following:

resource "aws_instance" "demo" {
  for_each = {
    fruit = "apple"
    vehicle = "car"
    continent = "Europe"
  }
  provider      = aws.aws_west
  ami           = var.ami
  instance_type = var.type

  tags = {
    name = "${each.key}: ${each.value}"
  }
}


If the “terraform template” is now applied, it would create an error for the output variable – “This object does not have an attribute named id”. Splat expressions work for the list type of variables. Since we have used map while setting the for_each meta-argument, we need to change the return value expression to for each, as below:

output "instance_id" {
  // value = aws_instance.demo[*].id
  value = [for b in aws_instance.demo : b.id]
}


As you can see, there are a number of useful meta-arguments for Terraform, each with its own set of rules and implications.

If you’re looking to learn more about meta-arguments, or Terraform in general, we invite you to join the InfraCode Slack to continue the conversation.