0.13.2
locals {
project_role_bindings1 = merge([{a=2}, {c=1}]...)
project_role_bindings2 = merge([]...)
project_role_bindings3 = merge()
}
output out1 {
value = local.project_role_bindings1
}
output out2 {
value = local.project_role_bindings2
}
output out3 {
value = local.project_role_bindings3
}
According to the documentation, "merge takes an arbitrary number of maps or objects, and returns a single map or object that contains a merged set of elements from all arguments." So the result should be something like:
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
out1 = {
"a" = 2
"c" = 1
}
out2 = {}
out3 = {}
In reality when the merge function has no parameters it returns null. out2 and out3 outputs are suppressed. Therefore in cases in which the return of the merge function is used by some other function or code expecting a list the code fails.
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
Outputs:
out1 = {
"a" = 2
"c" = 1
}
terraform initterraform apply
Our code is using the return of the merge function in the transpose function. The transpose function expects a map and its argument cannot be null. So we receive the following error:
Error: Invalid function argument
...
Invalid value for "values" parameter: argument must not be null.
Our code worked in Terraform 0.12, it is only after changing to Terraform 0.13 that the error occurred. So this is a behavior change from 0.12 to 0.13.
Hi @zamerman! Thanks for this bug report.
I reproduced this easily using terraform console on a development build of Terraform 0.14:
> merge({})
{}
> merge()
null
In the implementation of this function, it seems like it's intentionally accepting and ignoring null values, rather than returning an error saying that the argument must be an object:
> merge(null)
null
> merge(null, {})
{}
...and so this behavior of merge() returning null seems to be coming as an edge case of the rule "if all of the arguments are null then return null", because having no arguments at all is treated by this logic as them "all being null".
I think in order to decide what to do here we'll need to first do some code archaeology to determine what prompted having the merge function silently ignore null rather than returning an error (as is typical for most other Terraform language features). At first glance I agree that it seems intuitive for merge() to return {} (e.g. so that merge(var.empty_list_of_objects...) can return {}), but the fact that there is some special handling of null here suggests that this behavior was added to allow for some special use-case that might be now broken by changing the behavior in retrospect.
I did some digging, and this function has been largely unchanged since it was brought over to go-cty from terraform v0.11. There are no comments and no documentation that describe why this choice was made, so I think we're ok to (cautiously) change it for v0.15:
Since terraform v0.11 didn't have a concept of null values, and merge would return an error if there were no arguments, I'm inclined to update the behavior so it will return an error if there are no arguments or if all arguments are null, while still accepting null arguments _as long as there is at least one valid argument_.
This is the closest one could get to merging "null" in v0.11:
> merge()
1:3: merge: expected 1 arguments, got 0 in:
${merge()}
Empty maps are fine (non-null) in v0.11:
> merge(map())
{}
Most helpful comment
Hi @zamerman! Thanks for this bug report.
I reproduced this easily using
terraform consoleon a development build of Terraform 0.14:In the implementation of this function, it seems like it's intentionally accepting and ignoring
nullvalues, rather than returning an error saying that the argument must be an object:...and so this behavior of
merge()returningnullseems to be coming as an edge case of the rule "if all of the arguments are null then return null", because having no arguments at all is treated by this logic as them "all being null".I think in order to decide what to do here we'll need to first do some code archaeology to determine what prompted having the
mergefunction silently ignorenullrather than returning an error (as is typical for most other Terraform language features). At first glance I agree that it seems intuitive formerge()to return{}(e.g. so thatmerge(var.empty_list_of_objects...)can return{}), but the fact that there is some special handling ofnullhere suggests that this behavior was added to allow for some special use-case that might be now broken by changing the behavior in retrospect.