github.com/opentofu/opentofu@v1.7.1/internal/states/state_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package states
     7  
     8  import (
     9  	"fmt"
    10  	"reflect"
    11  	"testing"
    12  
    13  	"github.com/go-test/deep"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/opentofu/opentofu/internal/addrs"
    17  	"github.com/opentofu/opentofu/internal/lang/marks"
    18  )
    19  
    20  func TestState(t *testing.T) {
    21  	// This basic tests exercises the main mutation methods to construct
    22  	// a state. It is not fully comprehensive, so other tests should visit
    23  	// more esoteric codepaths.
    24  
    25  	state := NewState()
    26  
    27  	rootModule := state.RootModule()
    28  	if rootModule == nil {
    29  		t.Errorf("root module is nil; want valid object")
    30  	}
    31  
    32  	rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
    33  	rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
    34  	rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
    35  	rootModule.SetResourceInstanceCurrent(
    36  		addrs.Resource{
    37  			Mode: addrs.ManagedResourceMode,
    38  			Type: "test_thing",
    39  			Name: "baz",
    40  		}.Instance(addrs.IntKey(0)),
    41  		&ResourceInstanceObjectSrc{
    42  			Status:        ObjectReady,
    43  			SchemaVersion: 1,
    44  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
    45  		},
    46  		addrs.AbsProviderConfig{
    47  			Provider: addrs.NewDefaultProvider("test"),
    48  			Module:   addrs.RootModule,
    49  		},
    50  	)
    51  
    52  	childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
    53  	childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
    54  	multiModA := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")))
    55  	multiModA.SetOutputValue("pizza", cty.StringVal("cheese"), false)
    56  	multiModB := state.EnsureModule(addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")))
    57  	multiModB.SetOutputValue("pizza", cty.StringVal("sausage"), false)
    58  
    59  	want := &State{
    60  		Modules: map[string]*Module{
    61  			"": {
    62  				Addr: addrs.RootModuleInstance,
    63  				LocalValues: map[string]cty.Value{
    64  					"foo": cty.StringVal("foo value"),
    65  				},
    66  				OutputValues: map[string]*OutputValue{
    67  					"bar": {
    68  						Addr: addrs.AbsOutputValue{
    69  							OutputValue: addrs.OutputValue{
    70  								Name: "bar",
    71  							},
    72  						},
    73  						Value:     cty.StringVal("bar value"),
    74  						Sensitive: false,
    75  					},
    76  					"secret": {
    77  						Addr: addrs.AbsOutputValue{
    78  							OutputValue: addrs.OutputValue{
    79  								Name: "secret",
    80  							},
    81  						},
    82  						Value:     cty.StringVal("secret value"),
    83  						Sensitive: true,
    84  					},
    85  				},
    86  				Resources: map[string]*Resource{
    87  					"test_thing.baz": {
    88  						Addr: addrs.Resource{
    89  							Mode: addrs.ManagedResourceMode,
    90  							Type: "test_thing",
    91  							Name: "baz",
    92  						}.Absolute(addrs.RootModuleInstance),
    93  
    94  						Instances: map[addrs.InstanceKey]*ResourceInstance{
    95  							addrs.IntKey(0): {
    96  								Current: &ResourceInstanceObjectSrc{
    97  									SchemaVersion: 1,
    98  									Status:        ObjectReady,
    99  									AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   100  								},
   101  								Deposed: map[DeposedKey]*ResourceInstanceObjectSrc{},
   102  							},
   103  						},
   104  						ProviderConfig: addrs.AbsProviderConfig{
   105  							Provider: addrs.NewDefaultProvider("test"),
   106  							Module:   addrs.RootModule,
   107  						},
   108  					},
   109  				},
   110  			},
   111  			"module.child": {
   112  				Addr:        addrs.RootModuleInstance.Child("child", addrs.NoKey),
   113  				LocalValues: map[string]cty.Value{},
   114  				OutputValues: map[string]*OutputValue{
   115  					"pizza": {
   116  						Addr: addrs.AbsOutputValue{
   117  							Module: addrs.RootModuleInstance.Child("child", addrs.NoKey),
   118  							OutputValue: addrs.OutputValue{
   119  								Name: "pizza",
   120  							},
   121  						},
   122  						Value:     cty.StringVal("hawaiian"),
   123  						Sensitive: false,
   124  					},
   125  				},
   126  				Resources: map[string]*Resource{},
   127  			},
   128  			`module.multi["a"]`: {
   129  				Addr:        addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
   130  				LocalValues: map[string]cty.Value{},
   131  				OutputValues: map[string]*OutputValue{
   132  					"pizza": {
   133  						Addr: addrs.AbsOutputValue{
   134  							Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("a")),
   135  							OutputValue: addrs.OutputValue{
   136  								Name: "pizza",
   137  							},
   138  						},
   139  						Value:     cty.StringVal("cheese"),
   140  						Sensitive: false,
   141  					},
   142  				},
   143  				Resources: map[string]*Resource{},
   144  			},
   145  			`module.multi["b"]`: {
   146  				Addr:        addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
   147  				LocalValues: map[string]cty.Value{},
   148  				OutputValues: map[string]*OutputValue{
   149  					"pizza": {
   150  						Addr: addrs.AbsOutputValue{
   151  							Module: addrs.RootModuleInstance.Child("multi", addrs.StringKey("b")),
   152  							OutputValue: addrs.OutputValue{
   153  								Name: "pizza",
   154  							},
   155  						},
   156  						Value:     cty.StringVal("sausage"),
   157  						Sensitive: false,
   158  					},
   159  				},
   160  				Resources: map[string]*Resource{},
   161  			},
   162  		},
   163  	}
   164  
   165  	{
   166  		// Our structure goes deep, so we need to temporarily override the
   167  		// deep package settings to ensure that we visit the full structure.
   168  		oldDeepDepth := deep.MaxDepth
   169  		oldDeepCompareUnexp := deep.CompareUnexportedFields
   170  		deep.MaxDepth = 50
   171  		deep.CompareUnexportedFields = true
   172  		defer func() {
   173  			deep.MaxDepth = oldDeepDepth
   174  			deep.CompareUnexportedFields = oldDeepCompareUnexp
   175  		}()
   176  	}
   177  
   178  	for _, problem := range deep.Equal(state, want) {
   179  		t.Error(problem)
   180  	}
   181  
   182  	expectedOutputs := map[string]string{
   183  		`module.multi["a"].output.pizza`: "cheese",
   184  		`module.multi["b"].output.pizza`: "sausage",
   185  	}
   186  
   187  	for _, o := range state.ModuleOutputs(addrs.RootModuleInstance, addrs.ModuleCall{Name: "multi"}) {
   188  		addr := o.Addr.String()
   189  		expected := expectedOutputs[addr]
   190  		delete(expectedOutputs, addr)
   191  
   192  		if expected != o.Value.AsString() {
   193  			t.Fatalf("expected %q:%q, got %q", addr, expected, o.Value.AsString())
   194  		}
   195  	}
   196  
   197  	for addr, o := range expectedOutputs {
   198  		t.Fatalf("missing output %q:%q", addr, o)
   199  	}
   200  }
   201  
   202  func TestStateDeepCopyObject(t *testing.T) {
   203  	obj := &ResourceInstanceObject{
   204  		Value: cty.ObjectVal(map[string]cty.Value{
   205  			"id": cty.StringVal("id"),
   206  		}),
   207  		Private: []byte("private"),
   208  		Status:  ObjectReady,
   209  		Dependencies: []addrs.ConfigResource{
   210  			{
   211  				Module: addrs.RootModule,
   212  				Resource: addrs.Resource{
   213  					Mode: addrs.ManagedResourceMode,
   214  					Type: "test_instance",
   215  					Name: "bar",
   216  				},
   217  			},
   218  		},
   219  		CreateBeforeDestroy: true,
   220  	}
   221  
   222  	objCopy := obj.DeepCopy()
   223  	if !reflect.DeepEqual(obj, objCopy) {
   224  		t.Fatalf("not equal\n%#v\n%#v", obj, objCopy)
   225  	}
   226  }
   227  
   228  func TestStateDeepCopy(t *testing.T) {
   229  	state := NewState()
   230  
   231  	rootModule := state.RootModule()
   232  	if rootModule == nil {
   233  		t.Errorf("root module is nil; want valid object")
   234  	}
   235  
   236  	rootModule.SetLocalValue("foo", cty.StringVal("foo value"))
   237  	rootModule.SetOutputValue("bar", cty.StringVal("bar value"), false)
   238  	rootModule.SetOutputValue("secret", cty.StringVal("secret value"), true)
   239  	rootModule.SetResourceInstanceCurrent(
   240  		addrs.Resource{
   241  			Mode: addrs.ManagedResourceMode,
   242  			Type: "test_thing",
   243  			Name: "baz",
   244  		}.Instance(addrs.IntKey(0)),
   245  		&ResourceInstanceObjectSrc{
   246  			Status:              ObjectReady,
   247  			SchemaVersion:       1,
   248  			AttrsJSON:           []byte(`{"woozles":"confuzles"}`),
   249  			Private:             []byte("private data"),
   250  			Dependencies:        []addrs.ConfigResource{},
   251  			CreateBeforeDestroy: true,
   252  		},
   253  		addrs.AbsProviderConfig{
   254  			Provider: addrs.NewDefaultProvider("test"),
   255  			Module:   addrs.RootModule,
   256  		},
   257  	)
   258  	rootModule.SetResourceInstanceCurrent(
   259  		addrs.Resource{
   260  			Mode: addrs.ManagedResourceMode,
   261  			Type: "test_thing",
   262  			Name: "bar",
   263  		}.Instance(addrs.IntKey(0)),
   264  		&ResourceInstanceObjectSrc{
   265  			Status:        ObjectReady,
   266  			SchemaVersion: 1,
   267  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   268  			// Sensitive path at "woozles"
   269  			AttrSensitivePaths: []cty.PathValueMarks{
   270  				{
   271  					Path:  cty.Path{cty.GetAttrStep{Name: "woozles"}},
   272  					Marks: cty.NewValueMarks(marks.Sensitive),
   273  				},
   274  			},
   275  			Private: []byte("private data"),
   276  			Dependencies: []addrs.ConfigResource{
   277  				{
   278  					Module: addrs.RootModule,
   279  					Resource: addrs.Resource{
   280  						Mode: addrs.ManagedResourceMode,
   281  						Type: "test_thing",
   282  						Name: "baz",
   283  					},
   284  				},
   285  			},
   286  		},
   287  		addrs.AbsProviderConfig{
   288  			Provider: addrs.NewDefaultProvider("test"),
   289  			Module:   addrs.RootModule,
   290  		},
   291  	)
   292  
   293  	childModule := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   294  	childModule.SetOutputValue("pizza", cty.StringVal("hawaiian"), false)
   295  
   296  	stateCopy := state.DeepCopy()
   297  	if !state.Equal(stateCopy) {
   298  		t.Fatalf("\nexpected:\n%q\ngot:\n%q\n", state, stateCopy)
   299  	}
   300  }
   301  
   302  func TestStateHasResourceInstanceObjects(t *testing.T) {
   303  	providerConfig := addrs.AbsProviderConfig{
   304  		Module:   addrs.RootModule,
   305  		Provider: addrs.MustParseProviderSourceString("test/test"),
   306  	}
   307  	childModuleProviderConfig := addrs.AbsProviderConfig{
   308  		Module:   addrs.RootModule.Child("child"),
   309  		Provider: addrs.MustParseProviderSourceString("test/test"),
   310  	}
   311  
   312  	tests := map[string]struct {
   313  		Setup func(ss *SyncState)
   314  		Want  bool
   315  	}{
   316  		"empty": {
   317  			func(ss *SyncState) {},
   318  			false,
   319  		},
   320  		"one current, ready object in root module": {
   321  			func(ss *SyncState) {
   322  				ss.SetResourceInstanceCurrent(
   323  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   324  					&ResourceInstanceObjectSrc{
   325  						AttrsJSON: []byte(`{}`),
   326  						Status:    ObjectReady,
   327  					},
   328  					providerConfig,
   329  				)
   330  			},
   331  			true,
   332  		},
   333  		"one current, ready object in child module": {
   334  			func(ss *SyncState) {
   335  				ss.SetResourceInstanceCurrent(
   336  					mustAbsResourceAddr("module.child.test.foo").Instance(addrs.NoKey),
   337  					&ResourceInstanceObjectSrc{
   338  						AttrsJSON: []byte(`{}`),
   339  						Status:    ObjectReady,
   340  					},
   341  					childModuleProviderConfig,
   342  				)
   343  			},
   344  			true,
   345  		},
   346  		"one current, tainted object in root module": {
   347  			func(ss *SyncState) {
   348  				ss.SetResourceInstanceCurrent(
   349  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   350  					&ResourceInstanceObjectSrc{
   351  						AttrsJSON: []byte(`{}`),
   352  						Status:    ObjectTainted,
   353  					},
   354  					providerConfig,
   355  				)
   356  			},
   357  			true,
   358  		},
   359  		"one deposed, ready object in root module": {
   360  			func(ss *SyncState) {
   361  				ss.SetResourceInstanceDeposed(
   362  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   363  					DeposedKey("uhoh"),
   364  					&ResourceInstanceObjectSrc{
   365  						AttrsJSON: []byte(`{}`),
   366  						Status:    ObjectTainted,
   367  					},
   368  					providerConfig,
   369  				)
   370  			},
   371  			true,
   372  		},
   373  		"one empty resource husk in root module": {
   374  			func(ss *SyncState) {
   375  				// Current OpenTofu doesn't actually create resource husks
   376  				// as part of its everyday work, so this is a "should never
   377  				// happen" case but we'll test to make sure we're robust to
   378  				// it anyway, because this was a historical bug blocking
   379  				// "tofu workspace delete" and similar.
   380  				ss.SetResourceInstanceCurrent(
   381  					mustAbsResourceAddr("test.foo").Instance(addrs.NoKey),
   382  					&ResourceInstanceObjectSrc{
   383  						AttrsJSON: []byte(`{}`),
   384  						Status:    ObjectTainted,
   385  					},
   386  					providerConfig,
   387  				)
   388  				s := ss.Lock()
   389  				delete(s.Modules[""].Resources["test.foo"].Instances, addrs.NoKey)
   390  				ss.Unlock()
   391  			},
   392  			false,
   393  		},
   394  		"one current data resource object in root module": {
   395  			func(ss *SyncState) {
   396  				ss.SetResourceInstanceCurrent(
   397  					mustAbsResourceAddr("data.test.foo").Instance(addrs.NoKey),
   398  					&ResourceInstanceObjectSrc{
   399  						AttrsJSON: []byte(`{}`),
   400  						Status:    ObjectReady,
   401  					},
   402  					providerConfig,
   403  				)
   404  			},
   405  			false, // data resources aren't managed resources, so they don't count
   406  		},
   407  	}
   408  
   409  	for name, test := range tests {
   410  		t.Run(name, func(t *testing.T) {
   411  			state := BuildState(test.Setup)
   412  			got := state.HasManagedResourceInstanceObjects()
   413  			if got != test.Want {
   414  				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)
   415  			}
   416  		})
   417  	}
   418  
   419  }
   420  
   421  func TestState_MoveAbsResource(t *testing.T) {
   422  	// Set up a starter state for the embedded tests, which should start from a copy of this state.
   423  	state := NewState()
   424  	rootModule := state.RootModule()
   425  	rootModule.SetResourceInstanceCurrent(
   426  		addrs.Resource{
   427  			Mode: addrs.ManagedResourceMode,
   428  			Type: "test_thing",
   429  			Name: "foo",
   430  		}.Instance(addrs.IntKey(0)),
   431  		&ResourceInstanceObjectSrc{
   432  			Status:        ObjectReady,
   433  			SchemaVersion: 1,
   434  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   435  		},
   436  		addrs.AbsProviderConfig{
   437  			Provider: addrs.NewDefaultProvider("test"),
   438  			Module:   addrs.RootModule,
   439  		},
   440  	)
   441  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance)
   442  
   443  	t.Run("basic move", func(t *testing.T) {
   444  		s := state.DeepCopy()
   445  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance)
   446  
   447  		s.MoveAbsResource(src, dst)
   448  
   449  		if s.Empty() {
   450  			t.Fatal("unexpected empty state")
   451  		}
   452  
   453  		if len(s.RootModule().Resources) != 1 {
   454  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources))
   455  		}
   456  
   457  		got := s.Resource(dst)
   458  		if got.Addr.Resource != dst.Resource {
   459  			t.Fatalf("dst resource not in state")
   460  		}
   461  	})
   462  
   463  	t.Run("move to new module", func(t *testing.T) {
   464  		s := state.DeepCopy()
   465  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("one"))
   466  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(dstModule)
   467  
   468  		s.MoveAbsResource(src, dst)
   469  
   470  		if s.Empty() {
   471  			t.Fatal("unexpected empty state")
   472  		}
   473  
   474  		if s.Module(dstModule) == nil {
   475  			t.Fatalf("child module %s not in state", dstModule.String())
   476  		}
   477  
   478  		if len(s.Module(dstModule).Resources) != 1 {
   479  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources))
   480  		}
   481  
   482  		got := s.Resource(dst)
   483  		if got.Addr.Resource != dst.Resource {
   484  			t.Fatalf("dst resource not in state")
   485  		}
   486  	})
   487  
   488  	t.Run("from a child module to root", func(t *testing.T) {
   489  		s := state.DeepCopy()
   490  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   491  		cm := s.EnsureModule(srcModule)
   492  		cm.SetResourceInstanceCurrent(
   493  			addrs.Resource{
   494  				Mode: addrs.ManagedResourceMode,
   495  				Type: "test_thing",
   496  				Name: "child",
   497  			}.Instance(addrs.IntKey(0)), // Moving the AbsResouce moves all instances
   498  			&ResourceInstanceObjectSrc{
   499  				Status:        ObjectReady,
   500  				SchemaVersion: 1,
   501  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   502  			},
   503  			addrs.AbsProviderConfig{
   504  				Provider: addrs.NewDefaultProvider("test"),
   505  				Module:   addrs.RootModule,
   506  			},
   507  		)
   508  		cm.SetResourceInstanceCurrent(
   509  			addrs.Resource{
   510  				Mode: addrs.ManagedResourceMode,
   511  				Type: "test_thing",
   512  				Name: "child",
   513  			}.Instance(addrs.IntKey(1)), // Moving the AbsResouce moves all instances
   514  			&ResourceInstanceObjectSrc{
   515  				Status:        ObjectReady,
   516  				SchemaVersion: 1,
   517  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   518  			},
   519  			addrs.AbsProviderConfig{
   520  				Provider: addrs.NewDefaultProvider("test"),
   521  				Module:   addrs.RootModule,
   522  			},
   523  		)
   524  
   525  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   526  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(addrs.RootModuleInstance)
   527  		s.MoveAbsResource(src, dst)
   528  
   529  		if s.Empty() {
   530  			t.Fatal("unexpected empty state")
   531  		}
   532  
   533  		// The child module should have been removed after removing its only resource
   534  		if s.Module(srcModule) != nil {
   535  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   536  		}
   537  
   538  		if len(s.RootModule().Resources) != 2 {
   539  			t.Fatalf("wrong number of resources in state; expected 2, found %d", len(s.RootModule().Resources))
   540  		}
   541  
   542  		if len(s.Resource(dst).Instances) != 2 {
   543  			t.Fatalf("wrong number of resource instances for dst, got %d expected 2", len(s.Resource(dst).Instances))
   544  		}
   545  
   546  		got := s.Resource(dst)
   547  		if got.Addr.Resource != dst.Resource {
   548  			t.Fatalf("dst resource not in state")
   549  		}
   550  	})
   551  
   552  	t.Run("module to new module", func(t *testing.T) {
   553  		s := NewState()
   554  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists"))
   555  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new"))
   556  		cm := s.EnsureModule(srcModule)
   557  		cm.SetResourceInstanceCurrent(
   558  			addrs.Resource{
   559  				Mode: addrs.ManagedResourceMode,
   560  				Type: "test_thing",
   561  				Name: "child",
   562  			}.Instance(addrs.NoKey),
   563  			&ResourceInstanceObjectSrc{
   564  				Status:        ObjectReady,
   565  				SchemaVersion: 1,
   566  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   567  			},
   568  			addrs.AbsProviderConfig{
   569  				Provider: addrs.NewDefaultProvider("test"),
   570  				Module:   addrs.RootModule,
   571  			},
   572  		)
   573  
   574  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   575  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule)
   576  		s.MoveAbsResource(src, dst)
   577  
   578  		if s.Empty() {
   579  			t.Fatal("unexpected empty state")
   580  		}
   581  
   582  		// The child module should have been removed after removing its only resource
   583  		if s.Module(srcModule) != nil {
   584  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   585  		}
   586  
   587  		gotMod := s.Module(dstModule)
   588  		if len(gotMod.Resources) != 1 {
   589  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources))
   590  		}
   591  
   592  		got := s.Resource(dst)
   593  		if got.Addr.Resource != dst.Resource {
   594  			t.Fatalf("dst resource not in state")
   595  		}
   596  	})
   597  
   598  	t.Run("module to new module", func(t *testing.T) {
   599  		s := NewState()
   600  		srcModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("exists"))
   601  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("new"))
   602  		cm := s.EnsureModule(srcModule)
   603  		cm.SetResourceInstanceCurrent(
   604  			addrs.Resource{
   605  				Mode: addrs.ManagedResourceMode,
   606  				Type: "test_thing",
   607  				Name: "child",
   608  			}.Instance(addrs.NoKey),
   609  			&ResourceInstanceObjectSrc{
   610  				Status:        ObjectReady,
   611  				SchemaVersion: 1,
   612  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   613  			},
   614  			addrs.AbsProviderConfig{
   615  				Provider: addrs.NewDefaultProvider("test"),
   616  				Module:   addrs.RootModule,
   617  			},
   618  		)
   619  
   620  		src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(srcModule)
   621  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "child"}.Absolute(dstModule)
   622  		s.MoveAbsResource(src, dst)
   623  
   624  		if s.Empty() {
   625  			t.Fatal("unexpected empty state")
   626  		}
   627  
   628  		// The child module should have been removed after removing its only resource
   629  		if s.Module(srcModule) != nil {
   630  			t.Fatalf("child module %s was not removed from state after mv", srcModule.String())
   631  		}
   632  
   633  		gotMod := s.Module(dstModule)
   634  		if len(gotMod.Resources) != 1 {
   635  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(gotMod.Resources))
   636  		}
   637  
   638  		got := s.Resource(dst)
   639  		if got.Addr.Resource != dst.Resource {
   640  			t.Fatalf("dst resource not in state")
   641  		}
   642  	})
   643  }
   644  
   645  func TestState_MaybeMoveAbsResource(t *testing.T) {
   646  	state := NewState()
   647  	rootModule := state.RootModule()
   648  	rootModule.SetResourceInstanceCurrent(
   649  		addrs.Resource{
   650  			Mode: addrs.ManagedResourceMode,
   651  			Type: "test_thing",
   652  			Name: "foo",
   653  		}.Instance(addrs.IntKey(0)),
   654  		&ResourceInstanceObjectSrc{
   655  			Status:        ObjectReady,
   656  			SchemaVersion: 1,
   657  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   658  		},
   659  		addrs.AbsProviderConfig{
   660  			Provider: addrs.NewDefaultProvider("test"),
   661  			Module:   addrs.RootModule,
   662  		},
   663  	)
   664  
   665  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Absolute(addrs.RootModuleInstance)
   666  	dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "bar"}.Absolute(addrs.RootModuleInstance)
   667  
   668  	// First move, success
   669  	t.Run("first move", func(t *testing.T) {
   670  		moved := state.MaybeMoveAbsResource(src, dst)
   671  		if !moved {
   672  			t.Fatal("wrong result")
   673  		}
   674  	})
   675  
   676  	// Trying to move a resource that doesn't exist in state to a resource which does exist should be a noop.
   677  	t.Run("noop", func(t *testing.T) {
   678  		moved := state.MaybeMoveAbsResource(src, dst)
   679  		if moved {
   680  			t.Fatal("wrong result")
   681  		}
   682  	})
   683  }
   684  
   685  func TestState_MoveAbsResourceInstance(t *testing.T) {
   686  	state := NewState()
   687  	rootModule := state.RootModule()
   688  	rootModule.SetResourceInstanceCurrent(
   689  		addrs.Resource{
   690  			Mode: addrs.ManagedResourceMode,
   691  			Type: "test_thing",
   692  			Name: "foo",
   693  		}.Instance(addrs.NoKey),
   694  		&ResourceInstanceObjectSrc{
   695  			Status:        ObjectReady,
   696  			SchemaVersion: 1,
   697  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   698  		},
   699  		addrs.AbsProviderConfig{
   700  			Provider: addrs.NewDefaultProvider("test"),
   701  			Module:   addrs.RootModule,
   702  		},
   703  	)
   704  	// src resource from the state above
   705  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   706  
   707  	t.Run("resource to resource instance", func(t *testing.T) {
   708  		s := state.DeepCopy()
   709  		// For a little extra fun, move a resource to a resource instance: test_thing.foo to test_thing.foo[1]
   710  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance)
   711  
   712  		s.MoveAbsResourceInstance(src, dst)
   713  
   714  		if s.Empty() {
   715  			t.Fatal("unexpected empty state")
   716  		}
   717  
   718  		if len(s.RootModule().Resources) != 1 {
   719  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(state.RootModule().Resources))
   720  		}
   721  
   722  		got := s.ResourceInstance(dst)
   723  		if got == nil {
   724  			t.Fatalf("dst resource not in state")
   725  		}
   726  	})
   727  
   728  	t.Run("move to new module", func(t *testing.T) {
   729  		s := state.DeepCopy()
   730  		// test_thing.foo to module.kinder.test_thing.foo["baz"]
   731  		dstModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   732  		dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(dstModule)
   733  
   734  		s.MoveAbsResourceInstance(src, dst)
   735  
   736  		if s.Empty() {
   737  			t.Fatal("unexpected empty state")
   738  		}
   739  
   740  		if s.Module(dstModule) == nil {
   741  			t.Fatalf("child module %s not in state", dstModule.String())
   742  		}
   743  
   744  		if len(s.Module(dstModule).Resources) != 1 {
   745  			t.Fatalf("wrong number of resources in state; expected 1, found %d", len(s.Module(dstModule).Resources))
   746  		}
   747  
   748  		got := s.ResourceInstance(dst)
   749  		if got == nil {
   750  			t.Fatalf("dst resource not in state")
   751  		}
   752  	})
   753  }
   754  
   755  func TestState_MaybeMoveAbsResourceInstance(t *testing.T) {
   756  	state := NewState()
   757  	rootModule := state.RootModule()
   758  	rootModule.SetResourceInstanceCurrent(
   759  		addrs.Resource{
   760  			Mode: addrs.ManagedResourceMode,
   761  			Type: "test_thing",
   762  			Name: "foo",
   763  		}.Instance(addrs.NoKey),
   764  		&ResourceInstanceObjectSrc{
   765  			Status:        ObjectReady,
   766  			SchemaVersion: 1,
   767  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   768  		},
   769  		addrs.AbsProviderConfig{
   770  			Provider: addrs.NewDefaultProvider("test"),
   771  			Module:   addrs.RootModule,
   772  		},
   773  	)
   774  
   775  	// For a little extra fun, let's go from a resource to a resource instance: test_thing.foo to test_thing.bar[1]
   776  	src := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance)
   777  	dst := addrs.Resource{Mode: addrs.ManagedResourceMode, Type: "test_thing", Name: "foo"}.Instance(addrs.IntKey(1)).Absolute(addrs.RootModuleInstance)
   778  
   779  	// First move, success
   780  	t.Run("first move", func(t *testing.T) {
   781  		moved := state.MaybeMoveAbsResourceInstance(src, dst)
   782  		if !moved {
   783  			t.Fatal("wrong result")
   784  		}
   785  		got := state.ResourceInstance(dst)
   786  		if got == nil {
   787  			t.Fatal("destination resource instance not in state")
   788  		}
   789  	})
   790  
   791  	// Moving a resource instance that doesn't exist in state to a resource which does exist should be a noop.
   792  	t.Run("noop", func(t *testing.T) {
   793  		moved := state.MaybeMoveAbsResourceInstance(src, dst)
   794  		if moved {
   795  			t.Fatal("wrong result")
   796  		}
   797  	})
   798  }
   799  
   800  func TestState_MoveModuleInstance(t *testing.T) {
   801  	state := NewState()
   802  	srcModule := addrs.RootModuleInstance.Child("kinder", addrs.NoKey)
   803  	m := state.EnsureModule(srcModule)
   804  	m.SetResourceInstanceCurrent(
   805  		addrs.Resource{
   806  			Mode: addrs.ManagedResourceMode,
   807  			Type: "test_thing",
   808  			Name: "foo",
   809  		}.Instance(addrs.NoKey),
   810  		&ResourceInstanceObjectSrc{
   811  			Status:        ObjectReady,
   812  			SchemaVersion: 1,
   813  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   814  		},
   815  		addrs.AbsProviderConfig{
   816  			Provider: addrs.NewDefaultProvider("test"),
   817  			Module:   addrs.RootModule,
   818  		},
   819  	)
   820  
   821  	dstModule := addrs.RootModuleInstance.Child("child", addrs.IntKey(3))
   822  	state.MoveModuleInstance(srcModule, dstModule)
   823  
   824  	// srcModule should have been removed, dstModule should exist and have one resource
   825  	if len(state.Modules) != 2 { // kinder[3] and root
   826  		t.Fatalf("wrong number of modules in state. Expected 2, got %d", len(state.Modules))
   827  	}
   828  
   829  	got := state.Module(dstModule)
   830  	if got == nil {
   831  		t.Fatal("dstModule not found")
   832  	}
   833  
   834  	gone := state.Module(srcModule)
   835  	if gone != nil {
   836  		t.Fatal("srcModule not removed from state")
   837  	}
   838  
   839  	r := got.Resource(mustAbsResourceAddr("test_thing.foo").Resource)
   840  	if r.Addr.Module.String() != dstModule.String() {
   841  		fmt.Println(r.Addr.Module.String())
   842  		t.Fatal("resource address was not updated")
   843  	}
   844  
   845  }
   846  
   847  func TestState_MaybeMoveModuleInstance(t *testing.T) {
   848  	state := NewState()
   849  	src := addrs.RootModuleInstance.Child("child", addrs.StringKey("a"))
   850  	cm := state.EnsureModule(src)
   851  	cm.SetResourceInstanceCurrent(
   852  		addrs.Resource{
   853  			Mode: addrs.ManagedResourceMode,
   854  			Type: "test_thing",
   855  			Name: "foo",
   856  		}.Instance(addrs.NoKey),
   857  		&ResourceInstanceObjectSrc{
   858  			Status:        ObjectReady,
   859  			SchemaVersion: 1,
   860  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   861  		},
   862  		addrs.AbsProviderConfig{
   863  			Provider: addrs.NewDefaultProvider("test"),
   864  			Module:   addrs.RootModule,
   865  		},
   866  	)
   867  
   868  	dst := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("b"))
   869  
   870  	// First move, success
   871  	t.Run("first move", func(t *testing.T) {
   872  		moved := state.MaybeMoveModuleInstance(src, dst)
   873  		if !moved {
   874  			t.Fatal("wrong result")
   875  		}
   876  	})
   877  
   878  	// Second move, should be a noop
   879  	t.Run("noop", func(t *testing.T) {
   880  		moved := state.MaybeMoveModuleInstance(src, dst)
   881  		if moved {
   882  			t.Fatal("wrong result")
   883  		}
   884  	})
   885  }
   886  
   887  func TestState_MoveModule(t *testing.T) {
   888  	// For this test, add two module instances (kinder and kinder["a"]).
   889  	// MoveModule(kinder) should move both instances.
   890  	state := NewState() // starter state, should be copied by the subtests.
   891  	srcModule := addrs.RootModule.Child("kinder")
   892  	m := state.EnsureModule(srcModule.UnkeyedInstanceShim())
   893  	m.SetResourceInstanceCurrent(
   894  		addrs.Resource{
   895  			Mode: addrs.ManagedResourceMode,
   896  			Type: "test_thing",
   897  			Name: "foo",
   898  		}.Instance(addrs.NoKey),
   899  		&ResourceInstanceObjectSrc{
   900  			Status:        ObjectReady,
   901  			SchemaVersion: 1,
   902  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   903  		},
   904  		addrs.AbsProviderConfig{
   905  			Provider: addrs.NewDefaultProvider("test"),
   906  			Module:   addrs.RootModule,
   907  		},
   908  	)
   909  
   910  	moduleInstance := addrs.RootModuleInstance.Child("kinder", addrs.StringKey("a"))
   911  	mi := state.EnsureModule(moduleInstance)
   912  	mi.SetResourceInstanceCurrent(
   913  		addrs.Resource{
   914  			Mode: addrs.ManagedResourceMode,
   915  			Type: "test_thing",
   916  			Name: "foo",
   917  		}.Instance(addrs.NoKey),
   918  		&ResourceInstanceObjectSrc{
   919  			Status:        ObjectReady,
   920  			SchemaVersion: 1,
   921  			AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   922  		},
   923  		addrs.AbsProviderConfig{
   924  			Provider: addrs.NewDefaultProvider("test"),
   925  			Module:   addrs.RootModule,
   926  		},
   927  	)
   928  
   929  	_, mc := srcModule.Call()
   930  	src := mc.Absolute(addrs.RootModuleInstance.Child("kinder", addrs.NoKey))
   931  
   932  	t.Run("basic", func(t *testing.T) {
   933  		s := state.DeepCopy()
   934  		_, dstMC := addrs.RootModule.Child("child").Call()
   935  		dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   936  		s.MoveModule(src, dst)
   937  
   938  		// srcModule should have been removed, dstModule should exist and have one resource
   939  		if len(s.Modules) != 3 { // child, child["a"] and root
   940  			t.Fatalf("wrong number of modules in state. Expected 3, got %d", len(s.Modules))
   941  		}
   942  
   943  		got := s.Module(dst.Module)
   944  		if got == nil {
   945  			t.Fatal("dstModule not found")
   946  		}
   947  
   948  		got = s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a")))
   949  		if got == nil {
   950  			t.Fatal("dstModule instance \"a\" not found")
   951  		}
   952  
   953  		gone := s.Module(srcModule.UnkeyedInstanceShim())
   954  		if gone != nil {
   955  			t.Fatal("srcModule not removed from state")
   956  		}
   957  	})
   958  
   959  	t.Run("nested modules", func(t *testing.T) {
   960  		s := state.DeepCopy()
   961  
   962  		// add a child module to module.kinder
   963  		mi := mustParseModuleInstanceStr(`module.kinder.module.grand[1]`)
   964  		m := s.EnsureModule(mi)
   965  		m.SetResourceInstanceCurrent(
   966  			addrs.Resource{
   967  				Mode: addrs.ManagedResourceMode,
   968  				Type: "test_thing",
   969  				Name: "foo",
   970  			}.Instance(addrs.NoKey),
   971  			&ResourceInstanceObjectSrc{
   972  				Status:        ObjectReady,
   973  				SchemaVersion: 1,
   974  				AttrsJSON:     []byte(`{"woozles":"confuzles"}`),
   975  			},
   976  			addrs.AbsProviderConfig{
   977  				Provider: addrs.NewDefaultProvider("test"),
   978  				Module:   addrs.RootModule,
   979  			},
   980  		)
   981  
   982  		_, dstMC := addrs.RootModule.Child("child").Call()
   983  		dst := dstMC.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   984  		s.MoveModule(src, dst)
   985  
   986  		moved := s.Module(addrs.RootModuleInstance.Child("child", addrs.StringKey("a")))
   987  		if moved == nil {
   988  			t.Fatal("dstModule not found")
   989  		}
   990  
   991  		// The nested module's relative address should also have been updated
   992  		nested := s.Module(mustParseModuleInstanceStr(`module.child.module.grand[1]`))
   993  		if nested == nil {
   994  			t.Fatal("nested child module of src wasn't moved")
   995  		}
   996  	})
   997  }
   998  
   999  func mustParseModuleInstanceStr(str string) addrs.ModuleInstance {
  1000  	addr, diags := addrs.ParseModuleInstanceStr(str)
  1001  	if diags.HasErrors() {
  1002  		panic(diags.Err())
  1003  	}
  1004  	return addr
  1005  }
  1006  
  1007  func mustAbsResourceAddr(s string) addrs.AbsResource {
  1008  	addr, diags := addrs.ParseAbsResourceStr(s)
  1009  	if diags.HasErrors() {
  1010  		panic(diags.Err())
  1011  	}
  1012  	return addr
  1013  }