terraform/internal/terraform/variables_test.go

149 lines
4.1 KiB
Go
Raw Normal View History

2016-08-17 20:28:58 +02:00
package terraform
import (
"testing"
core and backend: remove redundant handling of default variable values Previously we had three different layers all thinking they were responsible for substituting a default value for an unset root module variable: - the local backend, via logic in backend.ParseVariableValues - the context.Plan function (and other similar functions) trying to preprocess the input variables using terraform.mergeDefaultInputVariableValues . - the newer prepareFinalInputVariableValue, which aims to centralize all of the variable preparation logic so it can be common to both root and child module variables. The second of these was also trying to handle type constraint checking, which is also the responsibility of the central function and not something we need to handle so early. Only the last of these consistently handles both root and child module variables, and so is the one we ought to keep. The others are now redundant and are causing prepareFinalInputVariableValue to get a slightly corrupted view of the caller's chosen variable values. To rectify that, here we remove the two redundant layers altogether and have unset root variables pass through as cty.NilVal all the way to the central prepareFinalInputVariableValue function, which will then handle them in a suitable way which properly respects the "nullable" setting. This commit includes some test changes in the terraform package to make those tests no longer rely on the mergeDefaultInputVariableValues logic we've removed, and to instead explicitly set cty.NilVal for all unset variables to comply with our intended contract for PlanOpts.SetVariables, and similar. (This is so that we can more easily catch bugs in callers where they _don't_ correctly handle input variables; it allows us to distinguish between the caller explicitly marking a variable as unset vs. not describing it at all, where the latter is a bug in the caller.)
2021-12-21 01:38:52 +01:00
"github.com/hashicorp/terraform/internal/configs"
"github.com/zclconf/go-cty/cty"
2016-08-17 20:28:58 +02:00
)
func TestCheckInputVariables(t *testing.T) {
c := testModule(t, "input-variables")
t.Run("No variables set", func(t *testing.T) {
// No variables set
diags := checkInputVariables(c.Module.Variables, nil)
if !diags.HasErrors() {
t.Fatal("check succeeded, but want errors")
}
// Required variables set, optional variables unset
// This is still an error at this layer, since it's the caller's
// responsibility to have already merged in any default values.
diags = checkInputVariables(c.Module.Variables, InputValues{
"foo": &InputValue{
Value: cty.StringVal("bar"),
SourceType: ValueFromCLIArg,
},
})
if !diags.HasErrors() {
t.Fatal("check succeeded, but want errors")
}
})
t.Run("All variables set", func(t *testing.T) {
diags := checkInputVariables(c.Module.Variables, InputValues{
"foo": &InputValue{
Value: cty.StringVal("bar"),
SourceType: ValueFromCLIArg,
},
"bar": &InputValue{
Value: cty.StringVal("baz"),
SourceType: ValueFromCLIArg,
},
"map": &InputValue{
Value: cty.StringVal("baz"), // okay because config has no type constraint
SourceType: ValueFromCLIArg,
},
"object_map": &InputValue{
Value: cty.MapVal(map[string]cty.Value{
"uno": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("baz"),
"bar": cty.NumberIntVal(2), // type = any
}),
"dos": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bat"),
"bar": cty.NumberIntVal(99), // type = any
}),
}),
SourceType: ValueFromCLIArg,
},
"object_list": &InputValue{
Value: cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("baz"),
"bar": cty.NumberIntVal(2), // type = any
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bang"),
"bar": cty.NumberIntVal(42), // type = any
}),
}),
SourceType: ValueFromCLIArg,
},
})
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
})
t.Run("Invalid Complex Types", func(t *testing.T) {
diags := checkInputVariables(c.Module.Variables, InputValues{
"foo": &InputValue{
Value: cty.StringVal("bar"),
SourceType: ValueFromCLIArg,
},
"bar": &InputValue{
Value: cty.StringVal("baz"),
SourceType: ValueFromCLIArg,
},
"map": &InputValue{
Value: cty.StringVal("baz"), // okay because config has no type constraint
SourceType: ValueFromCLIArg,
},
"object_map": &InputValue{
Value: cty.MapVal(map[string]cty.Value{
"uno": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("baz"),
"bar": cty.NumberIntVal(2), // type = any
}),
"dos": cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bat"),
"bar": cty.NumberIntVal(99), // type = any
}),
}),
SourceType: ValueFromCLIArg,
},
"object_list": &InputValue{
Value: cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("baz"),
"bar": cty.NumberIntVal(2), // type = any
}),
cty.ObjectVal(map[string]cty.Value{
"foo": cty.StringVal("bang"),
"bar": cty.StringVal("42"), // type = any, but mismatch with the first list item
}),
}),
SourceType: ValueFromCLIArg,
},
})
if diags.HasErrors() {
t.Fatalf("unexpected errors: %s", diags.Err())
}
})
}
core and backend: remove redundant handling of default variable values Previously we had three different layers all thinking they were responsible for substituting a default value for an unset root module variable: - the local backend, via logic in backend.ParseVariableValues - the context.Plan function (and other similar functions) trying to preprocess the input variables using terraform.mergeDefaultInputVariableValues . - the newer prepareFinalInputVariableValue, which aims to centralize all of the variable preparation logic so it can be common to both root and child module variables. The second of these was also trying to handle type constraint checking, which is also the responsibility of the central function and not something we need to handle so early. Only the last of these consistently handles both root and child module variables, and so is the one we ought to keep. The others are now redundant and are causing prepareFinalInputVariableValue to get a slightly corrupted view of the caller's chosen variable values. To rectify that, here we remove the two redundant layers altogether and have unset root variables pass through as cty.NilVal all the way to the central prepareFinalInputVariableValue function, which will then handle them in a suitable way which properly respects the "nullable" setting. This commit includes some test changes in the terraform package to make those tests no longer rely on the mergeDefaultInputVariableValues logic we've removed, and to instead explicitly set cty.NilVal for all unset variables to comply with our intended contract for PlanOpts.SetVariables, and similar. (This is so that we can more easily catch bugs in callers where they _don't_ correctly handle input variables; it allows us to distinguish between the caller explicitly marking a variable as unset vs. not describing it at all, where the latter is a bug in the caller.)
2021-12-21 01:38:52 +01:00
// testInputValuesUnset is a helper for constructing InputValues values for
// situations where all of the root module variables are optional and a
// test case intends to just use those default values and not override them
// at all.
//
// In other words, this constructs an InputValues with one entry per given
// input variable declaration where all of them are declared as unset.
func testInputValuesUnset(decls map[string]*configs.Variable) InputValues {
if len(decls) == 0 {
return nil
}
ret := make(InputValues, len(decls))
for name := range decls {
ret[name] = &InputValue{
Value: cty.NilVal,
SourceType: ValueFromUnknown,
}
}
return ret
}