Meta Arguments

In Terraform, a meta-argument is a special kind of argument that can be used to customize the behavior of a resource block, Module block, and Data block.
Meta-arguments are the way to give you more control over how Terraform creates, updates, and destroys your infrastructure. Using them, you will be able to build complex and flexible configurations of the infrastructure.

Types of Meta Arguments Resource

Terraform provides several meta-arguments that can be used by all resource type. Here are the metaarguments used:

depends_on Meta-Argument

depends_on is a metaargument in Terraform that explicitly specifies dependencies of resources. It is used where Terraform cannot, by itself, automatically infer the dependency between resources.

Why do I need depends_on?

Terraform does an outstanding job automatically inferring dependency relationships between resources via their access of each other's data. That is to say: if resource B uses resource A's output to configure itself, terraform will automatically create A before B.
However, there are scenarios when one resource depends on the behaviour of another resource, where it does not use any data of that other resource in its configuration. In such cases, Terraform can't automatically compute the dependency between the two resources. That's where depends_on comes in place.

When would I use depends_on?

Now, suppose you have two resources, A and B. Resource B depends on the behaviour of resource A, but it doesn't, in fact, use any of the data or output from resource A in its configuration. That is to say, B requires A to have been created, or otherwise configured, prior to its own creation/configuration, but it does not reference A's data in any way.
In such a case, you will want to use depends_on to tell Terraform that B depends on A's behavior. This says, in effect, even though B did not use any data from A, Terraform needs to make sure A is created before B is created.

Example

You'll have two resources: aws_security_group, Resource A is "my_sg", creating a security group and aws_instance, Resource B named "my_server" that would create an EC2 instance and attach to it a security group created by Resource A.
resource "aws_security_group" "my_sg" {
    name = "Example Security Group"
    vpc_id = aws_vpc.example.id
    ingress {
        from_port = 80
        to_port = 80
        protocol = "tcp"
        cidr_blocks = ["0.0.0.0/0"]
    }
    egress {
        from_port = 0
        to_port = 0
        protocol = "-1"
        cidr_blocks = ["0.0.0.0/0"]
    }
}

resource "aws_instance" "example_ec2_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    tags = {
        Name = "my-ec2-instance"
    }
    depends_on = [aws_security_group.my_sg]
}
In this case, Resource B depends on the behavior of Resource A - the EC2 instance needs to know about the security group at its time of creation. However, the configuration for the EC2 instance does not use any output of the resource for the security group, such as its ID or ARN. In order for Terraform to create the security group before creating the EC2 instance, you would use depends_on to specify that Resource B depends on Resource A's behavior:

The Count Meta-Argument

In Terraform, when you define a resource block, it usually configures only one infrastructure object. Sometimes you need to manage multiples of similar objects, say, for instance, a fixed pool of compute instances, without writing separate blocks for each.
For this purpose, Terraform provides two meta-arguments: count and for_each. In this explanation, we will explain the count meta-argument.

How it works - Count?

Adding a count argument to a resource informs Terraform to create several instances of this resource. The value of count must be a whole number and indicates how many instances to create. Each instance has an associated infrastructure object, and each is created, updated or destroyed separately as this configuration is applied.
Here is an example using count on an AWS EC2 instance resource:
resource "aws_instance" "example_ec2_instance" {
    count = 4
    ami = "ami-0123456789"
    instance_type = "t2.micro"
}
Here, Terraform is going to create four identical EC2 instances with the same AMI and instance type. Each of them will have an infrastructure object associated with a distinct one, and each will separately be created, updated, or destroyed upon application of the configuration.

The count Object

When using the count meta-argument with a resource, Terraform also exposes an additional object, count, in expressions. This object can be used to customize the configuration of each instance that the count meta-argument creates.
The count object has only one attribute:
For instance, consider a resource block using the count meta-argument to create three instances of the compute instance.
resource "aws_instance" "server" {
    count = 3
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    tags = {
        Name = "Ec2-${count.index}"
    }
}
In this example, the count.index attribute would have the values 0, 1 and 2 for each of the three instances that is to be created. The resultant instance tag would be "Ec2-0", "Ec2-1", and "Ec2-2".

