Terraform: 0.13 function merge() returns null

Created on 16 Sep 2020  路  2Comments  路  Source: hashicorp/terraform

Terraform Version

0.13.2

Terraform Configuration Files

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
}

Debug Output

Crash Output

Expected Behavior


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 = {}

Actual Behavior


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
}

Steps to Reproduce

  • terraform init
  • terraform apply
  • Additional Context


    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.

    References

    bug confirmed v0.13 v0.14

    Most helpful comment

    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.

    All 2 comments

    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())
    {}
    
    Was this page helpful?
    5 / 5 - 1 ratings