github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/state_test.go (about)

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