Referencing Instances of count

In Terraform, the count argument is used to create a resource multiple times. Terraform identifies multiple resources by assigning an index number for each instance, relying on the starting index 0.
Resources can be referred in Terraform in the following two ways:

The for_each Meta-Argument

The for_each metaargument is a way to create multiple instances of a resource block according to a collect of values. If a resource block includes a for_each argument whose value should be either a map or a set of strings, then Terraform will create an instance for each member of that map or set.
Each instance is handled independently, with its own infrastructural object associated with it. This means each instance will be independently created, updated, or destroyed based on the changes you've made when applying your Terraform configuration.
NOTE: A given resource block can't use the count and for_each at the same time.

Map Example

In the following example, we will use the for_each meta-argument with a map to provision multiples AWS S3 buckets. We will then define a collected set of buckets operating under different names and regions, and Terraform will simply create the buckets for each of them.
resource "aws_s3_bucket" "buckets" {
    bucket = each.key
    region = each.value
    for_each = tomap ({
        "my-bucket-1" = "us-east-1"
        "my-bucket-2" = "us-west-2"
        "my-bucket-3" = "eu-east-1"
    })
}
The tomap() here is a Terraform function that converts a list of key-value pairs into a map. The for_each meta-argument will step through the map, creating one instance of the aws_s3_bucket resource for each key-value pair. The each.key and each.value expressions are used to access the key and value of each pair, respectively.

Set Example

Here is an example where for_each is used on a set of strings to create many AWS RDS:
resource "aws_db_instance" "database" {
    for_each = toset ([ "database-1", "database-2", "database-3" )]
    allocated_storage = each.key
    engine = "mysql"
    instance_class = "db.t2.micro"
    db_name = each.key
    username = "myuser"
    password = "mypassword"
}
Here, toset() is a Terraform function that takes a list of strings in and returns a set. The for_each meta-argument will iterate over that set and create one instance of the aws_db_instance resource for every string in that set. Finally, the each.key expression is used to access the string value of each item of that set using the db_names variable as the database name.

The each Object

When you use the for_each inside of a Terraform block, you get this special object called each inside that block. So, it can be used to modify the configuration of each instance.
Each object has only two attribute:
Following is an example using AWS EC2:
resource "aws_instance" "instance" {
    for_each = tomap ({
        "dev" = "t2.micro"
        "prod" = "t2.micro"
    })
    ami = each.key
    instance_type = each.value
    tags = {
        Name = "Instance ${each.key} with type ${each.value}"
    }
}
In the above example, Terraform will create two instances of the aws_instance resource: one for "dev" and one for "prod". Within this block, the each object is available, whose properties can be used to configure each instance individually.
In the "dev" instance, each.key would be "dev" and each.value would be "t2.micro". Thus, the instance_type attribute is to be set to "t2.micro", and the tags.Name is to be "Instance dev with type t2.micro". Wherein, for an instance "prod", each.key will be "prod" and each.value will be "t2.large". So, instance_type is set to "t2.large" and tags.Name will set to "Instance prod with type t2.large".

Referencing Resource Blocks and Instances of for_each

When you attach a for_each to a Terraform block, Terraform will make several repetitions of that resource. In order to distinguish between them, Terraform uses the map key (or set of members) from the value provided to for_each.
There are two ways to reference resources in Terraform:

Chaining for_each Between Resources

The for_each meta-argument is a powerful feature in Terraform. It allows one to create resources multiple times, based on a map or set of values. When using resource, the for_each returns a map of objects where each key represents an individual instance and the respective value represents the attributes of that instance.
You could establish one-to-one relationships among resources by using the output from one for_each block as the input for another for_each block. This approach is referred to as "Chaining for_each between resources."

Example: Creating EC2 Instances and Elastic IP Addresses

