github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/website/docs/language/expressions/custom-conditions.mdx (about)

     1  ---
     2  page_title: Custom Conditions - 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 Conditions
     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 Infrastructure Using Checks](/terraform/tutorials/configuration-language/checks) tutorial to learn how to use `check` blocks. Try the [Validate Modules with Custom Conditions](/terraform/tutorials/configuration-language/custom-conditions) tutorial to learn how to use other custom conditions.
    12  
    13  This page explains the following:
    14    - Creating checks with [assertions](#checks-with-assertions) to verify your infrastructure as a whole (Terraform v1.5.0 and later)
    15    - Creating [validation conditions](#input-variable-validation) for input variables (Terraform v0.13.0 and later)
    16    - Creating [preconditions and postconditions](#preconditions-and-postconditions) for resources, data sources, and outputs (Terraform v1.2.0 and later)
    17    - Writing effective [condition expressions](#condition-expressions) and [error messages](#error-messages)
    18    - When Terraform [evaluates custom conditions](#conditions-checked-only-during-apply) during the plan and apply cycle
    19  
    20  ## Selecting a Custom Condition for your use case
    21  
    22  Terraform's different custom conditions are best suited to various situations. Use the following broad guidelines to select the best custom condition for your use case:
    23  1. [Check blocks with assertions](#checks-with-assertions) validate your infrastructure as a whole. Additionally, check blocks do not prevent or block the overall execution of Terraform operations.
    24  1. [Validation conditions](#input-variable-validation) or [output postconditions](#preconditions-and-postconditions) can ensure your configuration's inputs and outputs meet specific requirements.
    25  1. Resource [preconditions and postconditions](#preconditions-and-postconditions) can validate that Terraform produces your configuration with predictable results.
    26  
    27  For more information on when to use certain custom conditions, see [Choosing Between Preconditions and Postconditions](#choosing-between-preconditions-and-postconditions) and [Choosing Checks or Other Custom Conditions](/terraform/language/checks#choosing-checks-or-other-custom-conditions).
    28  
    29  
    30  ## Input Variable Validation
    31  
    32  -> **Note:** Input variable validation is available in Terraform v0.13.0 and later.
    33  
    34  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.
    35  
    36  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.
    37  
    38  The following example checks whether the AMI ID has valid syntax.
    39  
    40  ```hcl
    41  variable "image_id" {
    42    type        = string
    43    description = "The id of the machine image (AMI) to use for the server."
    44  
    45    validation {
    46      condition     = length(var.image_id) > 4 && substr(var.image_id, 0, 4) == "ami-"
    47      error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
    48    }
    49  }
    50  ```
    51  
    52  If the failure of an expression determines the validation decision, use the [`can` function](/terraform/language/functions/can) as demonstrated in the following example.
    53  
    54  ```hcl
    55  variable "image_id" {
    56    type        = string
    57    description = "The id of the machine image (AMI) to use for the server."
    58  
    59    validation {
    60      # regex(...) fails if it cannot find a match
    61      condition     = can(regex("^ami-", var.image_id))
    62      error_message = "The image_id value must be a valid AMI id, starting with \"ami-\"."
    63    }
    64  }
    65  ```
    66  
    67  
    68  ## Preconditions and Postconditions
    69  
    70  -> **Note:** Preconditions and postconditions are available in Terraform v1.2.0 and later.
    71  
    72  Use `precondition` and `postcondition` blocks to create custom rules for resources, data sources, and outputs.
    73  
    74  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.
    75  
    76  ### Usage
    77  
    78  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.
    79  
    80  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.
    81  
    82  The following example uses a postcondition to detect if the caller accidentally provided an AMI intended for the wrong system component.
    83  
    84  ```hcl
    85  data "aws_ami" "example" {
    86    id = var.aws_ami_id
    87  
    88    lifecycle {
    89      # The AMI ID must refer to an existing AMI that has the tag "nomad-server".
    90      postcondition {
    91        condition     = self.tags["Component"] == "nomad-server"
    92        error_message = "tags[\"Component\"] must be \"nomad-server\"."
    93      }
    94    }
    95  }
    96  ```
    97  
    98  #### Resources and Data Sources
    99  
   100  The `lifecycle` block inside a `resource` or `data` block can include both `precondition` and `postcondition` blocks.
   101  
   102  - 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.
   103  - 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.
   104  
   105  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.
   106  
   107  #### Outputs
   108  
   109  An `output` block can include a `precondition`  block.
   110  
   111  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.
   112  
   113  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.
   114  
   115  ### Examples
   116  
   117  The following example shows use cases for preconditions and postconditions. The preconditions and postconditions declare the following assumptions and guarantees.
   118  
   119  - **The AMI ID must refer to an AMI that contains an operating system for the
   120  `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.
   121  
   122  - **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.
   123  
   124  - **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.
   125  
   126  ```hcl
   127  
   128  data "aws_ami" "example" {
   129    owners = ["amazon"]
   130  
   131    filter {
   132      name   = "image-id"
   133      values = ["ami-abc123"]
   134    }
   135  }
   136  
   137  resource "aws_instance" "example" {
   138    instance_type = "t3.micro"
   139    ami           = data.aws_ami.example.id
   140  
   141    lifecycle {
   142      # The AMI ID must refer to an AMI that contains an operating system
   143      # for the `x86_64` architecture.
   144      precondition {
   145        condition     = data.aws_ami.example.architecture == "x86_64"
   146        error_message = "The selected AMI must be for the x86_64 architecture."
   147      }
   148  
   149      # The EC2 instance must be allocated a public DNS hostname.
   150      postcondition {
   151        condition     = self.public_dns != ""
   152        error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled."
   153      }
   154    }
   155  }
   156  
   157  data "aws_ebs_volume" "example" {
   158    # Use data resources that refer to other resources to
   159    # load extra data that isn't directly exported by a resource.
   160    #
   161    # Read the details about the root storage volume for the EC2 instance
   162    # declared by aws_instance.example, using the exported ID.
   163  
   164    filter {
   165      name = "volume-id"
   166      values = [aws_instance.example.root_block_device.volume_id]
   167    }
   168  
   169    # Whenever a data resource is verifying the result of a managed resource
   170    # declared in the same configuration, you MUST write the checks as
   171    # postconditions of the data resource. This ensures Terraform will wait
   172    # to read the data resource until after any changes to the managed resource
   173    # have completed.
   174    lifecycle {
   175      # The EC2 instance will have an encrypted root volume.
   176      postcondition {
   177        condition     = self.encrypted
   178        error_message = "The server's root volume is not encrypted."
   179      }
   180    }
   181  }
   182  
   183  output "api_base_url" {
   184    value = "https://${aws_instance.example.private_dns}:8433/"
   185  }
   186  ```
   187  
   188  ### Choosing Between Preconditions and Postconditions
   189  
   190  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.
   191  
   192  #### Use Preconditions for Assumptions
   193  
   194  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.
   195  
   196  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.
   197  
   198  #### Use Postconditions for Guarantees
   199  
   200  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.
   201  
   202  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.
   203  
   204  #### Additional Decision Factors
   205  
   206  You should also consider the following questions when creating preconditions and postconditions.
   207  
   208  - 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.
   209  - 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.
   210  - 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.
   211  
   212  ## Checks with Assertions
   213  
   214  -> **Note:** Check blocks and their assertions are only available in Terraform v1.5.0 and later.
   215  
   216  [Check blocks](/terraform/language/checks) can validate your infrastructure outside the usual resource lifecycle. You can add custom conditions via `assert` blocks, which execute at the end of the plan and apply stages and produce warnings to notify you of problems within your infrastructure.
   217  
   218  You can add one or more `assert` blocks within a `check` block to verify custom conditions. Each assertion requires a [`condition` argument](#condition-expressions), a boolean expression that should return `true` if the intended assumption or guarantee is fulfilled or `false` if it does not. Your `condition` expression can refer to any resource, data source, or variable available to the surrounding `check` block.
   219  
   220  The following example uses a check block with an assertion to verify the Terraform website is healthy.
   221  
   222  ```hcl
   223  check "health_check" {
   224    data "http" "terraform_io" {
   225      url = "https://www.terraform.io"
   226    }
   227  
   228    assert {
   229      condition = data.http.terraform_io.status_code == 200
   230      error_message = "${data.http.terraform_io.url} returned an unhealthy status code"
   231    }
   232  }
   233  ```
   234  
   235  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 assertions, Terraform returns error messages for all failed conditions.
   236  
   237  ### Continuous Validation in Terraform Cloud
   238  
   239  Terraform Cloud can automatically check whether the checks in a workspace’s configuration continue to pass after Terraform provisions the infrastructure. For example, you can write a `check` to continuously monitor the validity of an API gateway certificate. Continuous validation alerts you when the condition fails, so you can update the certificate and avoid errors the next time you want to update your infrastructure. Refer to [Continuous Validation](/terraform/cloud-docs/workspaces/health#continuous-validation) in the Terraform Cloud documentation for details.
   240  
   241  ## Condition Expressions
   242  
   243  Check assertions, 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.
   244  
   245  You can use any of Terraform's built-in functions or language operators
   246  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.
   247  
   248  ### Logical Operators
   249  
   250  Use the logical operators `&&` (AND), `||` (OR), and `!` (NOT) to combine multiple conditions together.
   251  
   252  ```hcl
   253    condition = var.name != "" && lower(var.name) == var.name
   254  ```
   255  
   256  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](/terraform/language/expressions/operators) for details.
   257  
   258  ### `contains` Function
   259  
   260  Use the [`contains` function](/terraform/language/functions/contains) to test whether a given value is one of a set of predefined valid values.
   261  
   262  ```hcl
   263    condition = contains(["STAGE", "PROD"], var.environment)
   264  ```
   265  
   266  ### `length` Function
   267  
   268  Use the [`length` function](/terraform/language/functions/length) to test a collection's length and require a non-empty list or map.
   269  
   270  ```hcl
   271    condition = length(var.items) != 0
   272  ```
   273  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.
   274  
   275  ### `for` Expressions
   276  
   277  Use [`for` expressions](/terraform/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.
   278  
   279  ```hcl
   280    condition = alltrue([
   281      for v in var.instances : contains(["t2.micro", "m3.medium"], v.type)
   282    ])
   283  ```
   284  
   285  ### `can` Function
   286  
   287  Use the [`can` function](/terraform/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.
   288  
   289  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.
   290  
   291  ```hcl
   292    condition = can(regex("^[a-z]+$", var.name))
   293  ```
   294  
   295  You can also use `can` with the type conversion functions to test whether a value is convertible to a type or type constraint.
   296  
   297  ```hcl
   298    # This remote output value must have a value that can
   299    # be used as a string, which includes strings themselves
   300    # but also allows numbers and boolean values.
   301    condition = can(tostring(data.terraform_remote_state.example.outputs["name"]))
   302  ```
   303  
   304  ```hcl
   305    # This remote output value must be convertible to a list
   306    # type of with element type.
   307    condition = can(tolist(data.terraform_remote_state.example.outputs["items"]))
   308  ```
   309  
   310  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.
   311  
   312  ```hcl
   313    # var.example must have an attribute named "foo"
   314    condition = can(var.example.foo)
   315  ```
   316  
   317  ```hcl
   318    # var.example must be a sequence with at least one element
   319    condition = can(var.example[0])
   320    # (although it would typically be clearer to write this as a
   321    # test like length(var.example) > 0 to better represent the
   322    # intent of the condition.)
   323  ```
   324  
   325  ### `self` Object
   326  
   327  Use the `self` object in postcondition blocks to refer to attributes of the instance under evaluation.
   328  
   329  ```hcl
   330  resource "aws_instance" "example" {
   331    instance_type = "t2.micro"
   332    ami           = "ami-abc123"
   333  
   334    lifecycle {
   335      postcondition {
   336        condition     = self.instance_state == "running"
   337        error_message = "EC2 instance must be running."
   338      }
   339    }
   340  }
   341  ```
   342  
   343  ### `each` and `count` Objects
   344  
   345  In blocks where [`for_each`](/terraform/language/meta-arguments/for_each) or [`count`](/terraform/language/meta-arguments/count)  are set, use `each` and `count` objects to refer to other resources that are expanded in a chain.
   346  
   347  ```hcl
   348  variable "vpc_cidrs" {
   349    type = set(string)
   350  }
   351  
   352  data "aws_vpc" "example" {
   353    for_each = var.vpc_cidrs
   354  
   355    filter {
   356      name   = "cidr"
   357      values = [each.key]
   358    }
   359  }
   360  
   361  resource "aws_internet_gateway" "example" {
   362    for_each = data.aws_vpc.example
   363    vpc_id = each.value.id
   364  
   365    lifecycle {
   366      precondition {
   367        condition     = data.aws_vpc.example[each.key].state == "available"
   368        error_message = "VPC ${each.key} must be available."
   369      }
   370    }
   371  }
   372  ```
   373  
   374  ## Error Messages
   375  
   376  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.
   377  
   378  ```
   379  Error: Resource postcondition failed
   380  
   381    with data.aws_ami.example,
   382    on ec2.tf line 19, in data "aws_ami" "example":
   383    72:       condition     = self.tags["Component"] == "nomad-server"
   384      |----------------
   385      | self.tags["Component"] is "consul-server"
   386  
   387  The selected AMI must be tagged with the Component value "nomad-server".
   388  ```
   389  
   390  The `error_message` argument can be any expression that evaluates to a string.
   391  This includes literal strings, heredocs, and template expressions. You can use the [`format` function](/terraform/language/functions/format) to convert items of `null`, `list`, or `map` types into a formatted string. Multi-line
   392  error messages are supported, and lines with leading whitespace will not be
   393  word wrapped.
   394  
   395  We recommend writing error messages as one or more full sentences in a
   396  style similar to Terraform's own error messages. Terraform will show the
   397  message alongside the name of the resource that detected the problem and any
   398  external values included in the condition expression.
   399  
   400  ## Conditions Checked Only During Apply
   401  
   402  Terraform evaluates custom conditions as early as possible.
   403  
   404  Input variable validations can only refer to the variable value, so Terraform always evaluates them immediately. Check assertions, preconditions, and postconditions depend on Terraform evaluating whether the value(s) associated with the condition are known before or after applying the configuration.
   405  
   406  - **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.
   407  - **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.
   408  
   409  During the apply phase, a failed _precondition_
   410  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.
   411  
   412  Terraform typically has less information during the initial creation of a
   413  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.
   414