From a11f482d92359af64d4e28ffe150bc564e0d26ad Mon Sep 17 00:00:00 2001 From: Kazuma Watanabe Date: Sun, 6 Nov 2022 01:27:03 +0900 Subject: [PATCH] Convert variable types before applying defaults (#1582) --- terraform/evaluator.go | 16 +++---- terraform/evaluator_test.go | 96 +++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/terraform/evaluator.go b/terraform/evaluator.go index 58fbb725f..978baea50 100644 --- a/terraform/evaluator.go +++ b/terraform/evaluator.go @@ -248,14 +248,6 @@ func (d *evaluationData) GetInputVariable(addr addrs.InputVariable, rng hcl.Rang val = config.Default } - // Apply defaults from the variable's type constraint to the value, - // unless the value is null. We do not apply defaults to top-level - // null values, as doing so could prevent assigning null to a nullable - // variable. - if config.TypeDefaults != nil && !val.IsNull() { - val = config.TypeDefaults.Apply(val) - } - var err error val, err = convert.Convert(val, config.ConstraintType) if err != nil { @@ -268,6 +260,14 @@ func (d *evaluationData) GetInputVariable(addr addrs.InputVariable, rng hcl.Rang val = cty.UnknownVal(config.Type) } + // Apply defaults from the variable's type constraint to the value, + // unless the value is null. We do not apply defaults to top-level + // null values, as doing so could prevent assigning null to a nullable + // variable. + if config.TypeDefaults != nil && !val.IsNull() { + val = config.TypeDefaults.Apply(val) + } + // Mark if sensitive if config.Sensitive { val = val.Mark(marks.Sensitive) diff --git a/terraform/evaluator_test.go b/terraform/evaluator_test.go index 750754b54..b27f9a8bc 100644 --- a/terraform/evaluator_test.go +++ b/terraform/evaluator_test.go @@ -517,6 +517,102 @@ variable "foo" { want: `cty.NullVal(cty.Object(map[string]cty.Type{"default":cty.Bool, "optional":cty.String, "required":cty.String}))`, errCheck: neverHappend, }, + { + name: "module variable optional attributes with nested default optional", + config: ` +variable "foo" { + type = set(object({ + name = string + schedules = set(object({ + name = string + cold_storage_after = optional(number, 10) + })) + })) +}`, + expr: expr(`var.foo`), + inputs: []InputValues{ + { + "foo": { + Value: cty.SetVal([]cty.Value{ + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("test1"), + "schedules": cty.SetVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "name": cty.StringVal("daily"), + }), + }), + }), + cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("test2"), + "schedules": cty.SetVal([]cty.Value{ + cty.MapVal(map[string]cty.Value{ + "name": cty.StringVal("daily"), + }), + cty.MapVal(map[string]cty.Value{ + "name": cty.StringVal("weekly"), + "cold_storage_after": cty.StringVal("0"), + }), + }), + }), + }), + }, + }, + }, + ty: cty.Set(cty.Object(map[string]cty.Type{ + "name": cty.String, + "schedules": cty.Set(cty.Object(map[string]cty.Type{ + "name": cty.String, + "cold_storage_after": cty.Number, + })), + })), + want: `cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("test1"), "schedules":cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"cold_storage_after":cty.NumberIntVal(10), "name":cty.StringVal("daily")})})}), cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("test2"), "schedules":cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"cold_storage_after":cty.NumberIntVal(0), "name":cty.StringVal("weekly")}), cty.ObjectVal(map[string]cty.Value{"cold_storage_after":cty.NumberIntVal(10), "name":cty.StringVal("daily")})})})})`, + errCheck: neverHappend, + }, + { + name: "module variable optional attributes with nested complex types", + config: ` +variable "foo" { + type = object({ + name = string + nested_object = object({ + name = string + value = optional(string, "foo") + }) + nested_object_with_default = optional(object({ + name = string + value = optional(string, "bar") + }), { + name = "nested_object_with_default" + }) + }) +}`, + expr: expr(`var.foo`), + inputs: []InputValues{ + { + "foo": { + Value: cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("object"), + "nested_object": cty.ObjectVal(map[string]cty.Value{ + "name": cty.StringVal("nested_object"), + }), + }), + }, + }, + }, + ty: cty.Object(map[string]cty.Type{ + "name": cty.String, + "nested_object": cty.Object(map[string]cty.Type{ + "name": cty.String, + "value": cty.String, + }), + "nested_object_with_default": cty.Object(map[string]cty.Type{ + "name": cty.String, + "value": cty.String, + }), + }), + want: `cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("object"), "nested_object":cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("nested_object"), "value":cty.StringVal("foo")}), "nested_object_with_default":cty.ObjectVal(map[string]cty.Value{"name":cty.StringVal("nested_object_with_default"), "value":cty.StringVal("bar")})})`, + errCheck: neverHappend, + }, { name: "static local value", config: `locals { foo = "bar" }`,