Understanding Meta-Arguments in Terraform

Knoldus Blog Audio
Reading Time: 4 minutes

Hello Readers, This blog is regarding Meta-Arguments in Terraform. These are some special constructs in Terraform which are available for Resource and Module Block. If you are new to Terraform and don’t know about what Resources in Terraform are, do checkout my Blog: Understanding IaC using Terraform.

Just a brief, the Resource block is used to define a resource/service that is actually going to be created by Terraform. For example, creating an ec2 instance by AWS provider, or Running a docker container using Docker provider.

Coming to Meta-Arguments, they helps us in achieving certain requirements within the resource block. I’ll be discussing all the available meta arguments in Terraform 1 by 1 with examples.
There are 5 Meta-Arguments in Terraform which are as follows:

  • depends_on
  • count
  • for_each
  • provider
  • lifecycle

depends_on

Terraform has a feature of identifying resource dependency. This means that Terraform internally knows the sequence in which the dependent resources needs to be created whereas the independent resources are created parallelly.

But in some scenarios, some dependencies are there that cannot be automatically inferred by Terraform. In these scenarios, a resource relies on some other resource’s behaviour but it doesn’t access any of the resource’s data in arguments.
For those dependencies, we’ll use depends_on meta-argument to explicitly define the dependency.

depends_on meta-argument must be a list of references to other resources in the same calling resource.

This argument is specified in resources as well as in modules (Terraform version 0.13+)

resource "aws_iam_role" "role" {
  name = "demo-role"
  assume_role_policy = "..."
}

resource "aws_iam_instance_profile" "instance-profile" {
  # Terraform can infer automatically that the role must be created first.
  role = aws_iam_role.role.name
}

resource "aws_iam_role_policy" "policy" {
  name   = "demo-policy"
  role   = aws_iam_role.role.name
  policy = jsonencode({
    "Statement" = [{
      "Action" = "s3:*",
      "Effect" = "Allow",
    }],
  })
}

resource "aws_instance" "ec2" {
  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  # Terraform can infer from this that the instance profile must
  # be created before the EC2 instance.
  iam_instance_profile = aws_iam_instance_profile.instance-profile

  # However, if software running in this EC2 instance needs access
  # to the S3 API in order to boot properly, there is also a "hidden"
  # dependency on the aws_iam_role_policy that Terraform cannot
  # automatically infer, so it must be declared explicitly:
  depends_on = [
    aws_iam_role_policy.policy
  ]
}

count

In Terraform, a resource block actually configures only one infrastructure object by default. If we want multiple resources with same configurations, we can define the count meta-argument. This will reduce the overhead of duplicating the resource block that number of times.

count require a whole number and will then create that resource that number of times. To identify each of them, we use the count.index which is the index number corresponds to each resource. The index ranges from 0 to count-1.

This argument is specified in resources as well as in modules (Terraform version 0.13+). Also, count meta-argument cannot be used with for_each.

resource "aws_instance" "server" {

  # create four similar EC2 instances
  count = 4

  ami           = "ami-a1b2c3d4"
  instance_type = "t2.micro"

  tags = {
    # Usage of count.index in providing a distinct name for every Instance
    Name = "Server ${count.index}"
  }
}

output "instance_id" {
  # Select all instance id using * in place of index value
  value = aws_instance.server[*].id
}

for_each

As specified in the count meta-argument, that the default behaviour of a resource is to create a single infrastructure object which can be overridden by using count, but there is one more flexible way of doing the same which is by using for_each meta argument.

The for_each meta argument accepts a map or set of strings. Terraform will create one instance of that resource for each member of that map or set. To identify each member of the for_each block, we have 2 objects:

  • each.key: The map key or set member corresponding to each member.
  • each.value: The map value corresponding to each member.

This argument is specified in resources (Terraform version 0.12.6) as well as in modules (Terraform version 0.13+)

## Example for map
resource "azurerm_resource_group" "rg" {
  for_each = {
    group_A = "eastus"
    group_B = "westus2"
  }
  name     = each.key
  location = each.value
}

## Example for set
resource "aws_iam_user" "accounts" {
  for_each = toset( ["Developer", "Tester", "Administrator", "Cloud-Architect"] )
  name     = each.key
}

provider

provider meta-argument specifies which provider to be used for a resource. This is useful when you are using multiple providers which is usually used when you are creating multi-region resources. For differentiating those providers, you use an alias field.
The resource then reference the same alias field of the provider as provider.alias to tell which one to use.

## Default Provider
provider "google" {
  region = "us-central1"
}

## Another Provider
provider "google" {
  alias  = "europe"
  region = "europe-west1"
}

## Referencing the other provider
resource "google_compute_instance" "example" {
  provider = google.europe
}

lifecycle

The lifecycle meta-argument defines the lifecycle for the resource. As per the resource behaviour, Terraform can do the following:

  • create a resource
  • destroy a resource
  • updated resource in place
  • update resource by deleting existing and create new

lifecycle is a nested block under resource that is used to customise that behaviour. Here are the following customisation that are available under lifecycle block

  • create_before_destroy: (Type: Bool)
    For resource, where Terraform cannot do an in place updation due to API limitation, its default behaviour is to destroy the resource first and then re create it. This can be changed by using this argument. It will first create the updated resource and then delete the old one.
  • prevent_destroy: (Type: Bool)
    This will prevent the resource from destroying. It is a useful measure where we want to prevent a resource against accidental replacement such as database instances.
  • ignore_changes: (Type: List(Attribute Names))
    By default, If Terraform detects any difference in the current state, it plans to update the remote object to match configuration. The ignore_changes feature is intended to be used when a resource is created with references to data that may change in the future, but should not affect said resource after its creation. It expects a list or map of values, whose updation will not recreate the resource. If we want all attributes to be passed here, we can simply use all.
## Ignore tag changes and won't recreate this resource if tags are updated
resource "aws_instance" "example" {
  lifecycle {
    ignore_changes = [
      tags,
    ]
  }
}

Conclusion

After reading this blog, you’ll must be familiar with what meta-arguments are and their use cases. You’d now aware of all the 5 meta-arguments and which one to use where. Just to summarise, we have 5 meta-arguments whose use case is mainly to provide some additional information for a resource or modules (only in some cases).
For any doubts/suggestions, you can contact me directly over yatharth.sharma@knoldus.com

Also, I would like to thank you for sticking to the end. If you like this blog, please do show your appreciation by giving thumbs-ups and share this blog and provide suggestions on how can I improve my future posts to suit your needs. Follow me to get updates on different technologies.

References

Terraform documentation