terraform
terraform copied to clipboard
`element types must all match for conversion` when using matching type but from module output
Terraform Version
ᐅ terraform -version
Terraform v1.5.7
on darwin_arm64
Terraform Configuration Files
main.tf
locals {
things = [
{
thing = {}
},
{
thing = {} # works
# thing = module.data.thing # yields: element types must all match for conversion
}
]
}
module "data" {
source = "./data"
}
module "things" {
source = "./things"
in = local.things
}
output "things" {
value = module.things
}
output "comparison" {
value = {
from_local = local.things[1].thing
from_module = module.data.thing
}
}
data/main.tf
output "thing" {
value = {}
}
things/main.tf
variable "in" {
type = list(any)
}
output "out" {
value = var.in
}
Debug Output
Expected Behavior
I have tried my best to distill the issue down to a minimal reproducible issue. Essentially, when I'm passing a variable to a module that has a type of list(any) the types of the contained values have to match; completely normal. If I define the value locally, great. However, if I use a value given as an output in another module, terraform complains that the types don't match.
Actual Behavior
As I illustrate in the example, terraform complains that the types don't match, but if you look at the planned output for comparison, they sure look like the same type to me.
ᐅ TF_LOG=trace TF_LOG_PATH=tf-trace-(date +%FT%T+01).log terraform plan -lock=false
Changes to Outputs:
+ comparison = {
+ from_local = {}
+ from_module = {}
}
+ things = {
+ out = [
+ {
+ thing = {}
},
+ {
+ thing = {}
},
]
}
Steps to Reproduce
- Create these files as given.
terraform initterraform plan- Comment L7.
- Uncomment L8.
- Observe the new error.
Additional Context
No response
References
No response
Thanks for the example @theherk! I'm not sure how this hasn't cropped up before, but it is a bit of an odd case. The failure is happening during validation, where runtime values are all considered as unknown, so the list is being created with an empty object and a dynamic value, which can't convert to a single type. While the static module output in this example makes the type obvious, in most cases the output type is not going to be known during validation, so deriving a correctly typed value may not be possible, and will probably require output type declarations as well.
In the meantime, using a more precise type for the module input usually will help with the type inference problems. You usually don't want something like list(any), because while that allows the list to contain a single element type which is unknown, it makes the type inference more difficult because that single type could literally be "anything".
In some cases, there are uses for any. This, I believe is one of them, though I admit that isn't clear in the contrived example. I don't understand what is unknown though, at least in this case. The output is a concrete empty map; nothing dynamic as you point out. So, I'm still unclear on why validation can't be done when the value can be known.
Are output type declarations forthcoming? That is an interesting development. Thank you.
Validation is done using unknown values wherever possible, because we are statically validating the configuration, and want to cover all possible inputs and values for variables and resources. Yes, the output is static here, and an exception could probably be added to the configuration to record the type in this particular case; but that is rare in practice, and does not work if the module is expanded or the type is inferred from other dynamic values.
Being a contrived example it's hard to say what may be optimal, but using list(any) is the what sets up the problem. Using that type constraint gives you very little extra assurance since the element type is still unknown and types are invariant, so it may be more useful if you simply use any instead if you can't declare a static type for the value.
Output type declarations are something being considered, but there is no set timeline as of yet.