Consider a scenario in which we need to create multiple EC2 instances and assign an Elastic IP address uniquely to each of the created instances. One can use for_each for creating EC2 instances, then make the output of that block as an input for another for_each block creating Elastic IP addresses.
resource "aws_instance" "instance" {
    for_each = tomap ({
        "t2.micro" = {
            "ami" = "ami-abc123"
            "instance_type" = "t2-micro"
        },
        "t2.small" = {
            "ami" = "ami-def456"
            "instance_type" = "t2-small"
        }
    })
    ami = each.value.ami
    instance_type = each.value.instance_type
    tags = {
        Name = "Instance with type ${each.key}"
    }
}
The above code creates a map of objects that contain two keys: "t2.micro" and "t2.small". Each of these keys here represents an individual EC2 instance, and the corresponding value represents the attributes of that instance.
The output of the above block is a map of objects, which looks like this:
{ 
    "t2.micro" = {
        "arn" = "arn:aws:ec2:ap-southeast-2:accountid:instance/i-id"
        "id" = "i-04f36f59dc445df86115ec"
        "availability_zone" = "ap-southeast-2a"
        "cpu_core_count" = 1
        "cpu_threads_per_core" = 2
        "ebs_block_device" = []
        "ami" = "ami-abc123"
        "instance_type" = "t2.micro"
    },

    "t2.small" = {
        "arn" = "arn:aws:ec2:us-east-1:accountid:instance/i-id"
        "id" = "i-8738673ddv3t381h63y765"
        "availability_zone" = "ap-southeast-2b"
        "cpu_core_count" = 1
        "cpu_threads_per_core" = 2
        "ebs_block_device" = []
        "ami" = "ami-def456"
        "instance_type" = "t2.small"
    }
}
Now we can use this output to be the input for another for_each block that creates the Elastic IP addresses.
resource "aws_instance" "instance" {
    for_each = aws_instance.instance
    instance_type = each.value.id
}
Here, the for_each block is getting its input from the output of the previous block where each value ID refers to the IDs of each EC2 instance created by the previously block, and an AWS EIP resource will create an Elastic IP address for each one.

Limitations on values used in for_each

Values to be known in advance

When using for_each with a map or set of values, the keys of the map, or all the values in case of a set of strings, must be known values. That is, values present in for_each can't be empty during the Terraform configuration apply phase. If you try to use unknown values, you get an error message indicating that for_each has dependencies that cannot be determined before apply.

Sensitive Values Not Allowed

Sensitive input variables, sensitive outputs, sensitive resource attributes are particularly sensitive values that cannot be used as an argument for for_each. The value used in for_each is to identify the resource instance and will always be disclosed in UI output during plan which can potentially reveal sensitive information. Using sensitive values as an argument to for_each will result in an error.

The provider Meta-Argument

The provider meta-argument in Terraform is special and is used to choose which provider configuration to use with a particular resource. It overrides Terraform's default behavior, which looks for a provider configuration with a matching resource type name.
By default, Terraform will automatically select a provider configuration based on the name of the resource type. For instance, if you declare the aws_instance resource, it will automatically select the default AWS provider configuration. However, in some cases, you may wish to use a different provider configuration.
The provider meta-argument is available to set explicitly which provider configuration is used for a resource. It takes the form of adding the provider name and alias (when using multiple configurations of the one provider type) to the resource declaration.

Provider Syntax

The syntax for using the provider meta-argument is:
<PROVIDER>.<ALIAS>

Example

Suppose you have two configurations of provider for AWS Cloud:
provider "aws" {
    region = "us-east-1"
}
provider "aws" {
    alias = "europeregion"
    region = "eu-west-1"
}
Now you want to create an AWS instance, using the alternative "europeregion" configuration and not the default configuration. You can achieve that by adding the provider meta-argument to the aws_instance resource declaration:
resource "aws_instance" "example_ec2_instance" {
    provider = aws.europeregion
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    tags = {
        Name = "my-ec2-instance"
    }
}
Here, the provider meta-argument has been used to specify that the aws_instance resource should use this "europeregion" configuration instead of using the default configuration. In other words, the EC2 instance will be created in the "eu-west-1" region as defined in "europeregion".

