Preconditions and Postconditions

Terraform preconditions and postconditions allow you to define custom validation rules. Such rules ensure that certain conditions are met before and after the creation or modification of resources, data sources, and outputs. This provides a way of enforcing custom validation logic and returning informative error messages to your users.
The basic syntax for a condition block is:
<condition> {
    condition = <expression>
    error_message = <expression>
}
Let's break down this syntax:

Example 1: Precondition

Here is an example of a precondition that could be used to validate if the instance_type of an EC2 instance is set to a valid value:
resource "aws_instance" "example_ec2_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    lifecycle {
        precondition {
            condition = contains(["t2.micro", "t2.small", "t2.medium"],aws_instance.example_ec2_instance.instance_type)
            error_message = "Invalid instance type. Must be one of t2.micro, t2.small, or t2.medium"
        }
    }
}
Here, the precondition will check whether the instance_type of the example_ec2_instance resource is one of t2.micro, t2.small, or t2.medium. If this is not the case, then the given error message is displayed.

Example 2: Postcondition

Here is a typical example of a postcondition to verify the status of an EC2 instance running upon creation:
resource "aws_instance" "example_ec2_instance" {
    ami = "ami-0123456789"
    instance_type = "t2.micro"
    lifecycle {
        postcondition {
            condition = self.status == "running"
            error_message = "Instance is not running after creation"
        }
    }
}
In the above example, this postcondition will make sure the status of the resource example_ec2_instance is running when created. If this condition does not happen, then an error message will be shown.

Arguments

1. condition

The condition argument must be a Boolean expression whose result is either true or false, and it can refer to any other objects of the same module while it must not introduce circular dependencies, which means dependencies pointing back to each other. For post-conditions you can also use the self object if you want to access the attributes from the resource that is being created or modified. So you will be able to check the resource properties after its creation or modification.

2. error_message

error_message is a string expression that describes an error message which will be shown when this condition is not met. If a condition fails, the result of the error_message expression will be part of the resulting error message from Terraform. This allows you to provide a custom error message that can explain to the user why it failed and make it easier to understand and fix.

How do they work?

When are they checked?

When the condition is not dependent on unknown values, Terraform will validate custom conditions as early as possible. That is, if your condition within Terraform does not depend on any unknown or dynamically generated values, Terraform will check it at the planning stage of the deployment.
On the other hand, if a condition does depend on unknown values, Terraform delays the checking of the condition until the apply phase. This will be because these unknowns such as resource IDs, resource attributes or outputs, are only known after the resources have been created or updated.
For instance, consider the following example of a postcondition that we define on a null_resource resource, specifically to check its id. The problem is that this null_resource resource hasn't been built yet, and therefore we don't know in advance what its id will be.
resource "null_resource" "postcondition" {
    provisioner "local-exec" {
      command = "echo 'Postcondition'"
    }
    lifecycle {
        postcondition {
            condition = self.id == 123
            error_message = "Running while apply phase"
        }
    }
}
Terraform will perform the following actions:
    # null_resource.postcondition will be created
    + resource "null_resource" "postcondition" {
        + id = (known after apply)
    }
Plan: 1 to add, 0 to change, 0 to destroy.

Enter a value: yes

null_resource.postcondition: Creating...
null_resource.postcondition: Provisioning with 'local-exec'...
null_resource.postcondition: Executing: ["/bin/sh" "-c" "echo 'postcondition'"]
null_resource.postcondition: postcondition
null_resource.postcondition: Creation complete after 0s [id=4425172414869377883]
  | Error: Resource postcondition failed
  | self.id is "123"
  | Running while apply phase
The first apply of this Terraform configuration, terraform is going to provision the null_resource with the local-exec provisioner and the postcondition is going to be evaluated during apply since the id attribute is unknown at plan time.
If we run apply the configuration again, Terraform will refresh the state of the null_resource and check the postcondition in the plan phase since the id attribute is known. This is because Terraform reference the existing state of the null_resource to check the condition.
Terraform will perform the following actions:
null_resource.default: Refreshing state... [id=6572268843874077948]
| Error: Resource postcondition failed
| self.id is "123"
| Running while apply phase
In summary, custom conditions are evaluated as early as possible during the plan phase, Terraform postpones the evaluation to the apply phase if the condition depends on the values that will be unknown until after the resources have been provisioned or updated.

Precondition in output block

An output block in Terraform can have a precondition block, but not a postcondition block. The precondition block is similar in concept to input validation blocks. While input validation checked assumptions about the inputs, preconditions check guarantees on the outputs. Preconditions can also catch Terraform from saving an invalid output value in the statefile. And they can preserve a valid output value from the previous apply.
local {
    output = -10
}
output "availability_zone" {
    value = local.output
    description = "An example output value"
    precondition {
        condition = local.output > 0
        error_message = "The example output value must be greater than 0."
    }
}
When you run this Terraform configuration, Terraform will execute the output block and validate the precondition. Since the local.output is set to "-10", it does not meet the condition of greater than 0 and therefore Terraform will return the following error:
Terraform will perform the following actions:
| Error: Module output value precondition failed
| local.output is -10
| The example output value must be greater than 0.
NOTE: Postcondition doesn't supported in output block.

Precondition and Postcondition in Resources and Data Sources

In Terraform, resources and data sources can support preconditions or postconditions. However, this needs to be defined inside the resource and data source's lifecycle block
The Terraform precondition blocks are evaluated after existing count and for_each arguments have been evaluated. That way Terraform can evaluate the precondition separately for each instance, and the each.key, count.index and other related variables are available within the precondition block. For example,
resource "null_resource" "postcondition" {
    for_each = toset(["first-instance","second-instance","third-instance"])
    provisioner "local-exec" {
      command = "echo '${each.value}'"
    }
    lifecycle {
        precondition {
            condition = each.value == "first-instance"
            error_message = "Example error"
        }
    }
}
Terraform will perform the following actions:
| Resource precondition failed
| each.value is "second-instance"
| Example error

| Resource precondition failed
| each.value is "third-instance"
| Example error
In the above example, the precondition block will be evaluated after the evaluation of for_each expression. If the value of the current instance is not "first-instance", then Terraform provides the message "Example error" and will abort the plan or apply.

Related Pages

Feedback

Was this page helpful?