github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/context_apply2_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/davecgh/go-spew/spew"
    13  	"github.com/google/go-cmp/cmp"
    14  	"github.com/zclconf/go-cty/cty"
    15  
    16  	"github.com/hashicorp/terraform/internal/addrs"
    17  	"github.com/hashicorp/terraform/internal/configs/configschema"
    18  	"github.com/hashicorp/terraform/internal/lang/marks"
    19  	"github.com/hashicorp/terraform/internal/plans"
    20  	"github.com/hashicorp/terraform/internal/providers"
    21  	"github.com/hashicorp/terraform/internal/states"
    22  )
    23  
    24  // Test that the PreApply hook is called with the correct deposed key
    25  func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) {
    26  	m := testModule(t, "apply-cbd-deposed-only")
    27  	p := testProvider("aws")
    28  	p.PlanResourceChangeFn = testDiffFn
    29  	p.ApplyResourceChangeFn = testApplyFn
    30  
    31  	deposedKey := states.NewDeposedKey()
    32  
    33  	state := states.NewState()
    34  	root := state.EnsureModule(addrs.RootModuleInstance)
    35  	root.SetResourceInstanceCurrent(
    36  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    37  		&states.ResourceInstanceObjectSrc{
    38  			Status:    states.ObjectReady,
    39  			AttrsJSON: []byte(`{"id":"bar"}`),
    40  		},
    41  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
    42  	)
    43  	root.SetResourceInstanceDeposed(
    44  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    45  		deposedKey,
    46  		&states.ResourceInstanceObjectSrc{
    47  			Status:    states.ObjectTainted,
    48  			AttrsJSON: []byte(`{"id":"foo"}`),
    49  		},
    50  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
    51  	)
    52  
    53  	hook := new(MockHook)
    54  	ctx := testContext2(t, &ContextOpts{
    55  		Hooks: []Hook{hook},
    56  		Providers: map[addrs.Provider]providers.Factory{
    57  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
    58  		},
    59  	})
    60  
    61  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    62  	if diags.HasErrors() {
    63  		t.Fatalf("diags: %s", diags.Err())
    64  	} else {
    65  		t.Logf(legacyDiffComparisonString(plan.Changes))
    66  	}
    67  
    68  	_, diags = ctx.Apply(plan, m)
    69  	if diags.HasErrors() {
    70  		t.Fatalf("diags: %s", diags.Err())
    71  	}
    72  
    73  	// Verify PreApply was called correctly
    74  	if !hook.PreApplyCalled {
    75  		t.Fatalf("PreApply hook not called")
    76  	}
    77  	if addr, wantAddr := hook.PreApplyAddr, mustResourceInstanceAddr("aws_instance.bar"); !addr.Equal(wantAddr) {
    78  		t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
    79  	}
    80  	if gen := hook.PreApplyGen; gen != deposedKey {
    81  		t.Errorf("expected gen to be %q, but was %q", deposedKey, gen)
    82  	}
    83  }
    84  
    85  func TestContext2Apply_destroyWithDataSourceExpansion(t *testing.T) {
    86  	// While managed resources store their destroy-time dependencies, data
    87  	// sources do not. This means that if a provider were only included in a
    88  	// destroy graph because of data sources, it could have dependencies which
    89  	// are not correctly ordered. Here we verify that the provider is not
    90  	// included in the destroy operation, and all dependency evaluations
    91  	// succeed.
    92  
    93  	m := testModuleInline(t, map[string]string{
    94  		"main.tf": `
    95  module "mod" {
    96    source = "./mod"
    97  }
    98  
    99  provider "other" {
   100    foo = module.mod.data
   101  }
   102  
   103  # this should not require the provider be present during destroy
   104  data "other_data_source" "a" {
   105  }
   106  `,
   107  
   108  		"mod/main.tf": `
   109  data "test_data_source" "a" {
   110    count = 1
   111  }
   112  
   113  data "test_data_source" "b" {
   114    count = data.test_data_source.a[0].foo == "ok" ? 1 : 0
   115  }
   116  
   117  output "data" {
   118    value = data.test_data_source.a[0].foo == "ok" ? data.test_data_source.b[0].foo : "nope"
   119  }
   120  `,
   121  	})
   122  
   123  	testP := testProvider("test")
   124  	otherP := testProvider("other")
   125  
   126  	readData := func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
   127  		return providers.ReadDataSourceResponse{
   128  			State: cty.ObjectVal(map[string]cty.Value{
   129  				"id":  cty.StringVal("data_source"),
   130  				"foo": cty.StringVal("ok"),
   131  			}),
   132  		}
   133  	}
   134  
   135  	testP.ReadDataSourceFn = readData
   136  	otherP.ReadDataSourceFn = readData
   137  
   138  	ps := map[addrs.Provider]providers.Factory{
   139  		addrs.NewDefaultProvider("test"):  testProviderFuncFixed(testP),
   140  		addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherP),
   141  	}
   142  
   143  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   144  		foo := req.Config.GetAttr("foo")
   145  		if foo.IsNull() || foo.AsString() != "ok" {
   146  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect config val: %#v\n", foo))
   147  		}
   148  		return resp
   149  	}
   150  
   151  	ctx := testContext2(t, &ContextOpts{
   152  		Providers: ps,
   153  	})
   154  
   155  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   156  	if diags.HasErrors() {
   157  		t.Fatal(diags.Err())
   158  	}
   159  
   160  	_, diags = ctx.Apply(plan, m)
   161  	if diags.HasErrors() {
   162  		t.Fatal(diags.Err())
   163  	}
   164  
   165  	// now destroy the whole thing
   166  	ctx = testContext2(t, &ContextOpts{
   167  		Providers: ps,
   168  	})
   169  
   170  	plan, diags = ctx.Plan(m, states.NewState(), &PlanOpts{
   171  		Mode: plans.DestroyMode,
   172  	})
   173  	if diags.HasErrors() {
   174  		t.Fatal(diags.Err())
   175  	}
   176  
   177  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   178  		// should not be used to destroy data sources
   179  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("provider should not be used"))
   180  		return resp
   181  	}
   182  
   183  	_, diags = ctx.Apply(plan, m)
   184  	if diags.HasErrors() {
   185  		t.Fatal(diags.Err())
   186  	}
   187  }
   188  
   189  func TestContext2Apply_destroyThenUpdate(t *testing.T) {
   190  	m := testModuleInline(t, map[string]string{
   191  		"main.tf": `
   192  resource "test_instance" "a" {
   193  	value = "udpated"
   194  }
   195  `,
   196  	})
   197  
   198  	p := testProvider("test")
   199  	p.PlanResourceChangeFn = testDiffFn
   200  
   201  	var orderMu sync.Mutex
   202  	var order []string
   203  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   204  		id := req.PriorState.GetAttr("id").AsString()
   205  		if id == "b" {
   206  			// slow down the b destroy, since a should wait for it
   207  			time.Sleep(100 * time.Millisecond)
   208  		}
   209  
   210  		orderMu.Lock()
   211  		order = append(order, id)
   212  		orderMu.Unlock()
   213  
   214  		resp.NewState = req.PlannedState
   215  		return resp
   216  	}
   217  
   218  	addrA := mustResourceInstanceAddr(`test_instance.a`)
   219  	addrB := mustResourceInstanceAddr(`test_instance.b`)
   220  
   221  	state := states.BuildState(func(s *states.SyncState) {
   222  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
   223  			AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`),
   224  			Status:    states.ObjectReady,
   225  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   226  
   227  		// test_instance.b depended on test_instance.a, and therefor should be
   228  		// destroyed before any changes to test_instance.a
   229  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
   230  			AttrsJSON:    []byte(`{"id":"b"}`),
   231  			Status:       states.ObjectReady,
   232  			Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
   233  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   234  	})
   235  
   236  	ctx := testContext2(t, &ContextOpts{
   237  		Providers: map[addrs.Provider]providers.Factory{
   238  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   239  		},
   240  	})
   241  
   242  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   243  	assertNoErrors(t, diags)
   244  
   245  	_, diags = ctx.Apply(plan, m)
   246  	if diags.HasErrors() {
   247  		t.Fatal(diags.Err())
   248  	}
   249  
   250  	if order[0] != "b" {
   251  		t.Fatalf("expected apply order [b, a], got: %v\n", order)
   252  	}
   253  }
   254  
   255  // verify that dependencies are updated in the state during refresh and apply
   256  func TestApply_updateDependencies(t *testing.T) {
   257  	state := states.NewState()
   258  	root := state.EnsureModule(addrs.RootModuleInstance)
   259  
   260  	fooAddr := mustResourceInstanceAddr("aws_instance.foo")
   261  	barAddr := mustResourceInstanceAddr("aws_instance.bar")
   262  	bazAddr := mustResourceInstanceAddr("aws_instance.baz")
   263  	bamAddr := mustResourceInstanceAddr("aws_instance.bam")
   264  	binAddr := mustResourceInstanceAddr("aws_instance.bin")
   265  	root.SetResourceInstanceCurrent(
   266  		fooAddr.Resource,
   267  		&states.ResourceInstanceObjectSrc{
   268  			Status:    states.ObjectReady,
   269  			AttrsJSON: []byte(`{"id":"foo"}`),
   270  			Dependencies: []addrs.ConfigResource{
   271  				bazAddr.ContainingResource().Config(),
   272  			},
   273  		},
   274  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   275  	)
   276  	root.SetResourceInstanceCurrent(
   277  		binAddr.Resource,
   278  		&states.ResourceInstanceObjectSrc{
   279  			Status:    states.ObjectReady,
   280  			AttrsJSON: []byte(`{"id":"bin","type":"aws_instance","unknown":"ok"}`),
   281  			Dependencies: []addrs.ConfigResource{
   282  				bazAddr.ContainingResource().Config(),
   283  			},
   284  		},
   285  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   286  	)
   287  	root.SetResourceInstanceCurrent(
   288  		bazAddr.Resource,
   289  		&states.ResourceInstanceObjectSrc{
   290  			Status:    states.ObjectReady,
   291  			AttrsJSON: []byte(`{"id":"baz"}`),
   292  			Dependencies: []addrs.ConfigResource{
   293  				// Existing dependencies should not be removed from orphaned instances
   294  				bamAddr.ContainingResource().Config(),
   295  			},
   296  		},
   297  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   298  	)
   299  	root.SetResourceInstanceCurrent(
   300  		barAddr.Resource,
   301  		&states.ResourceInstanceObjectSrc{
   302  			Status:    states.ObjectReady,
   303  			AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`),
   304  		},
   305  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   306  	)
   307  
   308  	m := testModuleInline(t, map[string]string{
   309  		"main.tf": `
   310  resource "aws_instance" "bar" {
   311    foo = aws_instance.foo.id
   312  }
   313  
   314  resource "aws_instance" "foo" {
   315  }
   316  
   317  resource "aws_instance" "bin" {
   318  }
   319  `,
   320  	})
   321  
   322  	p := testProvider("aws")
   323  
   324  	ctx := testContext2(t, &ContextOpts{
   325  		Providers: map[addrs.Provider]providers.Factory{
   326  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
   327  		},
   328  	})
   329  
   330  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   331  	assertNoErrors(t, diags)
   332  
   333  	bar := plan.PriorState.ResourceInstance(barAddr)
   334  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   335  		t.Fatalf("bar should depend on foo after refresh, but got %s", bar.Current.Dependencies)
   336  	}
   337  
   338  	foo := plan.PriorState.ResourceInstance(fooAddr)
   339  	if len(foo.Current.Dependencies) == 0 || !foo.Current.Dependencies[0].Equal(bazAddr.ContainingResource().Config()) {
   340  		t.Fatalf("foo should depend on baz after refresh because of the update, but got %s", foo.Current.Dependencies)
   341  	}
   342  
   343  	bin := plan.PriorState.ResourceInstance(binAddr)
   344  	if len(bin.Current.Dependencies) != 0 {
   345  		t.Fatalf("bin should depend on nothing after refresh because there is no change, but got %s", bin.Current.Dependencies)
   346  	}
   347  
   348  	baz := plan.PriorState.ResourceInstance(bazAddr)
   349  	if len(baz.Current.Dependencies) == 0 || !baz.Current.Dependencies[0].Equal(bamAddr.ContainingResource().Config()) {
   350  		t.Fatalf("baz should depend on bam after refresh, but got %s", baz.Current.Dependencies)
   351  	}
   352  
   353  	state, diags = ctx.Apply(plan, m)
   354  	if diags.HasErrors() {
   355  		t.Fatal(diags.Err())
   356  	}
   357  
   358  	bar = state.ResourceInstance(barAddr)
   359  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   360  		t.Fatalf("bar should still depend on foo after apply, but got %s", bar.Current.Dependencies)
   361  	}
   362  
   363  	foo = state.ResourceInstance(fooAddr)
   364  	if len(foo.Current.Dependencies) != 0 {
   365  		t.Fatalf("foo should have no deps after apply, but got %s", foo.Current.Dependencies)
   366  	}
   367  
   368  }
   369  
   370  func TestContext2Apply_additionalSensitiveFromState(t *testing.T) {
   371  	// Ensure we're not trying to double-mark values decoded from state
   372  	m := testModuleInline(t, map[string]string{
   373  		"main.tf": `
   374  variable "secret" {
   375    sensitive = true
   376    default = ["secret"]
   377  }
   378  
   379  resource "test_resource" "a" {
   380    sensitive_attr = var.secret
   381  }
   382  
   383  resource "test_resource" "b" {
   384    value = test_resource.a.id
   385  }
   386  `,
   387  	})
   388  
   389  	p := new(MockProvider)
   390  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   391  		ResourceTypes: map[string]*configschema.Block{
   392  			"test_resource": {
   393  				Attributes: map[string]*configschema.Attribute{
   394  					"id": {
   395  						Type:     cty.String,
   396  						Computed: true,
   397  					},
   398  					"value": {
   399  						Type:     cty.String,
   400  						Optional: true,
   401  					},
   402  					"sensitive_attr": {
   403  						Type:      cty.List(cty.String),
   404  						Optional:  true,
   405  						Sensitive: true,
   406  					},
   407  				},
   408  			},
   409  		},
   410  	})
   411  
   412  	state := states.BuildState(func(s *states.SyncState) {
   413  		s.SetResourceInstanceCurrent(
   414  			mustResourceInstanceAddr(`test_resource.a`),
   415  			&states.ResourceInstanceObjectSrc{
   416  				AttrsJSON: []byte(`{"id":"a","sensitive_attr":["secret"]}`),
   417  				AttrSensitivePaths: []cty.PathValueMarks{
   418  					{
   419  						Path:  cty.GetAttrPath("sensitive_attr"),
   420  						Marks: cty.NewValueMarks(marks.Sensitive),
   421  					},
   422  				},
   423  				Status: states.ObjectReady,
   424  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   425  		)
   426  	})
   427  
   428  	ctx := testContext2(t, &ContextOpts{
   429  		Providers: map[addrs.Provider]providers.Factory{
   430  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   431  		},
   432  	})
   433  
   434  	plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   435  	assertNoErrors(t, diags)
   436  
   437  	_, diags = ctx.Apply(plan, m)
   438  	if diags.HasErrors() {
   439  		t.Fatal(diags.ErrWithWarnings())
   440  	}
   441  }
   442  
   443  func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) {
   444  	// Ensure we're not trying to double-mark values decoded from state
   445  	m := testModuleInline(t, map[string]string{
   446  		"main.tf": `
   447  module "mod" {
   448    source = "./mod"
   449  }
   450  
   451  resource "test_object" "a" {
   452    test_string = module.mod.out
   453  }
   454  `,
   455  
   456  		"mod/main.tf": `
   457  variable "in" {
   458    sensitive = true
   459    default = "foo"
   460  }
   461  output "out" {
   462    value = var.in
   463  }
   464  `,
   465  	})
   466  
   467  	p := simpleMockProvider()
   468  
   469  	ctx := testContext2(t, &ContextOpts{
   470  		Providers: map[addrs.Provider]providers.Factory{
   471  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   472  		},
   473  	})
   474  
   475  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   476  	assertNoErrors(t, diags)
   477  
   478  	state, diags := ctx.Apply(plan, m)
   479  	if diags.HasErrors() {
   480  		t.Fatal(diags.ErrWithWarnings())
   481  	}
   482  
   483  	obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a"))
   484  	if len(obj.Current.AttrSensitivePaths) != 1 {
   485  		t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths)
   486  	}
   487  
   488  	plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
   489  	assertNoErrors(t, diags)
   490  
   491  	// make sure the same marks are compared in the next plan as well
   492  	for _, c := range plan.Changes.Resources {
   493  		if c.Action != plans.NoOp {
   494  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   495  		}
   496  	}
   497  }
   498  
   499  func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) {
   500  	// The impure function call should not cause a planned change with
   501  	// ignore_changes
   502  	m := testModuleInline(t, map[string]string{
   503  		"main.tf": `
   504  variable "pw" {
   505    sensitive = true
   506    default = "foo"
   507  }
   508  
   509  resource "test_object" "x" {
   510    test_map = {
   511  	string = "X${bcrypt(var.pw)}"
   512    }
   513    lifecycle {
   514      ignore_changes = [ test_map["string"] ]
   515    }
   516  }
   517  
   518  resource "test_object" "y" {
   519    test_map = {
   520  	string = "X${bcrypt(var.pw)}"
   521    }
   522    lifecycle {
   523      ignore_changes = [ test_map ]
   524    }
   525  }
   526  
   527  `,
   528  	})
   529  
   530  	p := simpleMockProvider()
   531  
   532  	ctx := testContext2(t, &ContextOpts{
   533  		Providers: map[addrs.Provider]providers.Factory{
   534  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   535  		},
   536  	})
   537  
   538  	plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   539  	assertNoErrors(t, diags)
   540  
   541  	state, diags := ctx.Apply(plan, m)
   542  	assertNoErrors(t, diags)
   543  
   544  	// FINAL PLAN:
   545  	plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   546  	assertNoErrors(t, diags)
   547  
   548  	// make sure the same marks are compared in the next plan as well
   549  	for _, c := range plan.Changes.Resources {
   550  		if c.Action != plans.NoOp {
   551  			t.Logf("marks before: %#v", c.BeforeValMarks)
   552  			t.Logf("marks after:  %#v", c.AfterValMarks)
   553  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   554  		}
   555  	}
   556  }
   557  
   558  func TestContext2Apply_destroyWithDeposed(t *testing.T) {
   559  	m := testModuleInline(t, map[string]string{
   560  		"main.tf": `
   561  resource "test_object" "x" {
   562    test_string = "ok"
   563    lifecycle {
   564      create_before_destroy = true
   565    }
   566  }`,
   567  	})
   568  
   569  	p := simpleMockProvider()
   570  
   571  	deposedKey := states.NewDeposedKey()
   572  
   573  	state := states.NewState()
   574  	root := state.EnsureModule(addrs.RootModuleInstance)
   575  	root.SetResourceInstanceDeposed(
   576  		mustResourceInstanceAddr("test_object.x").Resource,
   577  		deposedKey,
   578  		&states.ResourceInstanceObjectSrc{
   579  			Status:    states.ObjectTainted,
   580  			AttrsJSON: []byte(`{"test_string":"deposed"}`),
   581  		},
   582  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   583  	)
   584  
   585  	ctx := testContext2(t, &ContextOpts{
   586  		Providers: map[addrs.Provider]providers.Factory{
   587  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   588  		},
   589  	})
   590  
   591  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   592  		Mode: plans.DestroyMode,
   593  	})
   594  	if diags.HasErrors() {
   595  		t.Fatalf("plan: %s", diags.Err())
   596  	}
   597  
   598  	_, diags = ctx.Apply(plan, m)
   599  	if diags.HasErrors() {
   600  		t.Fatalf("apply: %s", diags.Err())
   601  	}
   602  
   603  }
   604  
   605  func TestContext2Apply_nullableVariables(t *testing.T) {
   606  	m := testModule(t, "apply-nullable-variables")
   607  	state := states.NewState()
   608  	ctx := testContext2(t, &ContextOpts{})
   609  	plan, diags := ctx.Plan(m, state, &PlanOpts{})
   610  	if diags.HasErrors() {
   611  		t.Fatalf("plan: %s", diags.Err())
   612  	}
   613  	state, diags = ctx.Apply(plan, m)
   614  	if diags.HasErrors() {
   615  		t.Fatalf("apply: %s", diags.Err())
   616  	}
   617  
   618  	outputs := state.Module(addrs.RootModuleInstance).OutputValues
   619  	// we check for null outputs be seeing that they don't exists
   620  	if _, ok := outputs["nullable_null_default"]; ok {
   621  		t.Error("nullable_null_default: expected no output value")
   622  	}
   623  	if _, ok := outputs["nullable_non_null_default"]; ok {
   624  		t.Error("nullable_non_null_default: expected no output value")
   625  	}
   626  	if _, ok := outputs["nullable_no_default"]; ok {
   627  		t.Error("nullable_no_default: expected no output value")
   628  	}
   629  
   630  	if v := outputs["non_nullable_default"].Value; v.AsString() != "ok" {
   631  		t.Fatalf("incorrect 'non_nullable_default' output value: %#v\n", v)
   632  	}
   633  	if v := outputs["non_nullable_no_default"].Value; v.AsString() != "ok" {
   634  		t.Fatalf("incorrect 'non_nullable_no_default' output value: %#v\n", v)
   635  	}
   636  }
   637  
   638  func TestContext2Apply_targetedDestroyWithMoved(t *testing.T) {
   639  	m := testModuleInline(t, map[string]string{
   640  		"main.tf": `
   641  module "modb" {
   642    source = "./mod"
   643    for_each = toset(["a", "b"])
   644  }
   645  `,
   646  		"./mod/main.tf": `
   647  resource "test_object" "a" {
   648  }
   649  
   650  module "sub" {
   651    for_each = toset(["a", "b"])
   652    source = "./sub"
   653  }
   654  
   655  moved {
   656    from = module.old
   657    to = module.sub
   658  }
   659  `,
   660  		"./mod/sub/main.tf": `
   661  resource "test_object" "s" {
   662  }
   663  `})
   664  
   665  	p := simpleMockProvider()
   666  
   667  	ctx := testContext2(t, &ContextOpts{
   668  		Providers: map[addrs.Provider]providers.Factory{
   669  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   670  		},
   671  	})
   672  
   673  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   674  	assertNoErrors(t, diags)
   675  
   676  	state, diags := ctx.Apply(plan, m)
   677  	assertNoErrors(t, diags)
   678  
   679  	// destroy only a single instance not included in the moved statements
   680  	_, diags = ctx.Plan(m, state, &PlanOpts{
   681  		Mode:    plans.DestroyMode,
   682  		Targets: []addrs.Targetable{mustResourceInstanceAddr(`module.modb["a"].test_object.a`)},
   683  	})
   684  	assertNoErrors(t, diags)
   685  }
   686  
   687  func TestContext2Apply_graphError(t *testing.T) {
   688  	m := testModuleInline(t, map[string]string{
   689  		"main.tf": `
   690  resource "test_object" "a" {
   691    test_string = "ok"
   692  }
   693  
   694  resource "test_object" "b" {
   695    test_string = test_object.a.test_string
   696  }
   697  `,
   698  	})
   699  
   700  	p := simpleMockProvider()
   701  
   702  	state := states.NewState()
   703  	root := state.EnsureModule(addrs.RootModuleInstance)
   704  	root.SetResourceInstanceCurrent(
   705  		mustResourceInstanceAddr("test_object.a").Resource,
   706  		&states.ResourceInstanceObjectSrc{
   707  			Status:    states.ObjectTainted,
   708  			AttrsJSON: []byte(`{"test_string":"ok"}`),
   709  		},
   710  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   711  	)
   712  	root.SetResourceInstanceCurrent(
   713  		mustResourceInstanceAddr("test_object.b").Resource,
   714  		&states.ResourceInstanceObjectSrc{
   715  			Status:    states.ObjectTainted,
   716  			AttrsJSON: []byte(`{"test_string":"ok"}`),
   717  		},
   718  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   719  	)
   720  
   721  	ctx := testContext2(t, &ContextOpts{
   722  		Providers: map[addrs.Provider]providers.Factory{
   723  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   724  		},
   725  	})
   726  
   727  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   728  		Mode: plans.DestroyMode,
   729  	})
   730  	if diags.HasErrors() {
   731  		t.Fatalf("plan: %s", diags.Err())
   732  	}
   733  
   734  	// We're going to corrupt the stored state so that the dependencies will
   735  	// cause a cycle when building the apply graph.
   736  	testObjA := plan.PriorState.Modules[""].Resources["test_object.a"].Instances[addrs.NoKey].Current
   737  	testObjA.Dependencies = append(testObjA.Dependencies, mustResourceInstanceAddr("test_object.b").ContainingResource().Config())
   738  
   739  	_, diags = ctx.Apply(plan, m)
   740  	if !diags.HasErrors() {
   741  		t.Fatal("expected cycle error from apply")
   742  	}
   743  }
   744  
   745  func TestContext2Apply_resourcePostcondition(t *testing.T) {
   746  	m := testModuleInline(t, map[string]string{
   747  		"main.tf": `
   748  variable "boop" {
   749    type = string
   750  }
   751  
   752  resource "test_resource" "a" {
   753  	value = var.boop
   754  }
   755  
   756  resource "test_resource" "b" {
   757    value = test_resource.a.output
   758    lifecycle {
   759      postcondition {
   760        condition     = self.output != ""
   761        error_message = "Output must not be blank."
   762      }
   763    }
   764  }
   765  
   766  resource "test_resource" "c" {
   767    value = test_resource.b.output
   768  }
   769  `,
   770  	})
   771  
   772  	p := testProvider("test")
   773  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   774  		ResourceTypes: map[string]*configschema.Block{
   775  			"test_resource": {
   776  				Attributes: map[string]*configschema.Attribute{
   777  					"value": {
   778  						Type:     cty.String,
   779  						Required: true,
   780  					},
   781  					"output": {
   782  						Type:     cty.String,
   783  						Computed: true,
   784  					},
   785  				},
   786  			},
   787  		},
   788  	})
   789  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   790  		m := req.ProposedNewState.AsValueMap()
   791  		m["output"] = cty.UnknownVal(cty.String)
   792  
   793  		resp.PlannedState = cty.ObjectVal(m)
   794  		resp.LegacyTypeSystem = true
   795  		return resp
   796  	}
   797  	ctx := testContext2(t, &ContextOpts{
   798  		Providers: map[addrs.Provider]providers.Factory{
   799  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   800  		},
   801  	})
   802  
   803  	t.Run("condition pass", func(t *testing.T) {
   804  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
   805  			Mode: plans.NormalMode,
   806  			SetVariables: InputValues{
   807  				"boop": &InputValue{
   808  					Value:      cty.StringVal("boop"),
   809  					SourceType: ValueFromCLIArg,
   810  				},
   811  			},
   812  		})
   813  		assertNoErrors(t, diags)
   814  		if len(plan.Changes.Resources) != 3 {
   815  			t.Fatalf("unexpected plan changes: %#v", plan.Changes)
   816  		}
   817  
   818  		p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   819  			m := req.PlannedState.AsValueMap()
   820  			m["output"] = cty.StringVal(fmt.Sprintf("new-%s", m["value"].AsString()))
   821  
   822  			resp.NewState = cty.ObjectVal(m)
   823  			return resp
   824  		}
   825  		state, diags := ctx.Apply(plan, m)
   826  		assertNoErrors(t, diags)
   827  
   828  		wantResourceAttrs := map[string]struct{ value, output string }{
   829  			"a": {"boop", "new-boop"},
   830  			"b": {"new-boop", "new-new-boop"},
   831  			"c": {"new-new-boop", "new-new-new-boop"},
   832  		}
   833  		for name, attrs := range wantResourceAttrs {
   834  			addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
   835  			r := state.ResourceInstance(addr)
   836  			rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
   837  				"value":  cty.String,
   838  				"output": cty.String,
   839  			}))
   840  			if err != nil {
   841  				t.Fatalf("error decoding test_resource.a: %s", err)
   842  			}
   843  			want := cty.ObjectVal(map[string]cty.Value{
   844  				"value":  cty.StringVal(attrs.value),
   845  				"output": cty.StringVal(attrs.output),
   846  			})
   847  			if !cmp.Equal(want, rd.Value, valueComparer) {
   848  				t.Errorf("wrong attrs for %s\n%s", addr, cmp.Diff(want, rd.Value, valueComparer))
   849  			}
   850  		}
   851  	})
   852  	t.Run("condition fail", func(t *testing.T) {
   853  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
   854  			Mode: plans.NormalMode,
   855  			SetVariables: InputValues{
   856  				"boop": &InputValue{
   857  					Value:      cty.StringVal("boop"),
   858  					SourceType: ValueFromCLIArg,
   859  				},
   860  			},
   861  		})
   862  		assertNoErrors(t, diags)
   863  		if len(plan.Changes.Resources) != 3 {
   864  			t.Fatalf("unexpected plan changes: %#v", plan.Changes)
   865  		}
   866  
   867  		p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   868  			m := req.PlannedState.AsValueMap()
   869  
   870  			// For the resource with a constraint, fudge the output to make the
   871  			// condition fail.
   872  			if value := m["value"].AsString(); value == "new-boop" {
   873  				m["output"] = cty.StringVal("")
   874  			} else {
   875  				m["output"] = cty.StringVal(fmt.Sprintf("new-%s", value))
   876  			}
   877  
   878  			resp.NewState = cty.ObjectVal(m)
   879  			return resp
   880  		}
   881  		state, diags := ctx.Apply(plan, m)
   882  		if !diags.HasErrors() {
   883  			t.Fatal("succeeded; want errors")
   884  		}
   885  		if got, want := diags.Err().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
   886  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
   887  		}
   888  
   889  		// Resources a and b should still be recorded in state
   890  		wantResourceAttrs := map[string]struct{ value, output string }{
   891  			"a": {"boop", "new-boop"},
   892  			"b": {"new-boop", ""},
   893  		}
   894  		for name, attrs := range wantResourceAttrs {
   895  			addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
   896  			r := state.ResourceInstance(addr)
   897  			rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
   898  				"value":  cty.String,
   899  				"output": cty.String,
   900  			}))
   901  			if err != nil {
   902  				t.Fatalf("error decoding test_resource.a: %s", err)
   903  			}
   904  			want := cty.ObjectVal(map[string]cty.Value{
   905  				"value":  cty.StringVal(attrs.value),
   906  				"output": cty.StringVal(attrs.output),
   907  			})
   908  			if !cmp.Equal(want, rd.Value, valueComparer) {
   909  				t.Errorf("wrong attrs for %s\n%s", addr, cmp.Diff(want, rd.Value, valueComparer))
   910  			}
   911  		}
   912  
   913  		// Resource c should not be in state
   914  		if state.ResourceInstance(mustResourceInstanceAddr("test_resource.c")) != nil {
   915  			t.Error("test_resource.c should not exist in state, but is")
   916  		}
   917  	})
   918  }
   919  
   920  func TestContext2Apply_resourceConditionApplyTimeFail(t *testing.T) {
   921  	// This tests the less common situation where a condition fails due to
   922  	// a change in a resource other than the one the condition is attached to,
   923  	// and the condition result is unknown during planning.
   924  	//
   925  	// This edge case is a tricky one because it relies on Terraform still
   926  	// visiting test_resource.b (in the configuration below) to evaluate
   927  	// its conditions even though there aren't any changes directly planned
   928  	// for it, so that we can consider whether changes to test_resource.a
   929  	// have changed the outcome.
   930  
   931  	m := testModuleInline(t, map[string]string{
   932  		"main.tf": `
   933  			variable "input" {
   934  				type = string
   935  			}
   936  
   937  			resource "test_resource" "a" {
   938  				value = var.input
   939  			}
   940  
   941  			resource "test_resource" "b" {
   942  				value = "beep"
   943  
   944  				lifecycle {
   945  					postcondition {
   946  						condition     = test_resource.a.output == self.output
   947  						error_message = "Outputs must match."
   948  					}
   949  				}
   950  			}
   951  		`,
   952  	})
   953  
   954  	p := testProvider("test")
   955  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   956  		ResourceTypes: map[string]*configschema.Block{
   957  			"test_resource": {
   958  				Attributes: map[string]*configschema.Attribute{
   959  					"value": {
   960  						Type:     cty.String,
   961  						Required: true,
   962  					},
   963  					"output": {
   964  						Type:     cty.String,
   965  						Computed: true,
   966  					},
   967  				},
   968  			},
   969  		},
   970  	})
   971  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   972  		// Whenever "value" changes, "output" follows it during the apply step,
   973  		// but is initially unknown during the plan step.
   974  
   975  		m := req.ProposedNewState.AsValueMap()
   976  		priorVal := cty.NullVal(cty.String)
   977  		if !req.PriorState.IsNull() {
   978  			priorVal = req.PriorState.GetAttr("value")
   979  		}
   980  		if m["output"].IsNull() || !priorVal.RawEquals(m["value"]) {
   981  			m["output"] = cty.UnknownVal(cty.String)
   982  		}
   983  
   984  		resp.PlannedState = cty.ObjectVal(m)
   985  		resp.LegacyTypeSystem = true
   986  		return resp
   987  	}
   988  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   989  		m := req.PlannedState.AsValueMap()
   990  		m["output"] = m["value"]
   991  		resp.NewState = cty.ObjectVal(m)
   992  		return resp
   993  	}
   994  	ctx := testContext2(t, &ContextOpts{
   995  		Providers: map[addrs.Provider]providers.Factory{
   996  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   997  		},
   998  	})
   999  	instA := mustResourceInstanceAddr("test_resource.a")
  1000  	instB := mustResourceInstanceAddr("test_resource.b")
  1001  
  1002  	// Preparation: an initial plan and apply with a correct input variable
  1003  	// should succeed and give us a valid and complete state to use for the
  1004  	// subsequent plan and apply that we'll expect to fail.
  1005  	var prevRunState *states.State
  1006  	{
  1007  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1008  			Mode: plans.NormalMode,
  1009  			SetVariables: InputValues{
  1010  				"input": &InputValue{
  1011  					Value:      cty.StringVal("beep"),
  1012  					SourceType: ValueFromCLIArg,
  1013  				},
  1014  			},
  1015  		})
  1016  		assertNoErrors(t, diags)
  1017  		planA := plan.Changes.ResourceInstance(instA)
  1018  		if planA == nil || planA.Action != plans.Create {
  1019  			t.Fatalf("incorrect initial plan for instance A\nwant a 'create' change\ngot: %s", spew.Sdump(planA))
  1020  		}
  1021  		planB := plan.Changes.ResourceInstance(instB)
  1022  		if planB == nil || planB.Action != plans.Create {
  1023  			t.Fatalf("incorrect initial plan for instance B\nwant a 'create' change\ngot: %s", spew.Sdump(planB))
  1024  		}
  1025  
  1026  		state, diags := ctx.Apply(plan, m)
  1027  		assertNoErrors(t, diags)
  1028  
  1029  		stateA := state.ResourceInstance(instA)
  1030  		if stateA == nil || stateA.Current == nil || !bytes.Contains(stateA.Current.AttrsJSON, []byte(`"beep"`)) {
  1031  			t.Fatalf("incorrect initial state for instance A\ngot: %s", spew.Sdump(stateA))
  1032  		}
  1033  		stateB := state.ResourceInstance(instB)
  1034  		if stateB == nil || stateB.Current == nil || !bytes.Contains(stateB.Current.AttrsJSON, []byte(`"beep"`)) {
  1035  			t.Fatalf("incorrect initial state for instance B\ngot: %s", spew.Sdump(stateB))
  1036  		}
  1037  		prevRunState = state
  1038  	}
  1039  
  1040  	// Now we'll run another plan and apply with a different value for
  1041  	// var.input that should cause the test_resource.b condition to be unknown
  1042  	// during planning and then fail during apply.
  1043  	{
  1044  		plan, diags := ctx.Plan(m, prevRunState, &PlanOpts{
  1045  			Mode: plans.NormalMode,
  1046  			SetVariables: InputValues{
  1047  				"input": &InputValue{
  1048  					Value:      cty.StringVal("boop"), // NOTE: This has changed
  1049  					SourceType: ValueFromCLIArg,
  1050  				},
  1051  			},
  1052  		})
  1053  		assertNoErrors(t, diags)
  1054  		planA := plan.Changes.ResourceInstance(instA)
  1055  		if planA == nil || planA.Action != plans.Update {
  1056  			t.Fatalf("incorrect initial plan for instance A\nwant an 'update' change\ngot: %s", spew.Sdump(planA))
  1057  		}
  1058  		planB := plan.Changes.ResourceInstance(instB)
  1059  		if planB == nil || planB.Action != plans.NoOp {
  1060  			t.Fatalf("incorrect initial plan for instance B\nwant a 'no-op' change\ngot: %s", spew.Sdump(planB))
  1061  		}
  1062  
  1063  		_, diags = ctx.Apply(plan, m)
  1064  		if !diags.HasErrors() {
  1065  			t.Fatal("final apply succeeded, but should've failed with a postcondition error")
  1066  		}
  1067  		if len(diags) != 1 {
  1068  			t.Fatalf("expected exactly one diagnostic, but got: %s", diags.Err().Error())
  1069  		}
  1070  		if got, want := diags[0].Description().Summary, "Resource postcondition failed"; got != want {
  1071  			t.Fatalf("wrong diagnostic summary\ngot:  %s\nwant: %s", got, want)
  1072  		}
  1073  	}
  1074  }
  1075  
  1076  // pass an input through some expanded values, and back to a provider to make
  1077  // sure we can fully evaluate a provider configuration during a destroy plan.
  1078  func TestContext2Apply_destroyWithConfiguredProvider(t *testing.T) {
  1079  	m := testModuleInline(t, map[string]string{
  1080  		"main.tf": `
  1081  variable "in" {
  1082    type = map(string)
  1083    default = {
  1084      "a" = "first"
  1085      "b" = "second"
  1086    }
  1087  }
  1088  
  1089  module "mod" {
  1090    source = "./mod"
  1091    for_each = var.in
  1092    in = each.value
  1093  }
  1094  
  1095  locals {
  1096    config = [for each in module.mod : each.out]
  1097  }
  1098  
  1099  provider "other" {
  1100    output = [for each in module.mod : each.out]
  1101    local = local.config
  1102    var = var.in
  1103  }
  1104  
  1105  resource "other_object" "other" {
  1106  }
  1107  `,
  1108  		"./mod/main.tf": `
  1109  variable "in" {
  1110    type = string
  1111  }
  1112  
  1113  data "test_object" "d" {
  1114    test_string = var.in
  1115  }
  1116  
  1117  resource "test_object" "a" {
  1118    test_string = var.in
  1119  }
  1120  
  1121  output "out" {
  1122    value = data.test_object.d.output
  1123  }
  1124  `})
  1125  
  1126  	testProvider := &MockProvider{
  1127  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  1128  			Provider: providers.Schema{Block: simpleTestSchema()},
  1129  			ResourceTypes: map[string]providers.Schema{
  1130  				"test_object": providers.Schema{Block: simpleTestSchema()},
  1131  			},
  1132  			DataSources: map[string]providers.Schema{
  1133  				"test_object": providers.Schema{
  1134  					Block: &configschema.Block{
  1135  						Attributes: map[string]*configschema.Attribute{
  1136  							"test_string": {
  1137  								Type:     cty.String,
  1138  								Optional: true,
  1139  							},
  1140  							"output": {
  1141  								Type:     cty.String,
  1142  								Computed: true,
  1143  							},
  1144  						},
  1145  					},
  1146  				},
  1147  			},
  1148  		},
  1149  	}
  1150  
  1151  	testProvider.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
  1152  		cfg := req.Config.AsValueMap()
  1153  		s := cfg["test_string"].AsString()
  1154  		if !strings.Contains("firstsecond", s) {
  1155  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("expected 'first' or 'second', got %s", s))
  1156  			return resp
  1157  		}
  1158  
  1159  		cfg["output"] = cty.StringVal(s + "-ok")
  1160  		resp.State = cty.ObjectVal(cfg)
  1161  		return resp
  1162  	}
  1163  
  1164  	otherProvider := &MockProvider{
  1165  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  1166  			Provider: providers.Schema{
  1167  				Block: &configschema.Block{
  1168  					Attributes: map[string]*configschema.Attribute{
  1169  						"output": {
  1170  							Type:     cty.List(cty.String),
  1171  							Optional: true,
  1172  						},
  1173  						"local": {
  1174  							Type:     cty.List(cty.String),
  1175  							Optional: true,
  1176  						},
  1177  						"var": {
  1178  							Type:     cty.Map(cty.String),
  1179  							Optional: true,
  1180  						},
  1181  					},
  1182  				},
  1183  			},
  1184  			ResourceTypes: map[string]providers.Schema{
  1185  				"other_object": providers.Schema{Block: simpleTestSchema()},
  1186  			},
  1187  		},
  1188  	}
  1189  
  1190  	ctx := testContext2(t, &ContextOpts{
  1191  		Providers: map[addrs.Provider]providers.Factory{
  1192  			addrs.NewDefaultProvider("test"):  testProviderFuncFixed(testProvider),
  1193  			addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherProvider),
  1194  		},
  1195  	})
  1196  
  1197  	opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
  1198  	plan, diags := ctx.Plan(m, states.NewState(), opts)
  1199  	assertNoErrors(t, diags)
  1200  
  1201  	state, diags := ctx.Apply(plan, m)
  1202  	assertNoErrors(t, diags)
  1203  
  1204  	otherProvider.ConfigureProviderCalled = false
  1205  	otherProvider.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
  1206  		// check that our config is complete, even during a destroy plan
  1207  		expected := cty.ObjectVal(map[string]cty.Value{
  1208  			"local":  cty.ListVal([]cty.Value{cty.StringVal("first-ok"), cty.StringVal("second-ok")}),
  1209  			"output": cty.ListVal([]cty.Value{cty.StringVal("first-ok"), cty.StringVal("second-ok")}),
  1210  			"var": cty.MapVal(map[string]cty.Value{
  1211  				"a": cty.StringVal("first"),
  1212  				"b": cty.StringVal("second"),
  1213  			}),
  1214  		})
  1215  
  1216  		if !req.Config.RawEquals(expected) {
  1217  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf(
  1218  				`incorrect provider config:
  1219  expected: %#v
  1220  got:      %#v`,
  1221  				expected, req.Config))
  1222  		}
  1223  
  1224  		return resp
  1225  	}
  1226  
  1227  	opts.Mode = plans.DestroyMode
  1228  	// skip refresh so that we don't configure the provider before the destroy plan
  1229  	opts.SkipRefresh = true
  1230  
  1231  	// destroy only a single instance not included in the moved statements
  1232  	_, diags = ctx.Plan(m, state, opts)
  1233  	assertNoErrors(t, diags)
  1234  
  1235  	if !otherProvider.ConfigureProviderCalled {
  1236  		t.Fatal("failed to configure provider during destroy plan")
  1237  	}
  1238  }
  1239  
  1240  // check that a provider can verify a planned destroy
  1241  func TestContext2Apply_plannedDestroy(t *testing.T) {
  1242  	m := testModuleInline(t, map[string]string{
  1243  		"main.tf": `
  1244  resource "test_object" "x" {
  1245    test_string = "ok"
  1246  }`,
  1247  	})
  1248  
  1249  	p := simpleMockProvider()
  1250  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1251  		if !req.ProposedNewState.IsNull() {
  1252  			// we should only be destroying in this test
  1253  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unexpected plan with %#v", req.ProposedNewState))
  1254  			return resp
  1255  		}
  1256  
  1257  		resp.PlannedState = req.ProposedNewState
  1258  		// we're going to verify the destroy plan by inserting private data required for destroy
  1259  		resp.PlannedPrivate = append(resp.PlannedPrivate, []byte("planned")...)
  1260  		return resp
  1261  	}
  1262  
  1263  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
  1264  		// if the value is nil, we return that directly to correspond to a delete
  1265  		if !req.PlannedState.IsNull() {
  1266  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unexpected apply with %#v", req.PlannedState))
  1267  			return resp
  1268  		}
  1269  
  1270  		resp.NewState = req.PlannedState
  1271  
  1272  		// make sure we get our private data from the plan
  1273  		private := string(req.PlannedPrivate)
  1274  		if private != "planned" {
  1275  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("missing private data from plan, got %q", private))
  1276  		}
  1277  		return resp
  1278  	}
  1279  
  1280  	state := states.NewState()
  1281  	root := state.EnsureModule(addrs.RootModuleInstance)
  1282  	root.SetResourceInstanceCurrent(
  1283  		mustResourceInstanceAddr("test_object.x").Resource,
  1284  		&states.ResourceInstanceObjectSrc{
  1285  			Status:    states.ObjectReady,
  1286  			AttrsJSON: []byte(`{"test_string":"ok"}`),
  1287  		},
  1288  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  1289  	)
  1290  
  1291  	ctx := testContext2(t, &ContextOpts{
  1292  		Providers: map[addrs.Provider]providers.Factory{
  1293  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1294  		},
  1295  	})
  1296  
  1297  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1298  		Mode: plans.DestroyMode,
  1299  		// we don't want to refresh, because that actually runs a normal plan
  1300  		SkipRefresh: true,
  1301  	})
  1302  	if diags.HasErrors() {
  1303  		t.Fatalf("plan: %s", diags.Err())
  1304  	}
  1305  
  1306  	_, diags = ctx.Apply(plan, m)
  1307  	if diags.HasErrors() {
  1308  		t.Fatalf("apply: %s", diags.Err())
  1309  	}
  1310  }