The Lifecycle Meta-Argument

In Terraform, a lifecycle block is a nested block that can only be used inside a resource block. This block allows you to customize the behavior of a resource during its lifecycle.
There are three phases in a resource's lifecycle: create, update, and destroy. The Lifecycle block allows you to tell Terraform how to handle resources in each of these three phases.
NOTE: When using Terraform, one should remember that the Lifecycle block can only be used within Resource Blocks.

Lifecycle arguments

When using a lifecycle block in a resource block with Terraform, one is able to specify several arguments to control what happens during the lifecycle of that resource. The available arguments inside a lifecycle block are :

1. create_before_destroy

By default, if Terraform needs to do an update for a resource argument which cannot be updated in-place due to some limitation of the remote API, it follows a behavior termed destroy-and-recreate where it deletes the existing resource object and then creates a new resource object with the updated configured arguments.
The create_before_destroy meta-argument reverses this default behavior. If set to true, Terraform will first create a new resource object with the updated configured arguments. If the new object is created, then Terraform destroys the old resource object. Such a behavior allows the new object to be created well before the old one is destroyed, hence useful in certain situations where such downtime must be minimized, or where the resource object must always remain available.

Example

resource "aws_instance" "example_ec2_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    lifecycle = {
        create_before_destroy = true
    }
}
In this case, if either the ami or instance_type arguments were changed, Terraform would create a new AWS instance with the new arguments before destroying the old one. This would avoid the old instance being destroyed before a new one was provided, which could cause potential downtime or other problems.
When you set create_before_destroy on a resource, Terraform will automatically cascade the same behavior to all resources depending on it, and it records that within the statefile. For instance, consider having two resources Resource A and Resource B where Resource A depends on Resource B. You set the create_before_destroy on Resource A to True but do not on Resource B. Terraform will automatically enable create_before_destroy on Resource B even though you never set it explicitly. It is because Resource A depends on Resource B and you can't override the create_before_destroy to false on Resource B.
resource "aws_instance" "instance_a" {
    ami = "ami-0123456789"
    instance_type = "t2.small"
    lifecycle = {
        create_before_destroy = true
    }
    depends_on = [aws_instance.instance_b]
}
resource "aws_instance" "instance_b" {
    ami = "ami-7836726816"
    instance_type = "t2.small"
}
NOTE: When create_before_destroy is true, Terraform will skip running the destroy provisioners for resource.

2. prevent_destroy

The prevent_destroy meta-argument of Terraform is used to avoid the accidental destruction of infrastructure objects, which may be expensive or hard to reproduce, for example, database instances.
Setting the prevent_destroy=true on a Terraform resource provides an instruction to Terraform to reject any plan that would eventually destroy the object of infrastructure associated with it. Therefore, if a user tries to run terraform apply or terraform destroy that would delete such an object, Terraform stops the operation and returns an error message.
However, if you remove the complete resource block from your Terraform configuration then the prevent_destroy setting is removed too and Terraform will no longer know about the protection it will permit the destroy of the remote object to go.

Example: Protecting a Database Instance with prevent_destroy

For example, let's say we have a critical database instance that holds very sensitive data regarding our application. We want to make certain it is never deleted accidentally, since that would incur very serious data loss and downtime.
One of the attributes that can be set is the Terraform prevent_destroy meta-argument. A sample for that configuration is as follows.
resource "aws_db_instance" "database" {
    engine = "mysql"
    instance_class = "db.t2.micro"
    db_name = "testing-database"
    username = "myuser"
    password = "mypassword"
    allocated_storage = 10
    lifecycle = {
        prevent_destroy = true
    }
}
Here we'll create an AWS database instance with the aws_db_instance resource. On the resource, we have one meta-argument, prevent_destroy, which is set to true. That would have Terraform refuse to apply a plan if it includes the destruction of this database instance.

3. ignore_changes

