github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/terraform/graph_builder_apply_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"testing"
     7  
     8  	"github.com/hashicorp/terraform/internal/addrs"
     9  	"github.com/hashicorp/terraform/internal/plans"
    10  	"github.com/hashicorp/terraform/internal/states"
    11  	"github.com/zclconf/go-cty/cty"
    12  )
    13  
    14  func TestApplyGraphBuilder_impl(t *testing.T) {
    15  	var _ GraphBuilder = new(ApplyGraphBuilder)
    16  }
    17  
    18  func TestApplyGraphBuilder(t *testing.T) {
    19  	changes := &plans.Changes{
    20  		Resources: []*plans.ResourceInstanceChangeSrc{
    21  			{
    22  				Addr: mustResourceInstanceAddr("test_object.create"),
    23  				ChangeSrc: plans.ChangeSrc{
    24  					Action: plans.Create,
    25  				},
    26  			},
    27  			{
    28  				Addr: mustResourceInstanceAddr("test_object.other"),
    29  				ChangeSrc: plans.ChangeSrc{
    30  					Action: plans.Update,
    31  				},
    32  			},
    33  			{
    34  				Addr: mustResourceInstanceAddr("module.child.test_object.create"),
    35  				ChangeSrc: plans.ChangeSrc{
    36  					Action: plans.Create,
    37  				},
    38  			},
    39  			{
    40  				Addr: mustResourceInstanceAddr("module.child.test_object.other"),
    41  				ChangeSrc: plans.ChangeSrc{
    42  					Action: plans.Create,
    43  				},
    44  			},
    45  		},
    46  	}
    47  
    48  	b := &ApplyGraphBuilder{
    49  		Config:  testModule(t, "graph-builder-apply-basic"),
    50  		Changes: changes,
    51  		Plugins: simpleMockPluginLibrary(),
    52  	}
    53  
    54  	g, err := b.Build(addrs.RootModuleInstance)
    55  	if err != nil {
    56  		t.Fatalf("err: %s", err)
    57  	}
    58  
    59  	if g.Path.String() != addrs.RootModuleInstance.String() {
    60  		t.Fatalf("wrong path %q", g.Path.String())
    61  	}
    62  
    63  	actual := strings.TrimSpace(g.String())
    64  
    65  	expected := strings.TrimSpace(testApplyGraphBuilderStr)
    66  	if actual != expected {
    67  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
    68  	}
    69  }
    70  
    71  // This tests the ordering of two resources where a non-CBD depends
    72  // on a CBD. GH-11349.
    73  func TestApplyGraphBuilder_depCbd(t *testing.T) {
    74  	changes := &plans.Changes{
    75  		Resources: []*plans.ResourceInstanceChangeSrc{
    76  			{
    77  				Addr: mustResourceInstanceAddr("test_object.A"),
    78  				ChangeSrc: plans.ChangeSrc{
    79  					Action: plans.CreateThenDelete,
    80  				},
    81  			},
    82  			{
    83  				Addr: mustResourceInstanceAddr("test_object.B"),
    84  				ChangeSrc: plans.ChangeSrc{
    85  					Action: plans.Update,
    86  				},
    87  			},
    88  		},
    89  	}
    90  
    91  	state := states.NewState()
    92  	root := state.EnsureModule(addrs.RootModuleInstance)
    93  	root.SetResourceInstanceCurrent(
    94  		mustResourceInstanceAddr("test_object.A").Resource,
    95  		&states.ResourceInstanceObjectSrc{
    96  			Status:    states.ObjectReady,
    97  			AttrsJSON: []byte(`{"id":"A"}`),
    98  		},
    99  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   100  	)
   101  	root.SetResourceInstanceCurrent(
   102  		mustResourceInstanceAddr("test_object.B").Resource,
   103  		&states.ResourceInstanceObjectSrc{
   104  			Status:       states.ObjectReady,
   105  			AttrsJSON:    []byte(`{"id":"B","test_list":["x"]}`),
   106  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("test_object.A")},
   107  		},
   108  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   109  	)
   110  
   111  	b := &ApplyGraphBuilder{
   112  		Config:  testModule(t, "graph-builder-apply-dep-cbd"),
   113  		Changes: changes,
   114  		Plugins: simpleMockPluginLibrary(),
   115  		State:   state,
   116  	}
   117  
   118  	g, err := b.Build(addrs.RootModuleInstance)
   119  	if err != nil {
   120  		t.Fatalf("err: %s", err)
   121  	}
   122  
   123  	if g.Path.String() != addrs.RootModuleInstance.String() {
   124  		t.Fatalf("wrong path %q", g.Path.String())
   125  	}
   126  
   127  	// We're going to go hunting for our deposed instance node here, so we
   128  	// can find out its key to use in the assertions below.
   129  	var dk states.DeposedKey
   130  	for _, v := range g.Vertices() {
   131  		tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
   132  		if !ok {
   133  			continue
   134  		}
   135  		if dk != states.NotDeposed {
   136  			t.Fatalf("more than one deposed instance node in the graph; want only one")
   137  		}
   138  		dk = tv.DeposedKey
   139  	}
   140  	if dk == states.NotDeposed {
   141  		t.Fatalf("no deposed instance node in the graph; want one")
   142  	}
   143  
   144  	destroyName := fmt.Sprintf("test_object.A (destroy deposed %s)", dk)
   145  
   146  	// Create A, Modify B, Destroy A
   147  	testGraphHappensBefore(
   148  		t, g,
   149  		"test_object.A",
   150  		destroyName,
   151  	)
   152  	testGraphHappensBefore(
   153  		t, g,
   154  		"test_object.A",
   155  		"test_object.B",
   156  	)
   157  	testGraphHappensBefore(
   158  		t, g,
   159  		"test_object.B",
   160  		destroyName,
   161  	)
   162  }
   163  
   164  // This tests the ordering of two resources that are both CBD that
   165  // require destroy/create.
   166  func TestApplyGraphBuilder_doubleCBD(t *testing.T) {
   167  	changes := &plans.Changes{
   168  		Resources: []*plans.ResourceInstanceChangeSrc{
   169  			{
   170  				Addr: mustResourceInstanceAddr("test_object.A"),
   171  				ChangeSrc: plans.ChangeSrc{
   172  					Action: plans.CreateThenDelete,
   173  				},
   174  			},
   175  			{
   176  				Addr: mustResourceInstanceAddr("test_object.B"),
   177  				ChangeSrc: plans.ChangeSrc{
   178  					Action: plans.CreateThenDelete,
   179  				},
   180  			},
   181  		},
   182  	}
   183  
   184  	b := &ApplyGraphBuilder{
   185  		Config:  testModule(t, "graph-builder-apply-double-cbd"),
   186  		Changes: changes,
   187  		Plugins: simpleMockPluginLibrary(),
   188  	}
   189  
   190  	g, err := b.Build(addrs.RootModuleInstance)
   191  	if err != nil {
   192  		t.Fatalf("err: %s", err)
   193  	}
   194  
   195  	if g.Path.String() != addrs.RootModuleInstance.String() {
   196  		t.Fatalf("wrong path %q", g.Path.String())
   197  	}
   198  
   199  	// We're going to go hunting for our deposed instance node here, so we
   200  	// can find out its key to use in the assertions below.
   201  	var destroyA, destroyB string
   202  	for _, v := range g.Vertices() {
   203  		tv, ok := v.(*NodeDestroyDeposedResourceInstanceObject)
   204  		if !ok {
   205  			continue
   206  		}
   207  
   208  		switch tv.Addr.Resource.Resource.Name {
   209  		case "A":
   210  			destroyA = fmt.Sprintf("test_object.A (destroy deposed %s)", tv.DeposedKey)
   211  		case "B":
   212  			destroyB = fmt.Sprintf("test_object.B (destroy deposed %s)", tv.DeposedKey)
   213  		default:
   214  			t.Fatalf("unknown instance: %s", tv.Addr)
   215  		}
   216  	}
   217  
   218  	// Create A, Modify B, Destroy A
   219  	testGraphHappensBefore(
   220  		t, g,
   221  		"test_object.A",
   222  		destroyA,
   223  	)
   224  	testGraphHappensBefore(
   225  		t, g,
   226  		"test_object.A",
   227  		"test_object.B",
   228  	)
   229  	testGraphHappensBefore(
   230  		t, g,
   231  		"test_object.B",
   232  		destroyB,
   233  	)
   234  }
   235  
   236  // This tests the ordering of two resources being destroyed that depend
   237  // on each other from only state. GH-11749
   238  func TestApplyGraphBuilder_destroyStateOnly(t *testing.T) {
   239  	changes := &plans.Changes{
   240  		Resources: []*plans.ResourceInstanceChangeSrc{
   241  			{
   242  				Addr: mustResourceInstanceAddr("module.child.test_object.A"),
   243  				ChangeSrc: plans.ChangeSrc{
   244  					Action: plans.Delete,
   245  				},
   246  			},
   247  			{
   248  				Addr: mustResourceInstanceAddr("module.child.test_object.B"),
   249  				ChangeSrc: plans.ChangeSrc{
   250  					Action: plans.Delete,
   251  				},
   252  			},
   253  		},
   254  	}
   255  
   256  	state := states.NewState()
   257  	root := state.EnsureModule(addrs.RootModuleInstance)
   258  	child := state.EnsureModule(addrs.RootModuleInstance.Child("child", addrs.NoKey))
   259  	root.SetResourceInstanceCurrent(
   260  		mustResourceInstanceAddr("test_object.A").Resource,
   261  		&states.ResourceInstanceObjectSrc{
   262  			Status:    states.ObjectReady,
   263  			AttrsJSON: []byte(`{"id":"foo"}`),
   264  		},
   265  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   266  	)
   267  	child.SetResourceInstanceCurrent(
   268  		mustResourceInstanceAddr("test_object.B").Resource,
   269  		&states.ResourceInstanceObjectSrc{
   270  			Status:       states.ObjectReady,
   271  			AttrsJSON:    []byte(`{"id":"bar"}`),
   272  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.child.test_object.A")},
   273  		},
   274  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   275  	)
   276  
   277  	b := &ApplyGraphBuilder{
   278  		Config:  testModule(t, "empty"),
   279  		Changes: changes,
   280  		State:   state,
   281  		Plugins: simpleMockPluginLibrary(),
   282  	}
   283  
   284  	g, diags := b.Build(addrs.RootModuleInstance)
   285  	if diags.HasErrors() {
   286  		t.Fatalf("err: %s", diags.Err())
   287  	}
   288  
   289  	if g.Path.String() != addrs.RootModuleInstance.String() {
   290  		t.Fatalf("wrong path %q", g.Path.String())
   291  	}
   292  
   293  	testGraphHappensBefore(
   294  		t, g,
   295  		"module.child.test_object.B (destroy)",
   296  		"module.child.test_object.A (destroy)")
   297  }
   298  
   299  // This tests the ordering of destroying a single count of a resource.
   300  func TestApplyGraphBuilder_destroyCount(t *testing.T) {
   301  	changes := &plans.Changes{
   302  		Resources: []*plans.ResourceInstanceChangeSrc{
   303  			{
   304  				Addr: mustResourceInstanceAddr("test_object.A[1]"),
   305  				ChangeSrc: plans.ChangeSrc{
   306  					Action: plans.Delete,
   307  				},
   308  			},
   309  			{
   310  				Addr: mustResourceInstanceAddr("test_object.B"),
   311  				ChangeSrc: plans.ChangeSrc{
   312  					Action: plans.Update,
   313  				},
   314  			},
   315  		},
   316  	}
   317  
   318  	state := states.NewState()
   319  	root := state.RootModule()
   320  	addrA := mustResourceInstanceAddr("test_object.A[1]")
   321  	root.SetResourceInstanceCurrent(
   322  		addrA.Resource,
   323  		&states.ResourceInstanceObjectSrc{
   324  			Status:    states.ObjectReady,
   325  			AttrsJSON: []byte(`{"id":"B"}`),
   326  		},
   327  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   328  	)
   329  	root.SetResourceInstanceCurrent(
   330  		mustResourceInstanceAddr("test_object.B").Resource,
   331  		&states.ResourceInstanceObjectSrc{
   332  			Status:       states.ObjectReady,
   333  			AttrsJSON:    []byte(`{"id":"B"}`),
   334  			Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
   335  		},
   336  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   337  	)
   338  
   339  	b := &ApplyGraphBuilder{
   340  		Config:  testModule(t, "graph-builder-apply-count"),
   341  		Changes: changes,
   342  		Plugins: simpleMockPluginLibrary(),
   343  		State:   state,
   344  	}
   345  
   346  	g, err := b.Build(addrs.RootModuleInstance)
   347  	if err != nil {
   348  		t.Fatalf("err: %s", err)
   349  	}
   350  
   351  	if g.Path.String() != addrs.RootModuleInstance.String() {
   352  		t.Fatalf("wrong module path %q", g.Path)
   353  	}
   354  
   355  	actual := strings.TrimSpace(g.String())
   356  	expected := strings.TrimSpace(testApplyGraphBuilderDestroyCountStr)
   357  	if actual != expected {
   358  		t.Fatalf("wrong result\n\ngot:\n%s\n\nwant:\n%s", actual, expected)
   359  	}
   360  }
   361  
   362  func TestApplyGraphBuilder_moduleDestroy(t *testing.T) {
   363  	changes := &plans.Changes{
   364  		Resources: []*plans.ResourceInstanceChangeSrc{
   365  			{
   366  				Addr: mustResourceInstanceAddr("module.A.test_object.foo"),
   367  				ChangeSrc: plans.ChangeSrc{
   368  					Action: plans.Delete,
   369  				},
   370  			},
   371  			{
   372  				Addr: mustResourceInstanceAddr("module.B.test_object.foo"),
   373  				ChangeSrc: plans.ChangeSrc{
   374  					Action: plans.Delete,
   375  				},
   376  			},
   377  		},
   378  	}
   379  
   380  	state := states.NewState()
   381  	modA := state.EnsureModule(addrs.RootModuleInstance.Child("A", addrs.NoKey))
   382  	modA.SetResourceInstanceCurrent(
   383  		mustResourceInstanceAddr("test_object.foo").Resource,
   384  		&states.ResourceInstanceObjectSrc{
   385  			Status:    states.ObjectReady,
   386  			AttrsJSON: []byte(`{"id":"foo"}`),
   387  		},
   388  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   389  	)
   390  	modB := state.EnsureModule(addrs.RootModuleInstance.Child("B", addrs.NoKey))
   391  	modB.SetResourceInstanceCurrent(
   392  		mustResourceInstanceAddr("test_object.foo").Resource,
   393  		&states.ResourceInstanceObjectSrc{
   394  			Status:       states.ObjectReady,
   395  			AttrsJSON:    []byte(`{"id":"foo","value":"foo"}`),
   396  			Dependencies: []addrs.ConfigResource{mustConfigResourceAddr("module.A.test_object.foo")},
   397  		},
   398  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   399  	)
   400  
   401  	b := &ApplyGraphBuilder{
   402  		Config:  testModule(t, "graph-builder-apply-module-destroy"),
   403  		Changes: changes,
   404  		Plugins: simpleMockPluginLibrary(),
   405  		State:   state,
   406  	}
   407  
   408  	g, err := b.Build(addrs.RootModuleInstance)
   409  	if err != nil {
   410  		t.Fatalf("err: %s", err)
   411  	}
   412  
   413  	testGraphHappensBefore(
   414  		t, g,
   415  		"module.B.test_object.foo (destroy)",
   416  		"module.A.test_object.foo (destroy)",
   417  	)
   418  }
   419  
   420  func TestApplyGraphBuilder_targetModule(t *testing.T) {
   421  	changes := &plans.Changes{
   422  		Resources: []*plans.ResourceInstanceChangeSrc{
   423  			{
   424  				Addr: mustResourceInstanceAddr("test_object.foo"),
   425  				ChangeSrc: plans.ChangeSrc{
   426  					Action: plans.Update,
   427  				},
   428  			},
   429  			{
   430  				Addr: mustResourceInstanceAddr("module.child2.test_object.foo"),
   431  				ChangeSrc: plans.ChangeSrc{
   432  					Action: plans.Update,
   433  				},
   434  			},
   435  		},
   436  	}
   437  
   438  	b := &ApplyGraphBuilder{
   439  		Config:  testModule(t, "graph-builder-apply-target-module"),
   440  		Changes: changes,
   441  		Plugins: simpleMockPluginLibrary(),
   442  		Targets: []addrs.Targetable{
   443  			addrs.RootModuleInstance.Child("child2", addrs.NoKey),
   444  		},
   445  	}
   446  
   447  	g, err := b.Build(addrs.RootModuleInstance)
   448  	if err != nil {
   449  		t.Fatalf("err: %s", err)
   450  	}
   451  
   452  	testGraphNotContains(t, g, "module.child1.output.instance_id")
   453  }
   454  
   455  // Ensure that an update resulting from the removal of a resource happens after
   456  // that resource is destroyed.
   457  func TestApplyGraphBuilder_updateFromOrphan(t *testing.T) {
   458  	schemas := simpleTestSchemas()
   459  	instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
   460  
   461  	bBefore, _ := plans.NewDynamicValue(
   462  		cty.ObjectVal(map[string]cty.Value{
   463  			"id":          cty.StringVal("b_id"),
   464  			"test_string": cty.StringVal("a_id"),
   465  		}), instanceSchema.ImpliedType())
   466  	bAfter, _ := plans.NewDynamicValue(
   467  		cty.ObjectVal(map[string]cty.Value{
   468  			"id":          cty.StringVal("b_id"),
   469  			"test_string": cty.StringVal("changed"),
   470  		}), instanceSchema.ImpliedType())
   471  
   472  	changes := &plans.Changes{
   473  		Resources: []*plans.ResourceInstanceChangeSrc{
   474  			{
   475  				Addr: mustResourceInstanceAddr("test_object.a"),
   476  				ChangeSrc: plans.ChangeSrc{
   477  					Action: plans.Delete,
   478  				},
   479  			},
   480  			{
   481  				Addr: mustResourceInstanceAddr("test_object.b"),
   482  				ChangeSrc: plans.ChangeSrc{
   483  					Action: plans.Update,
   484  					Before: bBefore,
   485  					After:  bAfter,
   486  				},
   487  			},
   488  		},
   489  	}
   490  
   491  	state := states.NewState()
   492  	root := state.EnsureModule(addrs.RootModuleInstance)
   493  	root.SetResourceInstanceCurrent(
   494  		addrs.Resource{
   495  			Mode: addrs.ManagedResourceMode,
   496  			Type: "test_object",
   497  			Name: "a",
   498  		}.Instance(addrs.NoKey),
   499  		&states.ResourceInstanceObjectSrc{
   500  			Status:    states.ObjectReady,
   501  			AttrsJSON: []byte(`{"id":"a_id"}`),
   502  		},
   503  		addrs.AbsProviderConfig{
   504  			Provider: addrs.NewDefaultProvider("test"),
   505  			Module:   addrs.RootModule,
   506  		},
   507  	)
   508  	root.SetResourceInstanceCurrent(
   509  		addrs.Resource{
   510  			Mode: addrs.ManagedResourceMode,
   511  			Type: "test_object",
   512  			Name: "b",
   513  		}.Instance(addrs.NoKey),
   514  		&states.ResourceInstanceObjectSrc{
   515  			Status:    states.ObjectReady,
   516  			AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
   517  			Dependencies: []addrs.ConfigResource{
   518  				{
   519  					Resource: addrs.Resource{
   520  						Mode: addrs.ManagedResourceMode,
   521  						Type: "test_object",
   522  						Name: "a",
   523  					},
   524  					Module: root.Addr.Module(),
   525  				},
   526  			},
   527  		},
   528  		addrs.AbsProviderConfig{
   529  			Provider: addrs.NewDefaultProvider("test"),
   530  			Module:   addrs.RootModule,
   531  		},
   532  	)
   533  
   534  	b := &ApplyGraphBuilder{
   535  		Config:  testModule(t, "graph-builder-apply-orphan-update"),
   536  		Changes: changes,
   537  		Plugins: simpleMockPluginLibrary(),
   538  		State:   state,
   539  	}
   540  
   541  	g, err := b.Build(addrs.RootModuleInstance)
   542  	if err != nil {
   543  		t.Fatalf("err: %s", err)
   544  	}
   545  
   546  	expected := strings.TrimSpace(`
   547  test_object.a (destroy)
   548  test_object.b
   549    test_object.a (destroy)
   550  `)
   551  
   552  	instanceGraph := filterInstances(g)
   553  	got := strings.TrimSpace(instanceGraph.String())
   554  
   555  	if got != expected {
   556  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
   557  	}
   558  }
   559  
   560  // Ensure that an update resulting from the removal of a resource happens before
   561  // a CBD resource is destroyed.
   562  func TestApplyGraphBuilder_updateFromCBDOrphan(t *testing.T) {
   563  	schemas := simpleTestSchemas()
   564  	instanceSchema := schemas.Providers[addrs.NewDefaultProvider("test")].ResourceTypes["test_object"]
   565  
   566  	bBefore, _ := plans.NewDynamicValue(
   567  		cty.ObjectVal(map[string]cty.Value{
   568  			"id":          cty.StringVal("b_id"),
   569  			"test_string": cty.StringVal("a_id"),
   570  		}), instanceSchema.ImpliedType())
   571  	bAfter, _ := plans.NewDynamicValue(
   572  		cty.ObjectVal(map[string]cty.Value{
   573  			"id":          cty.StringVal("b_id"),
   574  			"test_string": cty.StringVal("changed"),
   575  		}), instanceSchema.ImpliedType())
   576  
   577  	changes := &plans.Changes{
   578  		Resources: []*plans.ResourceInstanceChangeSrc{
   579  			{
   580  				Addr: mustResourceInstanceAddr("test_object.a"),
   581  				ChangeSrc: plans.ChangeSrc{
   582  					Action: plans.Delete,
   583  				},
   584  			},
   585  			{
   586  				Addr: mustResourceInstanceAddr("test_object.b"),
   587  				ChangeSrc: plans.ChangeSrc{
   588  					Action: plans.Update,
   589  					Before: bBefore,
   590  					After:  bAfter,
   591  				},
   592  			},
   593  		},
   594  	}
   595  
   596  	state := states.NewState()
   597  	root := state.EnsureModule(addrs.RootModuleInstance)
   598  	root.SetResourceInstanceCurrent(
   599  		addrs.Resource{
   600  			Mode: addrs.ManagedResourceMode,
   601  			Type: "test_object",
   602  			Name: "a",
   603  		}.Instance(addrs.NoKey),
   604  		&states.ResourceInstanceObjectSrc{
   605  			Status:              states.ObjectReady,
   606  			AttrsJSON:           []byte(`{"id":"a_id"}`),
   607  			CreateBeforeDestroy: true,
   608  		},
   609  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   610  	)
   611  	root.SetResourceInstanceCurrent(
   612  		addrs.Resource{
   613  			Mode: addrs.ManagedResourceMode,
   614  			Type: "test_object",
   615  			Name: "b",
   616  		}.Instance(addrs.NoKey),
   617  		&states.ResourceInstanceObjectSrc{
   618  			Status:    states.ObjectReady,
   619  			AttrsJSON: []byte(`{"id":"b_id","test_string":"a_id"}`),
   620  			Dependencies: []addrs.ConfigResource{
   621  				{
   622  					Resource: addrs.Resource{
   623  						Mode: addrs.ManagedResourceMode,
   624  						Type: "test_object",
   625  						Name: "a",
   626  					},
   627  					Module: root.Addr.Module(),
   628  				},
   629  			},
   630  		},
   631  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   632  	)
   633  
   634  	b := &ApplyGraphBuilder{
   635  		Config:  testModule(t, "graph-builder-apply-orphan-update"),
   636  		Changes: changes,
   637  		Plugins: simpleMockPluginLibrary(),
   638  		State:   state,
   639  	}
   640  
   641  	g, err := b.Build(addrs.RootModuleInstance)
   642  	if err != nil {
   643  		t.Fatalf("err: %s", err)
   644  	}
   645  
   646  	expected := strings.TrimSpace(`
   647  test_object.a (destroy)
   648    test_object.b
   649  test_object.b
   650  `)
   651  
   652  	instanceGraph := filterInstances(g)
   653  	got := strings.TrimSpace(instanceGraph.String())
   654  
   655  	if got != expected {
   656  		t.Fatalf("expected:\n%s\ngot:\n%s", expected, got)
   657  	}
   658  }
   659  
   660  // The orphan clean up node should not be connected to a provider
   661  func TestApplyGraphBuilder_orphanedWithProvider(t *testing.T) {
   662  	changes := &plans.Changes{
   663  		Resources: []*plans.ResourceInstanceChangeSrc{
   664  			{
   665  				Addr: mustResourceInstanceAddr("test_object.A"),
   666  				ChangeSrc: plans.ChangeSrc{
   667  					Action: plans.Delete,
   668  				},
   669  			},
   670  		},
   671  	}
   672  
   673  	state := states.NewState()
   674  	root := state.EnsureModule(addrs.RootModuleInstance)
   675  	root.SetResourceInstanceCurrent(
   676  		mustResourceInstanceAddr("test_object.A").Resource,
   677  		&states.ResourceInstanceObjectSrc{
   678  			Status:    states.ObjectReady,
   679  			AttrsJSON: []byte(`{"id":"A"}`),
   680  		},
   681  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"].foo`),
   682  	)
   683  
   684  	b := &ApplyGraphBuilder{
   685  		Config:  testModule(t, "graph-builder-orphan-alias"),
   686  		Changes: changes,
   687  		Plugins: simpleMockPluginLibrary(),
   688  		State:   state,
   689  	}
   690  
   691  	g, err := b.Build(addrs.RootModuleInstance)
   692  	if err != nil {
   693  		t.Fatal(err)
   694  	}
   695  
   696  	// The cleanup node has no state or config of its own, so would create a
   697  	// default provider which we don't want.
   698  	testGraphNotContains(t, g, "provider.test")
   699  }
   700  
   701  const testApplyGraphBuilderStr = `
   702  meta.count-boundary (EachMode fixup)
   703    module.child (close)
   704    test_object.other
   705  module.child (close)
   706    module.child.test_object.other
   707  module.child (expand)
   708  module.child.test_object.create
   709    module.child.test_object.create (expand)
   710  module.child.test_object.create (expand)
   711    module.child (expand)
   712    provider["registry.terraform.io/hashicorp/test"]
   713  module.child.test_object.other
   714    module.child.test_object.create
   715    module.child.test_object.other (expand)
   716  module.child.test_object.other (expand)
   717    module.child (expand)
   718    provider["registry.terraform.io/hashicorp/test"]
   719  provider["registry.terraform.io/hashicorp/test"]
   720  provider["registry.terraform.io/hashicorp/test"] (close)
   721    module.child.test_object.other
   722    test_object.other
   723  root
   724    meta.count-boundary (EachMode fixup)
   725    provider["registry.terraform.io/hashicorp/test"] (close)
   726  test_object.create
   727    test_object.create (expand)
   728  test_object.create (expand)
   729    provider["registry.terraform.io/hashicorp/test"]
   730  test_object.other
   731    test_object.create
   732    test_object.other (expand)
   733  test_object.other (expand)
   734    provider["registry.terraform.io/hashicorp/test"]
   735  `
   736  
   737  const testApplyGraphBuilderDestroyCountStr = `
   738  meta.count-boundary (EachMode fixup)
   739    test_object.B
   740  provider["registry.terraform.io/hashicorp/test"]
   741  provider["registry.terraform.io/hashicorp/test"] (close)
   742    test_object.B
   743  root
   744    meta.count-boundary (EachMode fixup)
   745    provider["registry.terraform.io/hashicorp/test"] (close)
   746  test_object.A (expand)
   747    provider["registry.terraform.io/hashicorp/test"]
   748  test_object.A[1] (destroy)
   749    provider["registry.terraform.io/hashicorp/test"]
   750  test_object.B
   751    test_object.A (expand)
   752    test_object.A[1] (destroy)
   753    test_object.B (expand)
   754  test_object.B (expand)
   755    provider["registry.terraform.io/hashicorp/test"]
   756  `