github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/website/docs/language/expressions/custom-conditions.mdx (about) 1 --- 2 page_title: Custom Condition Checks - Configuration Language 3 description: >- 4 Check custom requirements for variables, outputs, data sources, and resources and provide better error messages in context. 5 --- 6 7 # Custom Condition Checks 8 9 You can create conditions that produce custom error messages for several types of objects in a configuration. For example, you can add a condition to an input variable that checks whether incoming image IDs are formatted properly. Custom conditions can capture assumptions, helping future maintainers understand the configuration design and intent. They also return useful information about errors earlier and in context, helping consumers more easily diagnose issues in their configurations. 10 11 > **Hands On:** Try the [Validate Modules with Custom Conditions](https://learn.hashicorp.com/tutorials/terraform/custom-conditions?in=terraform/configuration-language) tutorial. 12 13 This page explains the following: 14 - Creating [validation conditions](#input-variable-validation) for input variables (Terraform v0.13.0 and later) 15 - Creating [preconditions and postconditions](#preconditions-and-postconditions) for resources, data sources, and outputs (Terraform v1.2.0 and later) 16 - Writing effective [condition expressions](#condition-expressions) and [error messages](#error-messages) 17 - When Terraform [evaluates custom conditions](#conditions-checked-only-during-apply) during the plan and apply cycle 18 19 20 ## Input Variable Validation 21 22 -> **Note:** Input variable validation is available in Terraform v0.13.0 and later. 23 24 Add one or more `validation` blocks within the `variable` block to specify custom conditions. Each validation requires a [`condition` argument](#condition-expressions), an expression that must use the value of the variable to return `true` if the value is valid, or `false` if it is invalid. The expression can refer only to the containing variable and must not produce errors. 25 26 If the condition evaluates to `false`, Terraform produces an [error message](#error-messages) that includes the result of the `error_message` expression. If you declare multiple validations, Terraform returns error messages for all failed conditions. 27 28 The following example checks whether the AMI ID has valid syntax. 29 30 ```hcl 31 variable "image_id" { 32 type = string 33 description = "The id of the machine image (AMI) to use for the server." 34 35 validation { 36 condition = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-" 37 error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"." 38 } 39 } 40 ``` 41 42 If the failure of an expression determines the validation decision, use the [`can` function](/language/functions/can) as demonstrated in the following example. 43 44 ```hcl 45 variable "image_id" { 46 type = string 47 description = "The id of the machine image (AMI) to use for the server." 48 49 validation { 50 # regex(...) fails if it cannot find a match 51 condition = can(regex("^ami-", var.image_id)) 52 error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"." 53 } 54 } 55 ``` 56 57 58 ## Preconditions and Postconditions 59 60 -> **Note:** Preconditions and postconditions are available in Terraform v1.2.0 and later. 61 62 Use `precondition` and `postcondition` blocks to create custom rules for resources, data sources, and outputs. 63 64 Terraform checks a precondition _before_ evaluating the object it is associated with and checks a postcondition _after_ evaluating the object. Terraform evaluates custom conditions as early as possible, but must defer conditions that depend on unknown values until the apply phase. Refer to [Conditions Checked Only During Apply](#conditions-checked-only-during-apply) for more details. 65 66 ### Usage 67 68 Each precondition and postcondition requires a [`condition` argument](#condition-expressions). This is an expression that must return `true` if the conditition is fufilled or `false` if it is invalid. The expression can refer to any other objects in the same module, as long as the references do not create cyclic dependencies. Resource postconditions can also use the [`self` object](#self-object) to refer to attributes of each instance of the resource where they are configured. 69 70 If the condition evaluates to `false`, Terraform will produce an [error message](#error-messages) that includes the result of the `error_message` expression. If you declare multiple preconditions or postconditions, Terraform returns error messages for all failed conditions. 71 72 The following example uses a postcondition to detect if the caller accidentally provided an AMI intended for the wrong system component. 73 74 ``` hcl 75 data "aws_ami" "example" { 76 id = var.aws_ami_id 77 78 lifecycle { 79 # The AMI ID must refer to an existing AMI that has the tag "nomad-server". 80 postcondition { 81 condition = self.tags["Component"] == "nomad-server" 82 error_message = "tags[\"Component\"] must be \"nomad-server\"." 83 } 84 } 85 } 86 ``` 87 88 #### Resources and Data Sources 89 90 The `lifecycle` block inside a `resource` or `data` block can include both `precondition` and `postcondition` blocks. 91 92 - Terraform evaluates `precondition` blocks after evaluating existing `count` and `for_each` arguments. This lets Terraform evaluate the precondition separately for each instance and then make `each.key`, `count.index`, etc. available to those conditions. Terraform also evaluates preconditions before evaluating the resource's configuration arguments. Preconditions can take precedence over argument evaluation errors. 93 - Terraform evaluates `postcondition` blocks after planning and applying changes to a managed resource, or after reading from a data source. Postcondition failures prevent changes to other resources that depend on the failing resource. 94 95 In most cases, we do not recommend including both a `data` block and a `resource` block that both represent the same object in the same configuration. Doing so can prevent Terraform from understanding that the `data` block result can be affected by changes in the `resource` block. However, when you need to check a result of a `resource` block that the resource itself does not directly export, you can use a `data` block to check that object safely as long as you place the check as a direct `postcondition` of the `data` block. This tells Terraform that the `data` block is serving as a check of an object defined elsewhere, allowing Terraform to perform actions in the correct order. 96 97 #### Outputs 98 99 An `output` block can include a `precondition` block. 100 101 Preconditions can serve a symmetrical purpose to input variable `validation` blocks. Whereas input variable validation checks assumptions the module makes about its inputs, preconditions check guarantees that the module makes about its outputs. You can use preconditions to prevent Terraform from saving an invalid new output value in the state. You can also use them to preserve a valid output value from the previous apply, if applicable. 102 103 Terraform evaluates output value preconditions before evaluating the `value` expression to finalize the result. Preconditions can take precedence over potential errors in the `value` expression. 104 105 ### Examples 106 107 The following example shows use cases for preconditions and postconditions. The preconditions and postconditions declare the following assumptions and guarantees. 108 109 - **The AMI ID must refer to an AMI that contains an operating system for the 110 `x86_64` architecture.** The precondition would detect if the caller accidentally built an AMI for a different architecture, which may not be able to run the software this virtual machine is intended to host. 111 112 - **The EC2 instance must be allocated a public DNS hostname.** In Amazon Web Services, EC2 instances are assigned public DNS hostnames only if they belong to a virtual network configured in a certain way. The postcondition would detect if the selected virtual network is not configured correctly, prompting the user to debug the network settings. 113 114 - **The EC2 instance will have an encrypted root volume.** The precondition ensures that the root volume is encrypted, even though the software running in this EC2 instance would probably still operate as expected on an unencrypted volume. This lets Terraform produce an error immediately, before any other components rely on the new EC2 instance. 115 116 ```hcl 117 118 resource "aws_instance" "example" { 119 instance_type = "t2.micro" 120 ami = "ami-abc123" 121 122 lifecycle { 123 # The AMI ID must refer to an AMI that contains an operating system 124 # for the `x86_64` architecture. 125 precondition { 126 condition = data.aws_ami.example.architecture == "x86_64" 127 error_message = "The selected AMI must be for the x86_64 architecture." 128 } 129 130 # The EC2 instance must be allocated a public DNS hostname. 131 postcondition { 132 condition = self.public_dns != "" 133 error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled." 134 } 135 } 136 } 137 138 data "aws_ebs_volume" "example" { 139 # Use data resources that refer to other resources to 140 # load extra data that isn't directly exported by a resource. 141 # 142 # Read the details about the root storage volume for the EC2 instance 143 # declared by aws_instance.example, using the exported ID. 144 145 filter { 146 name = "volume-id" 147 values = [aws_instance.example.root_block_device.volume_id] 148 } 149 150 # Whenever a data resource is verifying the result of a managed resource 151 # declared in the same configuration, you MUST write the checks as 152 # postconditions of the data resource. This ensures Terraform will wait 153 # to read the data resource until after any changes to the managed resource 154 # have completed. 155 lifecycle { 156 # The EC2 instance will have an encrypted root volume. 157 postcondition { 158 condition = self.encrypted 159 error_message = "The server's root volume is not encrypted." 160 } 161 } 162 } 163 164 output "api_base_url" { 165 value = "https://${aws_instance.example.private_dns}:8433/" 166 } 167 ``` 168 169 ### Choosing Between Preconditions and Postconditions 170 171 You can often implement a validation check as either a postcondition of the resource producing the data or as a precondition of a resource or output value using the data. To decide which is most appropriate, consider whether the check is representing either an assumption or a guarantee. 172 173 #### Use Preconditions for Assumptions 174 175 An assumption is a condition that must be true in order for the configuration of a particular resource to be usable. For example, an `aws_instance` configuration can have the assumption that the given AMI will always be configured for the `x86_64` CPU architecture. 176 177 We recommend using preconditions for assumptions, so that future maintainers can find them close to the other expressions that rely on that condition. This lets them understand more about what that resource is intended to allow. 178 179 #### Use Postconditions for Guarantees 180 181 A guarantee is a characteristic or behavior of an object that the rest of the configuration should be able to rely on. For example, an `aws_instance` configuration can have the guarantee that an EC2 instance will be running in a network that assigns it a private DNS record. 182 183 We recommend using postconditions for guarantees, so that future maintainers can find them close to the resource configuration that is responsible for implementing those guarantees. This lets them more easily determine which behaviors they should preserve when changing the configuration. 184 185 #### Additional Decision Factors 186 187 You should also consider the following questions when creating preconditions and postconditions. 188 189 - Which resource or output value would be most helpful to report in the error message? Terraform will always report errors in the location where the condition was declared. 190 - Which approach is more convenient? If a particular resource has many dependencies that all make an assumption about that resource, it can be pragmatic to declare that once as a post-condition of the resource, rather than declaring it many times as preconditions on each of the dependencies. 191 - Is it helpful to declare the same or similar conditions as both preconditions and postconditions? This can be useful if the postcondition is in a different module than the precondition because it lets the modules verify one another as they evolve independently. 192 193 194 ## Condition Expressions 195 196 Input variable validation, preconditions, and postconditions all require a `condition` argument. This is a boolean expression that should return `true` if the intended assumption or guarantee is fulfilled or `false` if it does not. 197 198 You can use any of Terraform's built-in functions or language operators 199 in a condition as long as the expression is valid and returns a boolean result. The following language features are particularly useful when writing condition expressions. 200 201 ### Logical Operators 202 203 Use the logical operators `&&` (AND), `||` (OR), and `!` (NOT) to combine multiple conditions together. 204 205 ```hcl 206 condition = var.name != "" && lower(var.name) == var.name 207 ``` 208 209 You can also use arithmetic operators (e.g. `a + b`), equality operators (eg., `a == b`) and comparison operators (e.g., `a < b`). Refer to [Arithmetic and Logical Operators](/language/expressions/operators) for details. 210 211 ### `contains` Function 212 213 Use the [`contains` function](/language/functions/contains) to test whether a given value is one of a set of predefined valid values. 214 215 ```hcl 216 condition = contains(["STAGE", "PROD"], var.environment) 217 ``` 218 219 ### `length` Function 220 221 Use the [`length` function](/language/functions/length) to test a collection's length and require a non-empty list or map. 222 223 ```hcl 224 condition = length(var.items) != 0 225 ``` 226 This is a better approach than directly comparing with another collection using `==` or `!=`. This is because the comparison operators can only return `true` if both operands have exactly the same type, which is often ambiguous for empty collections. 227 228 ### `for` Expressions 229 230 Use [`for` expressions](/language/expressions/for) in conjunction with the functions `alltrue` and `anytrue` to test whether a condition holds for all or for any elements of a collection. 231 232 ```hcl 233 condition = alltrue([ 234 for v in var.instances : contains(["t2.micro", "m3.medium"], v.type) 235 ]) 236 ``` 237 238 ### `can` Function 239 240 Use the [`can` function](/language/functions/can) to concisely use the validity of an expression as a condition. It returns `true` if its given expression evaluates successfully and `false` if it returns any error, so you can use various other functions that typically return errors as a part of your condition expressions. 241 242 For example, you can use `can` with `regex` to test if a string matches a particular pattern because `regex` returns an error when given a non-matching string. 243 244 ```hcl 245 condition = can(regex("^[a-z]+$", var.name)) 246 ``` 247 248 You can also use `can` with the type conversion functions to test whether a value is convertible to a type or type constraint. 249 250 ```hcl 251 # This remote output value must have a value that can 252 # be used as a string, which includes strings themselves 253 # but also allows numbers and boolean values. 254 condition = can(tostring(data.terraform_remote_state.example.outputs["name"])) 255 ``` 256 257 ```hcl 258 # This remote output value must be convertible to a list 259 # type of with element type. 260 condition = can(tolist(data.terraform_remote_state.example.outputs["items"])) 261 ``` 262 263 You can also use `can` with attribute access or index operators to test whether a collection or structural value has a particular element or index. 264 265 ```hcl 266 # var.example must have an attribute named "foo" 267 condition = can(var.example.foo) 268 ``` 269 270 ```hcl 271 # var.example must be a sequence with at least one element 272 condition = can(var.example[0]) 273 # (although it would typically be clearer to write this as a 274 # test like length(var.example) > 0 to better represent the 275 # intent of the condition.) 276 ``` 277 278 ### `self` Object 279 280 Use the `self` object in postcondition blocks to refer to attributes of the instance under evaluation. 281 282 ```hcl 283 resource "aws_instance" "example" { 284 instance_type = "t2.micro" 285 ami = "ami-abc123" 286 287 lifecycle { 288 postcondition { 289 condition = self.instance_state == "running" 290 error_message = "EC2 instance must be running." 291 } 292 } 293 } 294 ``` 295 296 ### `each` and `count` Objects 297 298 In blocks where [`for_each`](/language/meta-arguments/for_each) or [`count`](/language/meta-arguments/count) are set, use `each` and `count` objects to refer to other resources that are expanded in a chain. 299 300 ```hcl 301 variable "vpc_cidrs" { 302 type = set(string) 303 } 304 305 data "aws_vpc" "example" { 306 for_each = var.vpc_cidrs 307 308 filter { 309 name = "cidr" 310 values = [each.key] 311 } 312 } 313 314 resource "aws_internet_gateway" "example" { 315 for_each = data.aws_vpc.example 316 vpc_id = each.value.id 317 318 lifecycle { 319 precondition { 320 condition = data.aws_vpc.example[each.key].state == "available" 321 error_message = "VPC ${each.key} must be available." 322 } 323 } 324 } 325 ``` 326 327 ## Error Messages 328 329 Input variable validations, preconditions, and postconditions all must include the `error_message` argument. This contains the text that Terraform will include as part of error messages when it detects an unmet condition. 330 331 ``` 332 Error: Resource postcondition failed 333 334 with data.aws_ami.example, 335 on ec2.tf line 19, in data "aws_ami" "example": 336 72: condition = self.tags["Component"] == "nomad-server" 337 |---------------- 338 | self.tags["Component"] is "consul-server" 339 340 The selected AMI must be tagged with the Component value "nomad-server". 341 ``` 342 343 The `error_message` argument can be any expression that evaluates to a string. 344 This includes literal strings, heredocs, and template expressions. You can use the [`format` function](/language/functions/format) to convert items of `null`, `list`, or `map` types into a formatted string. Multi-line 345 error messages are supported, and lines with leading whitespace will not be 346 word wrapped. 347 348 We recommend writing error messages as one or more full sentences in a 349 style similar to Terraform's own error messages. Terraform will show the 350 message alongside the name of the resource that detected the problem and any 351 external values included in the condition expression. 352 353 ## Conditions Checked Only During Apply 354 355 Terraform evaluates custom conditions as early as possible. 356 357 Input variable validations can only refer to the variable value, so Terraform always evaluates them immediately. When Terraform evaluates preconditions and postconditions depends on whether the value(s) associated with the condition are known before or after applying the configuration. 358 359 - **Known before apply:** Terraform checks the condition during the planning phase. For example, Terraform can know the value of an image ID during planning as long as it is not generated from another resource. 360 - **Known after apply:** Terraform delays checking that condition until the apply phase. For example, AWS only assigns the root volume ID when it starts an EC2 instance, so Terraform cannot know this value until apply. 361 362 During the apply phase, a failed _precondition_ 363 will prevent Terraform from implementing planned actions for the associated resource. However, a failed _postcondition_ will halt processing after Terraform has already implemented these actions. The failed postcondition prevents any further downstream actions that rely on the resource, but does not undo the actions Terraform has already taken. 364 365 Terraform typically has less information during the initial creation of a 366 full configuration than when applying subsequent changes. Therefore, Terraform may check conditions during apply for initial creation and then check them during planning for subsequent updates. 367