package states import ( "fmt" "reflect" "testing" "github.com/go-test/deep" "github.com/zclconf/go-cty/cty" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/lang/marks" ) func TestState(t *testing.T) { // This basic tests exercises the main mutation methods to construct // a state. It is not fully comprehensive, so other tests should visit // more esoteric codepaths. state := NewState() rootModule := state.RootModule() if rootModule == nil { t.Errorf("root module is nil; want valid object") } rootModule.SetLocalValue("foo", cty.StringVal("foo value")) rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "baz", }.Instance(addrs.IntKey(0)), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a"))) multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false) multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b"))) multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false) want := &State{ Modules: map[string]*Module{ "": { Addr: addrs.RootModuleInstance, LocalValues: map[string]cty.Value{ "foo": cty.StringVal("foo value"), }, OutputValues: map[string]*OutputValue{ "bar": { Addr: addrs.AbsOutputValue{ OutputValue: addrs.OutputValue{ Name: "bar", }, }, Value: cty.StringVal("bar value"), Sensitive: false, }, "secret": { Addr: addrs.AbsOutputValue{ OutputValue: addrs.OutputValue{ Name: "secret", }, }, Value: cty.StringVal("secret value"), Sensitive: true, }, }, Resources: map[string]*Resource{ "test_thing.baz": { Addr: addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "baz", }.Absolute(addrs.RootModuleInstance), Instances: map[addrs.InstanceKey]*ResourceInstance{ addrs.IntKey(0): { Current: &ResourceInstanceObjectSrc{ SchemaVersion: 1, Status: ObjectReady, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{}, }, }, ProviderConfig: addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, }, }, }, "module.child": { Addr: addrs.RootModuleInstance.Child("child", addrs.NoKey), LocalValues: map[string]cty.Value{}, OutputValues: map[string]*OutputValue{ "pizza": { Addr: addrs.AbsOutputValue{ Module: addrs.RootModuleInstance.Child("child", addrs.NoKey), OutputValue: addrs.OutputValue{ Name: "pizza", }, }, Value: cty.StringVal("hawaiian"), Sensitive: false, }, }, Resources: map[string]*Resource{}, }, `module.multi["a"]`: { Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), LocalValues: map[string]cty.Value{}, OutputValues: map[string]*OutputValue{ "pizza": { Addr: addrs.AbsOutputValue{ Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")), OutputValue: addrs.OutputValue{ Name: "pizza", }, }, Value: cty.StringVal("cheese"), Sensitive: false, }, }, Resources: map[string]*Resource{}, }, `module.multi["b"]`: { Addr: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), LocalValues: map[string]cty.Value{}, OutputValues: map[string]*OutputValue{ "pizza": { Addr: addrs.AbsOutputValue{ Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")), OutputValue: addrs.OutputValue{ Name: "pizza", }, }, Value: cty.StringVal("sausage"), Sensitive: false, }, }, Resources: map[string]*Resource{}, }, }, } { // Our structure goes deep, so we need to temporarily override the // deep package settings to ensure that we visit the full structure. oldDeepDepth := deep.MaxDepth oldDeepCompareUnexp := deep.CompareUnexportedFields deep.MaxDepth = 50 deep.CompareUnexportedFields = true defer func() { deep.MaxDepth = oldDeepDepth deep.CompareUnexportedFields = oldDeepCompareUnexp }() } for _, problem := range deep.Equal(state, want) { t.Error(problem) } expectedOutputs := map[string]string{ `module.multi["a"].output.pizza`: "cheese", `module.multi["b"].output.pizza`: "sausage", } for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) { addr := o.Addr.String() expected := expectedOutputs[addr] delete(expectedOutputs, addr) if expected != o.Value.AsString() { t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString()) } } for addr, o := range expectedOutputs { t.Fatalf("missing output %q:%q", addr, o) } } func TestStateDeepCopyObject(t *testing.T) { obj := &ResourceInstanceObject{ Value: cty.ObjectVal(map[string]cty.Value{ "id": cty.StringVal("id"), }), Private: []byte("private"), Status: ObjectReady, Dependencies: []addrs.ConfigResource{ { Module: addrs.RootModule, Resource: addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "bar", }, }, }, CreateBeforeDestroy: true, } objCopy := obj.DeepCopy() if !reflect.DeepEqual(obj, objCopy) { t.Fatalf("not equal\n%#v\n%#v", obj, objCopy) } } func TestStateDeepCopy(t *testing.T) { state := NewState() rootModule := state.RootModule() if rootModule == nil { t.Errorf("root module is nil; want valid object") } rootModule.SetLocalValue("foo", cty.StringVal("foo value")) rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false) rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true) rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "baz", }.Instance(addrs.IntKey(0)), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), Private: []byte("private data"), Dependencies: []addrs.ConfigResource{}, CreateBeforeDestroy: true, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar", }.Instance(addrs.IntKey(0)), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), // Sensitive path at "woozles" AttrSensitivePaths: []cty.PathValueMarks{ { Path: cty.Path{cty.GetAttrStep{Name: "woozles"}}, Marks: cty.NewValueMarks(marks.Sensitive), }, }, Private: []byte("private data"), Dependencies: []addrs.ConfigResource{ { Module: addrs.RootModule, Resource: addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "baz", }, }, }, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey)) childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false) stateCopy := state.DeepCopy() if !state.Equal(stateCopy) { t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy) } } func TestStateHasResourceInstanceObjects(t *testing.T) { providerConfig := addrs.AbsProviderConfig{ Module: addrs.RootModule, Provider: addrs.MustParseProviderSourceString("test/test"), } childModuleProviderConfig := addrs.AbsProviderConfig{ Module: addrs.RootModule.Child("child"), Provider: addrs.MustParseProviderSourceString("test/test"), } tests := map[string]struct { Setup func(ss *SyncState) Want bool }{ "empty": { func(ss *SyncState) {}, false, }, "one current, ready object in root module": { func(ss *SyncState) { ss.SetResourceInstanceCurrent( mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectReady, }, providerConfig, ) }, true, }, "one current, ready object in child module": { func(ss *SyncState) { ss.SetResourceInstanceCurrent( mustAbsResourceAddr("module.child.test.foo").Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectReady, }, childModuleProviderConfig, ) }, true, }, "one current, tainted object in root module": { func(ss *SyncState) { ss.SetResourceInstanceCurrent( mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectTainted, }, providerConfig, ) }, true, }, "one deposed, ready object in root module": { func(ss *SyncState) { ss.SetResourceInstanceDeposed( mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), DeposedKey("uhoh"), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectTainted, }, providerConfig, ) }, true, }, "one empty resource husk in root module": { func(ss *SyncState) { // Current Terraform doesn't actually create resource husks // as part of its everyday work, so this is a "should never // happen" case but we'll test to make sure we're robust to // it anyway, because this was a historical bug blocking // "terraform workspace delete" and similar. ss.SetResourceInstanceCurrent( mustAbsResourceAddr("test.foo").Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectTainted, }, providerConfig, ) s := ss.Lock() delete(s.Modules[""].Resources["test.foo"].Instances, addrs.NoKey) ss.Unlock() }, false, }, "one current data resource object in root module": { func(ss *SyncState) { ss.SetResourceInstanceCurrent( mustAbsResourceAddr("data.test.foo").Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{}`), Status: ObjectReady, }, providerConfig, ) }, false, // data resources aren't managed resources, so they don't count }, } for name, test := range tests { t.Run(name, func(t *testing.T) { state := BuildState(test.Setup) got := state.HasManagedResourceInstanceObjects() if got != test.Want { t.Errorf("wrong result\nstate content: (using legacy state string format; might not be comprehensive)\n%s\n\ngot: %t\nwant: %t", state, got, test.Want) } }) } } func TestState_MoveAbsResource(t *testing.T) { // Set up a starter state for the embedded tests, which should start from a copy of this state. state := NewState() rootModule := state.RootModule() rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.IntKey(0)), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) t.Run("basic move", func(t *testing.T) { s := state.DeepCopy() dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) s.MoveAbsResource(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } if len(s.RootModule().Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) } got := s.Resource(dst) if got.Addr.Resource != dst.Resource { t.Fatalf("dst resource not in state") } }) t.Run("move to new module", func(t *testing.T) { s := state.DeepCopy() dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("one")) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(dstModule) s.MoveAbsResource(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } if s.Module(dstModule) == nil { t.Fatalf("child module %s not in state", dstModule.String()) } if len(s.Module(dstModule).Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) } got := s.Resource(dst) if got.Addr.Resource != dst.Resource { t.Fatalf("dst resource not in state") } }) t.Run("from a child module to root", func(t *testing.T) { s := state.DeepCopy() srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) cm := s.EnsureModule(srcModule) cm.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child", }.Instance(addrs.IntKey(0)), // Moving the AbsResouce moves all instances &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) cm.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child", }.Instance(addrs.IntKey(1)), // Moving the AbsResouce moves all instances &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(addrs.RootModuleInstance) s.MoveAbsResource(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } // The child module should have been removed after removing its only resource if s.Module(srcModule) != nil { t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) } if len(s.RootModule().Resources) != 2 { t.Fatalf("wrong number of resources in state; expected 2, found %d", len(s.RootModule().Resources)) } if len(s.Resource(dst).Instances) != 2 { t.Fatalf("wrong number of resource instances for dst, got %d expected 2", len(s.Resource(dst).Instances)) } got := s.Resource(dst) if got.Addr.Resource != dst.Resource { t.Fatalf("dst resource not in state") } }) t.Run("module to new module", func(t *testing.T) { s := NewState() srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) cm := s.EnsureModule(srcModule) cm.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) s.MoveAbsResource(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } // The child module should have been removed after removing its only resource if s.Module(srcModule) != nil { t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) } gotMod := s.Module(dstModule) if len(gotMod.Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) } got := s.Resource(dst) if got.Addr.Resource != dst.Resource { t.Fatalf("dst resource not in state") } }) t.Run("module to new module", func(t *testing.T) { s := NewState() srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists")) dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new")) cm := s.EnsureModule(srcModule) cm.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule) s.MoveAbsResource(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } // The child module should have been removed after removing its only resource if s.Module(srcModule) != nil { t.Fatalf("child module %s was not removed from state after mv", srcModule.String()) } gotMod := s.Module(dstModule) if len(gotMod.Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources)) } got := s.Resource(dst) if got.Addr.Resource != dst.Resource { t.Fatalf("dst resource not in state") } }) } func TestState_MaybeMoveAbsResource(t *testing.T) { state := NewState() rootModule := state.RootModule() rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.IntKey(0)), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance) // First move, success t.Run("first move", func(t *testing.T) { moved := state.MaybeMoveAbsResource(src, dst) if !moved { t.Fatal("wrong result") } }) // Trying to move a resource that doesn't exist in state to a resource which does exist should be a noop. t.Run("noop", func(t *testing.T) { moved := state.MaybeMoveAbsResource(src, dst) if moved { t.Fatal("wrong result") } }) } func TestState_MoveAbsResourceInstance(t *testing.T) { state := NewState() rootModule := state.RootModule() rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) // src resource from the state above src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) t.Run("resource to resource instance", func(t *testing.T) { s := state.DeepCopy() // For a little extra fun, move a resource to a resource instance: test_thing.foo to test_thing.foo[1] dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) s.MoveAbsResourceInstance(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } if len(s.RootModule().Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources)) } got := s.ResourceInstance(dst) if got == nil { t.Fatalf("dst resource not in state") } }) t.Run("move to new module", func(t *testing.T) { s := state.DeepCopy() // test_thing.foo to module.kinder.test_thing.foo["baz"] dstModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(dstModule) s.MoveAbsResourceInstance(src, dst) if s.Empty() { t.Fatal("unexpected empty state") } if s.Module(dstModule) == nil { t.Fatalf("child module %s not in state", dstModule.String()) } if len(s.Module(dstModule).Resources) != 1 { t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources)) } got := s.ResourceInstance(dst) if got == nil { t.Fatalf("dst resource not in state") } }) } func TestState_MaybeMoveAbsResourceInstance(t *testing.T) { state := NewState() rootModule := state.RootModule() rootModule.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) // For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1] src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance) dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance) // First move, success t.Run("first move", func(t *testing.T) { moved := state.MaybeMoveAbsResourceInstance(src, dst) if !moved { t.Fatal("wrong result") } got := state.ResourceInstance(dst) if got == nil { t.Fatal("destination resource instance not in state") } }) // Moving a resource instance that doesn't exist in state to a resource which does exist should be a noop. t.Run("noop", func(t *testing.T) { moved := state.MaybeMoveAbsResourceInstance(src, dst) if moved { t.Fatal("wrong result") } }) } func TestState_MoveModuleInstance(t *testing.T) { state := NewState() srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey) m := state.EnsureModule(srcModule) m.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3)) state.MoveModuleInstance(srcModule, dstModule) // srcModule should have been removed, dstModule should exist and have one resource if len(state.Modules) != 2 { // kinder[3] and root t.Fatalf("wrong number of modules in state. Expected 2, got %d", len(state.Modules)) } got := state.Module(dstModule) if got == nil { t.Fatal("dstModule not found") } gone := state.Module(srcModule) if gone != nil { t.Fatal("srcModule not removed from state") } r := got.Resource(mustAbsResourceAddr("test_thing.foo").Resource) if r.Addr.Module.String() != dstModule.String() { fmt.Println(r.Addr.Module.String()) t.Fatal("resource address was not updated") } } func TestState_MaybeMoveModuleInstance(t *testing.T) { state := NewState() src := addrs.RootModuleInstance.Child("child", addrs.StringKey("a")) cm := state.EnsureModule(src) cm.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b")) // First move, success t.Run("first move", func(t *testing.T) { moved := state.MaybeMoveModuleInstance(src, dst) if !moved { t.Fatal("wrong result") } }) // Second move, should be a noop t.Run("noop", func(t *testing.T) { moved := state.MaybeMoveModuleInstance(src, dst) if moved { t.Fatal("wrong result") } }) } func TestState_MoveModule(t *testing.T) { // For this test, add two module instances (kinder and kinder["a"]). // MoveModule(kinder) should move both instances. state := NewState() // starter state, should be copied by the subtests. srcModule := addrs.RootModule.Child("kinder") m := state.EnsureModule(srcModule.UnkeyedInstanceShim()) m.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a")) mi := state.EnsureModule(moduleInstance) mi.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) _, mc := srcModule.Call() src := mc.Absolute(addrs.RootModuleInstance.Child("kinder", addrs.NoKey)) t.Run("basic", func(t *testing.T) { s := state.DeepCopy() _, dstMC := addrs.RootModule.Child("child").Call() dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) s.MoveModule(src, dst) // srcModule should have been removed, dstModule should exist and have one resource if len(s.Modules) != 3 { // child, child["a"] and root t.Fatalf("wrong number of modules in state. Expected 3, got %d", len(s.Modules)) } got := s.Module(dst.Module) if got == nil { t.Fatal("dstModule not found") } got = s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) if got == nil { t.Fatal("dstModule instance \"a\" not found") } gone := s.Module(srcModule.UnkeyedInstanceShim()) if gone != nil { t.Fatal("srcModule not removed from state") } }) t.Run("nested modules", func(t *testing.T) { s := state.DeepCopy() // add a child module to module.kinder mi := mustParseModuleInstanceStr(`module.kinder.module.grand[1]`) m := s.EnsureModule(mi) m.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo", }.Instance(addrs.NoKey), &ResourceInstanceObjectSrc{ Status: ObjectReady, SchemaVersion: 1, AttrsJSON: []byte(`{"woozles":"confuzles"}`), }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) _, dstMC := addrs.RootModule.Child("child").Call() dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)) s.MoveModule(src, dst) moved := s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))) if moved == nil { t.Fatal("dstModule not found") } // The nested module's relative address should also have been updated nested := s.Module(mustParseModuleInstanceStr(`module.child.module.grand[1]`)) if nested == nil { t.Fatal("nested child module of src wasn't moved") } }) } func mustParseModuleInstanceStr(str string) addrs.ModuleInstance { addr, diags := addrs.ParseModuleInstanceStr(str) if diags.HasErrors() { panic(diags.Err()) } return addr } func mustAbsResourceAddr(s string) addrs.AbsResource { addr, diags := addrs.ParseAbsResourceStr(s) if diags.HasErrors() { panic(diags.Err()) } return addr }