diff --git a/website/docs/language/expressions/conditionals.mdx b/website/docs/language/expressions/conditionals.mdx index 8f5fca61f2..f542483f85 100644 --- a/website/docs/language/expressions/conditionals.mdx +++ b/website/docs/language/expressions/conditionals.mdx @@ -39,6 +39,131 @@ The condition can be any expression that resolves to a boolean value. This will usually be an expression that uses the equality, comparison, or logical operators. +The following language features are particularly useful when writing condition expressions. + +### `contains` Function + +Use the built-in function `contains` to test whether a given value is one of a set of predefined valid values. + +```hcl + condition = contains(["STAGE", "PROD"], var.environment) +``` + +### Boolean Operators + +Use the boolean operators `&&` (AND), `||` (OR), and `!` (NOT) to combine multiple conditions together. + +```hcl + condition = var.name != "" && lower(var.name) == var.name + ``` + +### `length` Function + +Require a non-empty list or map by testing the collection's length. + +```hcl + condition = length(var.items) != 0 +``` +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. + +### `for` Expressions + +Use `for` expressions in conjunction with the functions `alltrue` and `anytrue` to test whether a condition holds for all or for any elements of a collection. + +```hcl + condition = alltrue([ + for v in var.instances : contains(["t2.micro", "m3.medium"], v.type) + ]) +``` + +### `can` Function + +Use the `can` function 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. + +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. + +```hcl + condition = can(regex("^[a-z]+$", var.name) +``` + +You can also use `can` with the type conversion functions to test whether a value is convertible to a type or type constraint. + +```hcl + # This remote output value must have a value that can + # be used as a string, which includes strings themselves + # but also allows numbers and boolean values. + condition = can(tostring(data.terraform_remote_state.example.outputs["name"])) +``` + +```hcl + # This remote output value must be convertible to a list + # type of with element type. + condition = can(tolist(data.terraform_remote_state.example.outputs["items"])) +``` + +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. + +```hcl + # var.example must have an attribute named "foo" + condition = can(var.example.foo) ``` + +```hcl + # var.example must be a sequence with at least one element + condition = can(var.example[0]) + # (although it would typically be clearer to write this as a + # test like length(var.example) > 0 to better represent the + # intent of the condition.) +``` + +### `self` Object + +Use the `self` object in postcondition blocks to refer to attributes of the instance under evaluation. + +```hcl +resource "aws_instance" "example" { + instance_type = "t2.micro" + ami = "ami-abc123" + + lifecycle { + postcondition { + condition = self.instance_state == "running" + error_message = "EC2 instance must be running." + } + } +} +``` + +### `each` and `count` Objects + +In blocks where `for_each` or `count` are set, use `each` and `count` objects to refer to other resources that are expanded in a chain. + +```hcl +variable "vpc_cidrs" { + type = set(string) +} + +data "aws_vpc" "example" { + for_each = var.vpc_cidrs + + filter { + name = "cidr" + values = [each.key] + } +} + +resource "aws_internet_gateway" "example" { + for_each = aws_vpc.example + vpc_id = each.value.id + + lifecycle { + precondition { + condition = aws_vpc.example[each.key].state == "available" + error_message = "VPC ${each.key} must be available." + } + } +} +``` + ## Result Types The two result values may be of any type, but they must both diff --git a/website/docs/language/expressions/custom-conditions.mdx b/website/docs/language/expressions/custom-conditions.mdx index 9bc8e1b982..abf984bb38 100644 --- a/website/docs/language/expressions/custom-conditions.mdx +++ b/website/docs/language/expressions/custom-conditions.mdx @@ -185,131 +185,7 @@ You should also consider the following factors. 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. You can use any of Terraform's built-in functions or language operators -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. - -### `contains` Function - -Use the built-in function `contains` to test whether a given value is one of a set of predefined valid values. - -```hcl - condition = contains(["STAGE", "PROD"], var.environment) -``` - -### Boolean Operators - -Use the boolean operators `&&` (AND), `||` (OR), and `!` (NOT) to combine multiple conditions together. - -```hcl - condition = var.name != "" && lower(var.name) == var.name - ``` - -### `length` Function - -Require a non-empty list or map by testing the collection's length. - -```hcl - condition = length(var.items) != 0 -``` -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. - -### `for` Expressions - -Use `for` expressions in conjunction with the functions `alltrue` and `anytrue` to test whether a condition holds for all or for any elements of a collection. - -```hcl - condition = alltrue([ - for v in var.instances : contains(["t2.micro", "m3.medium"], v.type) - ]) -``` - -### `can` Function - -Use the `can` function 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. - -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. - -```hcl - condition = can(regex("^[a-z]+$", var.name) -``` - -You can also use `can` with the type conversion functions to test whether a value is convertible to a type or type constraint. - -```hcl - # This remote output value must have a value that can - # be used as a string, which includes strings themselves - # but also allows numbers and boolean values. - condition = can(tostring(data.terraform_remote_state.example.outputs["name"])) -``` - -```hcl - # This remote output value must be convertible to a list - # type of with element type. - condition = can(tolist(data.terraform_remote_state.example.outputs["items"])) -``` - -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. - -```hcl - # var.example must have an attribute named "foo" - condition = can(var.example.foo) ``` - -```hcl - # var.example must be a sequence with at least one element - condition = can(var.example[0]) - # (although it would typically be clearer to write this as a - # test like length(var.example) > 0 to better represent the - # intent of the condition.) -``` - -### `self` Object - -Use the `self` object in postcondition blocks to refer to attributes of the instance under evaluation. - -```hcl -resource "aws_instance" "example" { - instance_type = "t2.micro" - ami = "ami-abc123" - - lifecycle { - postcondition { - condition = self.instance_state == "running" - error_message = "EC2 instance must be running." - } - } -} -``` - -### `each` and `count` Objects - -In blocks where `for_each` or `count` are set, use `each` and `count` objects to refer to other resources that are expanded in a chain. - -```hcl -variable "vpc_cidrs" { - type = set(string) -} - -data "aws_vpc" "example" { - for_each = var.vpc_cidrs - - filter { - name = "cidr" - values = [each.key] - } -} - -resource "aws_internet_gateway" "example" { - for_each = aws_vpc.example - vpc_id = each.value.id - - lifecycle { - precondition { - condition = aws_vpc.example[each.key].state == "available" - error_message = "VPC ${each.key} must be available." - } - } -} -``` +in a condition as long as the expression is valid and returns a boolean result. Refer to [Conditional Expressions](/language/expressions/conditionals) for more details and examples. ## Error Messages