terraform: Eval module call arguments for import

Include the import walk in the list of operations for which we create an
EvalModuleCallArgument node. This causes module call arguments to be
evaluated even if the module variables have defaults, ensuring that
invalid default values (such as the common "{}" for variables thought of
as maps) do not cause failures specific to import.

This fixes a bug where a child module evaluates an input variable in its
locals block, assuming that it is a nested object structure. The bug
report includes a default value of "{}", which is overridden by a root
variable value. Without the eval node added in this commit, the default
value is used and the local evaluation errors.
This commit is contained in:
Alisdair McDiarmid 2020-08-17 17:03:25 -04:00
parent 27affd06ce
commit d8e9964363
5 changed files with 91 additions and 1 deletions

View File

@ -802,6 +802,76 @@ func TestImportModuleVarFile(t *testing.T) {
}
}
// This test covers an edge case where a module with a complex input variable
// of nested objects has an invalid default which is overridden by the calling
// context, and is used in locals. If we don't evaluate module call variables
// for the import walk, this results in an error.
//
// The specific example has a variable "foo" which is a nested object:
//
// foo = { bar = { baz = true } }
//
// This is used as foo = var.foo in the call to the child module, which then
// uses the traversal foo.bar.baz in a local. A default value in the child
// module of {} causes this local evaluation to error, breaking import.
func TestImportModuleInputVariableEvaluation(t *testing.T) {
td := tempDir(t)
copy.CopyDir(testFixturePath("import-module-input-variable"), td)
defer os.RemoveAll(td)
defer testChdir(t, td)()
statePath := testTempFile(t)
p := testProvider()
p.GetSchemaReturn = &terraform.ProviderSchema{
ResourceTypes: map[string]*configschema.Block{
"test_instance": {
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true},
},
},
},
}
providerSource, close := newMockProviderSource(t, map[string][]string{
"test": {"1.2.3"},
})
defer close()
// init to install the module
ui := new(cli.MockUi)
m := Meta{
testingOverrides: metaOverridesForProvider(testProvider()),
Ui: ui,
ProviderSource: providerSource,
}
ic := &InitCommand{
Meta: m,
}
if code := ic.Run([]string{}); code != 0 {
t.Fatalf("init failed\n%s", ui.ErrorWriter)
}
// import
ui = new(cli.MockUi)
c := &ImportCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
Ui: ui,
},
}
args := []string{
"-state", statePath,
"module.child.test_instance.foo",
"bar",
}
code := c.Run(args)
if code != 0 {
t.Fatalf("import failed; expected success")
}
}
func TestImport_dataResource(t *testing.T) {
defer testChdir(t, testFixturePath("import-missing-resource-config"))()

View File

@ -0,0 +1,11 @@
variable "foo" {
default = {}
}
locals {
baz = var.foo.bar.baz
}
resource "test_instance" "foo" {
foo = local.baz
}

View File

@ -0,0 +1,8 @@
variable "foo" {
default = {}
}
module "child" {
source = "./child"
foo = var.foo
}

View File

@ -0,0 +1 @@
foo = { bar = { baz = true } }

View File

@ -154,7 +154,7 @@ func (n *nodeModuleVariable) EvalTree() EvalNode {
Nodes: []EvalNode{
&EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkPlan, walkApply,
walkDestroy},
walkDestroy, walkImport},
Node: &EvalModuleCallArgument{
Addr: n.Addr.Variable,
Config: n.Config,