By default, Terraform will compare the statefile (remote state) with your configuration files (desired state). When it finds any difference, it creates a plan to update the remote infrastructure object to reflect your configuration. In other words, it will keep your infrastructure in sync with your desired state.
But in some cases, you will create a resource with references to data that can change in the future but should not change the resource itself. For example, say you have a virtual machine that references a dynamic IP address that periodically changes. In this case, each time you run terraform apply, Terraform would detect that the IP address attribute has changed compared to state file, and plan to update the virtual machine - even though this is a change to an ignorable attribute that doesn't impact the configuration of the resource itself.
The purpose of the ignore_changes meta-argument is to deal with this problem. It takes a list of resource attributes that Terraform should always ignore during terraform plan operations for the remote object associated with this resource. In other words, changes to these attributes won't be detected and Terraform won't plan updates to these resources unnecessarily.

Referencing Attributes

The ignore_changes argument value must be a List of attribute references. When referencing attributes, you can use its relative address within the resource. For example, if you have a tags attribute that is a map, you can reference a specific tag using tags["Name"]. If you have a list attribute, then you can reference a specific element using list[0].

Example: Ignoring changes to tags and ami on an AWS Instance

resource "aws_instance" "ignore_changes_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.large"
    tags = {
        env = "production"
    }
    lifecycle = {
        ignore_changes = [
            tags["env"]
            ami
        ]
    }
}
In the example above, Terraform will neglect the changes of env tag and ami attribute. Thus, in the case of modification of env tag, or value of ami attribute, Terraform won't detect them and won't plan update of instance.

Using all to ignore all attributes

Rather than listing attributes to ignore, the special keyword all can be used to direct Terraform to ignore all resource attributes. So terraform will create and delete the object on the remote system, but will never update it.
resource "aws_instance" "ignore_all_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.large"
    tags = {
        env = "production"
    }
    lifecycle = {
        ignore_changes = [
            all
        ]
    }
}
The above example tells terraform to ignore all of the attributes of the aws_instance resource including ami, instance_type, tags, and any others which may be defined.

Limitations with ignore_changes

4. replace_triggered_by

replace_triggered_by argument in Terraform allows users to instruct the Terraform to replace the resource whenever any of the referenced items change. Any of the listed referenced items in this replace_triggered_by argument get updated, then Terraform will destroy the existing resource that has this replace_triggered_by argument and will create a new one.
The value of the this argument must be a List of resource or attribute references.

Types of referenced items

There are three types of referenced items to be used in this argument:
resource "aws_instance" "machine" {
    ami = "ami-0123456789"
    instance_type = "t2.large"
}

resource "aws_s3_bucket" "bucket" {
    bucket = "my-bucket"
    acl = "private"
    lifecycle {
        replace_triggered_by = [aws_instance.machine]
    }
}
In the example, the replace_triggered_by attribute notifies changes of the aws_instance resource to the aws_s3_bucket resource. Therefore, in case any of the attributes from the aws_instance resource are updated, it will destroy and re-create the aws_s3_bucket resource.
resource "aws_instance" "machine" {
    count = 2
    ami = "ami-0123456789"
    instance_type = "t2.large"
}

resource "aws_s3_bucket" "bucket" {
    bucket = "my-bucket"
    acl = "private"
    lifecycle {
        replace_triggered_by = [aws_instance.machine[0]]
    }
}
In the above example, the aws_s3_bucket resource has a replace_triggered_by attribute referring to the first instance of the aws_instance resource, i.e., aws_instance.machine[0]. This implies that when the first instance of the aws_instance resource is replaced, the aws_s3_bucket resource will be replaced, too.
resource "aws_instance" "machine" {
    count = 2
    ami = "ami-0123456789"
    instance_type = "t2.large"
}

resource "aws_s3_bucket" "bucket" {
    bucket = "my-bucket"
    acl = "private"
    lifecycle {
        replace_triggered_by = [aws_instance.machine.ami]
    }
}
In this example, the aws_s3_bucket resource example has a replace_triggered_by attribute that references the ami attribute of the aws_instance resource example. This means that if the ami attribute of the aws_instance resource is updated, the aws_s3_bucket resource will also be updated.

Related Pages

Feedback

Was this page helpful?