While trying out the new module_variable_optional_attrs experiment, I ran into this issue with nested optional attributes. The exact same thing works if I explicitly set the optional_attr to null, and remove the experiment.
0.14.2
Please see the minimal github repo here
There are two examples in the repo. working-without-optional and broken-with-optional. They are identical except the broken example attempts to remove the explicit optional_attr = null using the optional type.
See the debug output in the repo here
See the crash.log in the repo here
The two examples should behave exactly the same way. The optional_attr should be implicitly set to null in the broken-with-optional example, and pass tf plan.
Terraform panics and crashes.
git clone https://github.com/bsch150/terraform-14.2-optional-issue.git
cd broken-with-optional
terraform init
terraform plan
Does 'optional' need to be used in 'object' type?
variable "in" {
type = object({
required = string
optional = optional(string)
})
}
Does 'optional' need to be used in 'object' type?
variable "in" { type = object({ required = string optional = optional(string) }) }
@snowsky I'm not sure I understand your question. Are you asking whether the optional is needed there? Or do you mean to point out some way I'm misusing the experiment?
Thank you for providing the reproduction repo @bsch150 , and I'm sorry about the panic! I've confirmed that the issue still exists in the 0.14 release branch and our master branch.
The crash output gives us a good hint about the problem: convert (in the upstream go-cty library) is getting inconsistent types; in one the list_with_optional is cty.ObjectWithOptionalAttrs and in the other it's just a cty.Object. This is enough information for us to dig into _where_ the problem is occurring. I can't guarantee that we won't find that there's a configuration error in here somewhere, but it _definitely_ should not panic.
Here's the main panic (with some editing and extra newlines, for easier reading):
inconsistent list element types
(cty.Object(map[string]cty.Type{"nullable_object":cty.Object(map[string]cty.Type{"list_with_optional":cty.List(cty.ObjectWithOptionalAttrs(map[string]cty.Type{"attr":cty.String, "optional_attr":cty.String}, []string{"", "optional_attr"}))})})
cty.Object(map[string]cty.Type{"nullable_object":cty.Object(map[string]cty.Type{"list_with_optional":cty.List(cty.Object(map[string]cty.Type{"attr":cty.String, "optional_attr":cty.String}))})}))
And finally to answer @snowsky 's question, any attribute inside an object can be optional, it does not need to be the entire object.
I also faced a similar issue today. But, the variable structure I used is a little different. Just wanted to share that.
The variable structure in the bug's description is using optional in a nested object. Like this,
variable "list_of_objects_var" {
type = list(object({
nullable_object = object({
list_with_optional = list(object({
attr = string
optional_attr = optional(string)
}))
})
}))
}
The one I used was a nested optional object. Like this,
variable "test_optional" {
type = list(object({
level_one_req_attr = string
level_one_optional_attr = optional(object({
level_two_req_attr = string
level_two_optional_attr = optional(string)
}))
}))
}
And the input to the above variable is like this,
test_optional = [
{
level_one_req_attr = "sample1"
},
{
level_one_req_attr = "sample2"
level_one_optional_attr = {
level_two_req_attr = "sample2"
}
},
{
level_one_req_attr = "sample3"
level_one_optional_attr = {
level_two_req_attr = "sample3"
level_two_optional_attr = "sample3"
}
}
]
And the main panic is,
inconsistent list element types
(cty.Object(map[string]cty.Type{"level_one_optional_attr":cty.ObjectWithOptionalAttrs(map[string]cty.Type{"level_two_optional_attr":cty.String, "level_two_req_attr":cty.String}, []string{"", "level_two_optional_attr"}), "level_one_req_attr":cty.String}) then
cty.Object(map[string]cty.Type{"level_one_optional_attr":cty.Object(map[string]cty.Type{"level_two_optional_attr":cty.String, "level_two_req_attr":cty.String}), "level_one_req_attr":cty.String}))
The reason for inconsistent list element types is exactly the same as @mildwonkey described above.
Thanks for the responses all - especially @mildwonkey!
I just made an interesting discovery around this issue - if you flip the order of the input array, plan passes.
in my broken-with-optional folder, my inputs to the module are:
module "test" {
source = "./module"
list_of_objects_var = [
{
nullable_object = null
},
{
nullable_object = {
list_with_optional = [
{
attr = "line"
}
]
}
}
]
}
Now, if I switch the two elements of the list:
module "test" {
source = "./module"
list_of_objects_var = [
{
nullable_object = {
list_with_optional = [
{
attr = "line"
}
]
}
},
{
nullable_object = null
}
]
}
With this change I can run plan successfully. However, this is not a solution for my use case as the order does matter for me.
Having never looked into your source code I'll avoid making any guesses why that would be - hopefully this is helpful.
Most helpful comment
Thank you for providing the reproduction repo @bsch150 , and I'm sorry about the panic! I've confirmed that the issue still exists in the 0.14 release branch and our master branch.
The crash output gives us a good hint about the problem:
convert(in the upstream go-cty library) is getting inconsistent types; in one thelist_with_optionaliscty.ObjectWithOptionalAttrsand in the other it's just acty.Object. This is enough information for us to dig into _where_ the problem is occurring. I can't guarantee that we won't find that there's a configuration error in here somewhere, but it _definitely_ should not panic.Here's the main panic (with some editing and extra newlines, for easier reading):
And finally to answer @snowsky 's question, any attribute inside an object can be
optional, it does not need to be the entire object.