Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Default (optional) values in nested null object cause it to be non-null #32160

Closed
slwbuild opened this issue Nov 3, 2022 · 7 comments · Fixed by #32178
Closed

Default (optional) values in nested null object cause it to be non-null #32160

slwbuild opened this issue Nov 3, 2022 · 7 comments · Fixed by #32178
Assignees
Labels
bug config confirmed a Terraform Core team member has reproduced this issue v1.3 Issues (primarily bugs) reported against v1.3 releases

Comments

@slwbuild
Copy link

slwbuild commented Nov 3, 2022

Terraform Version

Terraform v1.3.4

Terraform Configuration Files

module "defaulting_module" {
    source = "./modules/default-module"
    request = {}
}

output "output" {
    value = module.defaulting_module
}
variable "request" {
    type = object({
        thing = optional(object({
            flag = optional(bool, false)
        }))
    })
}

output "request" {
    value = var.request
}

Debug Output

Changes to Outputs:
  + output = {
      + request = {
          + thing = {
              + flag = false
            }
        }
    }

Expected Behavior

thing should be null

Actual Behavior

thing is non-null and contains defaulted values

Steps to Reproduce

terraform plan

Additional Context

Output in TF 1.3.3 matched expected results. Changed in 1.3.4

References

No response

@slwbuild slwbuild added bug new new issue not yet triaged labels Nov 3, 2022
@GRBurst
Copy link

GRBurst commented Nov 3, 2022

I came across a similar issue and are currently trying to figure out what I should expect. From the docs:

An optional attribute with a non-null default value is guaranteed to never have the value null within the receiving module. Terraform will substitute the default value both when a caller omits the attribute altogether and when a caller explicitly sets it to null, thereby avoiding the need for additional checks to handle a possible null value.

Terraform applies object attribute defaults top-down in nested variable types. This means that Terraform applies the default value you specify in the optional modifier first and then later applies any nested default values to that attribute.

Especially the second paragraph seems like it initializes and applies the nested default values to the parent object, hence creating an object?
So this might be the intended use, but it is definitely not something I would expect or design.

@slwbuild
Copy link
Author

slwbuild commented Nov 3, 2022

It isn't what i expected either and the behavior completely changed between 1.3.3 and 1.3.4 so i assumed it was not intentional

@apparentlymart
Copy link
Contributor

Thanks for reporting this, @slwbuild.

Indeed, I think this is a regression that resulted from an attempt to address a nearby bug related to the population of default values in nested objects.

Based on my understanding of the intended design here, I would expect that leaving thing unassigned would cause it to be null, because you didn't specify an empty object as its default value.

I would expect the output you shared if you had assigned it like this:

  request = {
    thing = {}
  }

...because once thing is actually an object the optional attribute rules declared inside it should be effective.

I would also expect the output you shared if the type declaration included a non-null default value for thing, like this:

variable "request" {
    type = object({
        thing = optional(object({
            flag = optional(bool, false)
        }), {})
    })
}

In the above case, the default value for thing must be converted to the declared object type and so the default value is effectively { flag = false } after that conversion.

Without either of these things being true, I would agree with you that the final value of this variable ought to have been:

{
  thing = null /* of object({ flag = bool }) */
}

@apparentlymart
Copy link
Contributor

Here are the three cases I described above represented as code:

variable "thing_default_null_unset" {
  type = object({
    thing = optional(object({
      flag = optional(bool, false)
    }))
  })
  default = {}
}

variable "thing_default_null_but_set" {
  type = object({
    thing = optional(object({
      flag = optional(bool, false)
    }))
  })
  default = {
    thing = {}
  }
}

variable "thing_default_obj" {
  type = object({
    thing = optional(object({
      flag = optional(bool, false)
    }), {})
  })
  default = {}
}

output "thing_default_null_unset" {
  value = var.thing_default_null_unset

  precondition {
    condition     = var.thing_default_null_unset.thing == null
    error_message = "thing should be null."
  }
}

output "thing_default_null_but_set" {
  value = var.thing_default_null_but_set

  precondition {
    condition     = !var.thing_default_null_but_set.thing.flag
    error_message = "thing.flag should be false."
  }
}

output "thing_default_obj" {
  value = var.thing_default_obj

  precondition {
    condition     = !var.thing_default_obj.thing.flag
    error_message = "thing.flag should be false."
  }
}

When I run this the first output value's precondition fails:

╷
│ Error: Module output value precondition failed
│ 
│   on optional-object-null.tf line 34, in output "thing_default_null_unset":
│   34:     condition     = var.thing_default_null_unset.thing == null
│     ├────────────────
│     │ var.thing_default_null_unset.thing is object with 1 attribute "flag"
│ 
│ thing should be null.
╵

I believe all three of these should pass, per my understanding of the design intent of this feature.

@apparentlymart apparentlymart added config confirmed a Terraform Core team member has reproduced this issue v1.3 Issues (primarily bugs) reported against v1.3 releases and removed new new issue not yet triaged labels Nov 3, 2022
@stevehipwell
Copy link

We're seeing a similar regression from v1.3.3 to v1.3.4 where we have optional nested attributes with defaults.

@liamcervante
Copy link
Member

I think the root cause of this is the same as #32157.

In the other case the problem is being exposed as a crash because the created objects are trying to be pushed into a collection which then fails as they have different types. Here it is just creating the bad objects from the nulls.

@github-actions
Copy link

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.
If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Dec 11, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug config confirmed a Terraform Core team member has reproduced this issue v1.3 Issues (primarily bugs) reported against v1.3 releases
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants