Allow generating multiple error messages when checking the validity of a collection using check assertions, preconditions/postconditions, variable validation, or test run assertions
Terraform Version
latest
Use Cases
Objects are often used with for_each logic to create multiple versions of a resource. In order to validate the variables a similar construct is required for the check block. Currently check does not support either for_each, count or dynamic so more complex interpolations are required to create a single check for multiple counts of a resource.
Attempted Solutions
locals {
my_object = {
one = {
big = 10
small = 7
},
two = {
big = 7
small = 10
}
}
big = 10
small = 7
}
check "greaterthan_simple" {
assert {
condition = local.big > local.small
error_message = "big is not greater than small"
}
}
check "greaterthan_complex" {
assert {
condition = alltrue([for key, val in local.my_object : val.big > val.small])
error_message = format("big is not greater than small for these keys: %s", join(", ", [for key, val in local.my_object : key if val.big <= val.small]))
}
}
// Would be nice if you could do this in a for_each loop
check "greaterthan_foreach" {
for_each = local.my_object
assert {
condition = each.value.big > each.value.small
error_message = format("big is not greater than small in key: %s", each.key)
}
}
// Alternatively supporting dynamic would also unlock similar functionality
check "greaterthan_dynamic" {
dynamic "assert" {
for_each = local.my_object
content {
condition = assert.value.big > assert.value.small
error_message = format("big is not greater than small in key: %s", assert.key)
}
}
}
Proposal
Add support for for_each and / or dynamic blocks to simplify the logic required to use a check block. The attempted solution gives a simple tf file and example implementations.
References
No response
Thanks for this feature request! If you are viewing this issue and would like to indicate your interest, please use the 👍 reaction on the issue description to upvote this issue. We also welcome additional use case descriptions. Thanks again!
Thanks for sharing this use-case, @davepattie.
We were also chatting in another place about this and so I just wanted to note here an example of a different design I shared that I've been considering, since I too have been frustrated by writing conditions for collection-typed values where I want to treat each element as a separate entity for validation.
assert {
error_messages = [
for k, v in local.my_objects :
"Element ${format("%q", k)}: big attribute must be greater than small attribute."
if v.value.big <= v.value.small
]
}
I've shown this as an assert block to match with the examples you shared, but whatever we do here I would expect to support it in the same way for variable validation rules, preconditions, and postconditions too, since they are all intended to support similar patterns.
The way I imagine this working is that:
-
error_messagesis mutually-exclusive with theconditionanderror_messagepair that these blocks currently require. Both would be valid, but each condition block would have either a list of error messages or a single condition and associated error message. - The
error_messagesexpression must return something that can convert tolist(string). - Each element of the list becomes a separate error message, making it possible to generate a separate error message for each element of a collection rather than just a single error message which must somehow incorporate a list of all of the incorrect element keys.
- If the list has zero elements, there are no errors, and therefore that's the same as having a
conditionargument that is set totrue: the check passes.
Most of the syntax in my example above is not new: it's just a for expression that produces a sequence of strings. The only truly new part is the error_messages argument itself, and it would accept any valid Terraform expression that produces something that convert to a list of strings, meaning this feature could scale to more complicated situations when needed.
Is there a way to use a check resource as a boolean for a count?
Something like this:
resource "aws_iam_service_linked_role" "AWSServiceRoleForAutoScaling" {
count = check.role_AWSServiceRoleForAutoScaling_already_exists ? 0 : 1
aws_service_name = "autoscaling.amazonaws.com"
description = "Default Service-Linked Role enables access to AWS Services and Resources used or managed by Auto Scaling."
}
check "role_AWSServiceRoleForAutoScaling_already_exists" {
data "aws_iam_role" "AWSServiceRoleForAutoScaling" {
name = "AWSServiceRoleForAutoScaling"
}
assert {
condition = data.aws_iam_role.AWSServiceRoleForAutoScaling.name == "AWSServiceRoleForAutoScaling"
error_message = "${data.aws_iam_role.AWSServiceRoleForAutoScaling.name} doesn't exist yet."
}
}
Then if we could use for_each, we could be checking for multiple "conditions" which would in turn make it even easier.
Another example of something that would be useful for something like this:
assert {
for_each = [
"serviceAccount:[email protected]",
"user:[email protected]",
# [...] more members here
]
condition = contains(
keys(google_secret_manager_secret_iam_member.accessor_membership),
each.key
)
error_message = "Member not found."
}
(of course, there are probably other ways to do this, maybe using setsubtract(), alltrue() etc, but it's a little trickier, especially in the case of there being multiple objects)