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

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"sync"
     9  	"testing"
    10  
    11  	"github.com/davecgh/go-spew/spew"
    12  	"github.com/google/go-cmp/cmp"
    13  	"github.com/hashicorp/terraform/internal/addrs"
    14  	"github.com/hashicorp/terraform/internal/checks"
    15  	"github.com/hashicorp/terraform/internal/configs/configschema"
    16  	"github.com/hashicorp/terraform/internal/lang/marks"
    17  	"github.com/hashicorp/terraform/internal/plans"
    18  	"github.com/hashicorp/terraform/internal/providers"
    19  	"github.com/hashicorp/terraform/internal/states"
    20  	"github.com/hashicorp/terraform/internal/tfdiags"
    21  	"github.com/zclconf/go-cty/cty"
    22  )
    23  
    24  func TestContext2Plan_removedDuringRefresh(t *testing.T) {
    25  	// This tests the situation where an object tracked in the previous run
    26  	// state has been deleted outside of Terraform, which we should detect
    27  	// during the refresh step and thus ultimately produce a plan to recreate
    28  	// the object, since it's still present in the configuration.
    29  	m := testModuleInline(t, map[string]string{
    30  		"main.tf": `
    31  resource "test_object" "a" {
    32  }
    33  `,
    34  	})
    35  
    36  	p := simpleMockProvider()
    37  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    38  		Provider: providers.Schema{Block: simpleTestSchema()},
    39  		ResourceTypes: map[string]providers.Schema{
    40  			"test_object": {
    41  				Block: &configschema.Block{
    42  					Attributes: map[string]*configschema.Attribute{
    43  						"arg": {Type: cty.String, Optional: true},
    44  					},
    45  				},
    46  			},
    47  		},
    48  	}
    49  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
    50  		resp.NewState = cty.NullVal(req.PriorState.Type())
    51  		return resp
    52  	}
    53  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
    54  		// We should've been given the prior state JSON as our input to upgrade.
    55  		if !bytes.Contains(req.RawStateJSON, []byte("previous_run")) {
    56  			t.Fatalf("UpgradeResourceState request doesn't contain the previous run object\n%s", req.RawStateJSON)
    57  		}
    58  
    59  		// We'll put something different in "arg" as part of upgrading, just
    60  		// so that we can verify below that PrevRunState contains the upgraded
    61  		// (but NOT refreshed) version of the object.
    62  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
    63  			"arg": cty.StringVal("upgraded"),
    64  		})
    65  		return resp
    66  	}
    67  
    68  	addr := mustResourceInstanceAddr("test_object.a")
    69  	state := states.BuildState(func(s *states.SyncState) {
    70  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
    71  			AttrsJSON: []byte(`{"arg":"previous_run"}`),
    72  			Status:    states.ObjectTainted,
    73  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
    74  	})
    75  
    76  	ctx := testContext2(t, &ContextOpts{
    77  		Providers: map[addrs.Provider]providers.Factory{
    78  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
    79  		},
    80  	})
    81  
    82  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    83  	assertNoErrors(t, diags)
    84  
    85  	if !p.UpgradeResourceStateCalled {
    86  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
    87  	}
    88  	if !p.ReadResourceCalled {
    89  		t.Errorf("Provider's ReadResource wasn't called; should've been")
    90  	}
    91  
    92  	// The object should be absent from the plan's prior state, because that
    93  	// records the result of refreshing.
    94  	if got := plan.PriorState.ResourceInstance(addr); got != nil {
    95  		t.Errorf(
    96  			"instance %s is in the prior state after planning; should've been removed\n%s",
    97  			addr, spew.Sdump(got),
    98  		)
    99  	}
   100  
   101  	// However, the object should still be in the PrevRunState, because
   102  	// that reflects what we believed to exist before refreshing.
   103  	if got := plan.PrevRunState.ResourceInstance(addr); got == nil {
   104  		t.Errorf(
   105  			"instance %s is missing from the previous run state after planning; should've been preserved",
   106  			addr,
   107  		)
   108  	} else {
   109  		if !bytes.Contains(got.Current.AttrsJSON, []byte("upgraded")) {
   110  			t.Fatalf("previous run state has non-upgraded object\n%s", got.Current.AttrsJSON)
   111  		}
   112  	}
   113  
   114  	// This situation should result in a drifted resource change.
   115  	var drifted *plans.ResourceInstanceChangeSrc
   116  	for _, dr := range plan.DriftedResources {
   117  		if dr.Addr.Equal(addr) {
   118  			drifted = dr
   119  			break
   120  		}
   121  	}
   122  
   123  	if drifted == nil {
   124  		t.Errorf("instance %s is missing from the drifted resource changes", addr)
   125  	} else {
   126  		if got, want := drifted.Action, plans.Delete; got != want {
   127  			t.Errorf("unexpected instance %s drifted resource change action. got: %s, want: %s", addr, got, want)
   128  		}
   129  	}
   130  
   131  	// Because the configuration still mentions test_object.a, we should've
   132  	// planned to recreate it in order to fix the drift.
   133  	for _, c := range plan.Changes.Resources {
   134  		if c.Action != plans.Create {
   135  			t.Fatalf("expected Create action for missing %s, got %s", c.Addr, c.Action)
   136  		}
   137  	}
   138  }
   139  
   140  func TestContext2Plan_noChangeDataSourceSensitiveNestedSet(t *testing.T) {
   141  	m := testModuleInline(t, map[string]string{
   142  		"main.tf": `
   143  variable "bar" {
   144    sensitive = true
   145    default   = "baz"
   146  }
   147  
   148  data "test_data_source" "foo" {
   149    foo {
   150      bar = var.bar
   151    }
   152  }
   153  `,
   154  	})
   155  
   156  	p := new(MockProvider)
   157  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   158  		DataSources: map[string]*configschema.Block{
   159  			"test_data_source": {
   160  				Attributes: map[string]*configschema.Attribute{
   161  					"id": {
   162  						Type:     cty.String,
   163  						Computed: true,
   164  					},
   165  				},
   166  				BlockTypes: map[string]*configschema.NestedBlock{
   167  					"foo": {
   168  						Block: configschema.Block{
   169  							Attributes: map[string]*configschema.Attribute{
   170  								"bar": {Type: cty.String, Optional: true},
   171  							},
   172  						},
   173  						Nesting: configschema.NestingSet,
   174  					},
   175  				},
   176  			},
   177  		},
   178  	})
   179  
   180  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
   181  		State: cty.ObjectVal(map[string]cty.Value{
   182  			"id":  cty.StringVal("data_id"),
   183  			"foo": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}),
   184  		}),
   185  	}
   186  
   187  	state := states.NewState()
   188  	root := state.EnsureModule(addrs.RootModuleInstance)
   189  	root.SetResourceInstanceCurrent(
   190  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
   191  		&states.ResourceInstanceObjectSrc{
   192  			Status:    states.ObjectReady,
   193  			AttrsJSON: []byte(`{"id":"data_id", "foo":[{"bar":"baz"}]}`),
   194  			AttrSensitivePaths: []cty.PathValueMarks{
   195  				{
   196  					Path:  cty.GetAttrPath("foo"),
   197  					Marks: cty.NewValueMarks(marks.Sensitive),
   198  				},
   199  			},
   200  		},
   201  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   202  	)
   203  
   204  	ctx := testContext2(t, &ContextOpts{
   205  		Providers: map[addrs.Provider]providers.Factory{
   206  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   207  		},
   208  	})
   209  
   210  	plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   211  	assertNoErrors(t, diags)
   212  
   213  	for _, res := range plan.Changes.Resources {
   214  		if res.Action != plans.NoOp {
   215  			t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action)
   216  		}
   217  	}
   218  }
   219  
   220  func TestContext2Plan_orphanDataInstance(t *testing.T) {
   221  	// ensure the planned replacement of the data source is evaluated properly
   222  	m := testModuleInline(t, map[string]string{
   223  		"main.tf": `
   224  data "test_object" "a" {
   225    for_each = { new = "ok" }
   226  }
   227  
   228  output "out" {
   229    value = [ for k, _ in data.test_object.a: k ]
   230  }
   231  `,
   232  	})
   233  
   234  	p := simpleMockProvider()
   235  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   236  		resp.State = req.Config
   237  		return resp
   238  	}
   239  
   240  	state := states.BuildState(func(s *states.SyncState) {
   241  		s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a["old"]`), &states.ResourceInstanceObjectSrc{
   242  			AttrsJSON: []byte(`{"test_string":"foo"}`),
   243  			Status:    states.ObjectReady,
   244  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   245  	})
   246  
   247  	ctx := testContext2(t, &ContextOpts{
   248  		Providers: map[addrs.Provider]providers.Factory{
   249  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   250  		},
   251  	})
   252  
   253  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   254  	assertNoErrors(t, diags)
   255  
   256  	change, err := plan.Changes.Outputs[0].Decode()
   257  	if err != nil {
   258  		t.Fatal(err)
   259  	}
   260  
   261  	expected := cty.TupleVal([]cty.Value{cty.StringVal("new")})
   262  
   263  	if change.After.Equals(expected).False() {
   264  		t.Fatalf("expected %#v, got %#v\n", expected, change.After)
   265  	}
   266  }
   267  
   268  func TestContext2Plan_basicConfigurationAliases(t *testing.T) {
   269  	m := testModuleInline(t, map[string]string{
   270  		"main.tf": `
   271  provider "test" {
   272    alias = "z"
   273    test_string = "config"
   274  }
   275  
   276  module "mod" {
   277    source = "./mod"
   278    providers = {
   279      test.x = test.z
   280    }
   281  }
   282  `,
   283  
   284  		"mod/main.tf": `
   285  terraform {
   286    required_providers {
   287      test = {
   288        source = "registry.terraform.io/hashicorp/test"
   289        configuration_aliases = [ test.x ]
   290  	}
   291    }
   292  }
   293  
   294  resource "test_object" "a" {
   295    provider = test.x
   296  }
   297  
   298  `,
   299  	})
   300  
   301  	p := simpleMockProvider()
   302  
   303  	// The resource within the module should be using the provider configured
   304  	// from the root module. We should never see an empty configuration.
   305  	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   306  		if req.Config.GetAttr("test_string").IsNull() {
   307  			resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing test_string value"))
   308  		}
   309  		return resp
   310  	}
   311  
   312  	ctx := testContext2(t, &ContextOpts{
   313  		Providers: map[addrs.Provider]providers.Factory{
   314  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   315  		},
   316  	})
   317  
   318  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   319  	assertNoErrors(t, diags)
   320  }
   321  
   322  func TestContext2Plan_dataReferencesResourceInModules(t *testing.T) {
   323  	p := testProvider("test")
   324  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   325  		cfg := req.Config.AsValueMap()
   326  		cfg["id"] = cty.StringVal("d")
   327  		resp.State = cty.ObjectVal(cfg)
   328  		return resp
   329  	}
   330  
   331  	m := testModuleInline(t, map[string]string{
   332  		"main.tf": `
   333  locals {
   334    things = {
   335      old = "first"
   336      new = "second"
   337    }
   338  }
   339  
   340  module "mod" {
   341    source = "./mod"
   342    for_each = local.things
   343  }
   344  `,
   345  
   346  		"./mod/main.tf": `
   347  resource "test_resource" "a" {
   348  }
   349  
   350  data "test_data_source" "d" {
   351    depends_on = [test_resource.a]
   352  }
   353  
   354  resource "test_resource" "b" {
   355    value = data.test_data_source.d.id
   356  }
   357  `})
   358  
   359  	oldDataAddr := mustResourceInstanceAddr(`module.mod["old"].data.test_data_source.d`)
   360  
   361  	state := states.BuildState(func(s *states.SyncState) {
   362  		s.SetResourceInstanceCurrent(
   363  			mustResourceInstanceAddr(`module.mod["old"].test_resource.a`),
   364  			&states.ResourceInstanceObjectSrc{
   365  				AttrsJSON: []byte(`{"id":"a"}`),
   366  				Status:    states.ObjectReady,
   367  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   368  		)
   369  		s.SetResourceInstanceCurrent(
   370  			mustResourceInstanceAddr(`module.mod["old"].test_resource.b`),
   371  			&states.ResourceInstanceObjectSrc{
   372  				AttrsJSON: []byte(`{"id":"b","value":"d"}`),
   373  				Status:    states.ObjectReady,
   374  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   375  		)
   376  		s.SetResourceInstanceCurrent(
   377  			oldDataAddr,
   378  			&states.ResourceInstanceObjectSrc{
   379  				AttrsJSON: []byte(`{"id":"d"}`),
   380  				Status:    states.ObjectReady,
   381  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   382  		)
   383  	})
   384  
   385  	ctx := testContext2(t, &ContextOpts{
   386  		Providers: map[addrs.Provider]providers.Factory{
   387  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   388  		},
   389  	})
   390  
   391  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   392  	assertNoErrors(t, diags)
   393  
   394  	oldMod := oldDataAddr.Module
   395  
   396  	for _, c := range plan.Changes.Resources {
   397  		// there should be no changes from the old module instance
   398  		if c.Addr.Module.Equal(oldMod) && c.Action != plans.NoOp {
   399  			t.Errorf("unexpected change %s for %s\n", c.Action, c.Addr)
   400  		}
   401  	}
   402  }
   403  
   404  func TestContext2Plan_dataResourceChecksManagedResourceChange(t *testing.T) {
   405  	// This tests the situation where the remote system contains data that
   406  	// isn't valid per a data resource postcondition, but that the
   407  	// configuration is destined to make the remote system valid during apply
   408  	// and so we must defer reading the data resource and checking its
   409  	// conditions until the apply step.
   410  	//
   411  	// This is an exception to the rule tested in
   412  	// TestContext2Plan_dataReferencesResourceIndirectly which is relevant
   413  	// whenever there's at least one precondition or postcondition attached
   414  	// to a data resource.
   415  	//
   416  	// See TestContext2Plan_managedResourceChecksOtherManagedResourceChange for
   417  	// an incorrect situation where a data resource is used only indirectly
   418  	// to drive a precondition elsewhere, which therefore doesn't achieve this
   419  	// special exception.
   420  
   421  	p := testProvider("test")
   422  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   423  		Provider: providers.Schema{
   424  			Block: &configschema.Block{},
   425  		},
   426  		ResourceTypes: map[string]providers.Schema{
   427  			"test_resource": {
   428  				Block: &configschema.Block{
   429  					Attributes: map[string]*configschema.Attribute{
   430  						"id": {
   431  							Type:     cty.String,
   432  							Computed: true,
   433  						},
   434  						"valid": {
   435  							Type:     cty.Bool,
   436  							Required: true,
   437  						},
   438  					},
   439  				},
   440  			},
   441  		},
   442  		DataSources: map[string]providers.Schema{
   443  			"test_data_source": {
   444  				Block: &configschema.Block{
   445  					Attributes: map[string]*configschema.Attribute{
   446  						"id": {
   447  							Type:     cty.String,
   448  							Required: true,
   449  						},
   450  						"valid": {
   451  							Type:     cty.Bool,
   452  							Computed: true,
   453  						},
   454  					},
   455  				},
   456  			},
   457  		},
   458  	}
   459  	var mu sync.Mutex
   460  	validVal := cty.False
   461  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   462  		// NOTE: This assumes that the prior state declared below will have
   463  		// "valid" set to false already, and thus will match validVal above.
   464  		resp.NewState = req.PriorState
   465  		return resp
   466  	}
   467  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   468  		cfg := req.Config.AsValueMap()
   469  		mu.Lock()
   470  		cfg["valid"] = validVal
   471  		mu.Unlock()
   472  		resp.State = cty.ObjectVal(cfg)
   473  		return resp
   474  	}
   475  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   476  		cfg := req.Config.AsValueMap()
   477  		prior := req.PriorState.AsValueMap()
   478  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
   479  			"id":    prior["id"],
   480  			"valid": cfg["valid"],
   481  		})
   482  		return resp
   483  	}
   484  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   485  		planned := req.PlannedState.AsValueMap()
   486  
   487  		mu.Lock()
   488  		validVal = planned["valid"]
   489  		mu.Unlock()
   490  
   491  		resp.NewState = req.PlannedState
   492  		return resp
   493  	}
   494  
   495  	m := testModuleInline(t, map[string]string{
   496  		"main.tf": `
   497  
   498  resource "test_resource" "a" {
   499  	valid = true
   500  }
   501  
   502  locals {
   503  	# NOTE: We intentionally read through a local value here to make sure
   504  	# that this behavior still works even if there isn't a direct dependency
   505  	# between the data resource and the managed resource.
   506  	object_id = test_resource.a.id
   507  }
   508  
   509  data "test_data_source" "a" {
   510  	id = local.object_id
   511  
   512  	lifecycle {
   513  		postcondition {
   514  			condition     = self.valid
   515  			error_message = "Not valid!"
   516  		}
   517  	}
   518  }
   519  `})
   520  
   521  	managedAddr := mustResourceInstanceAddr(`test_resource.a`)
   522  	dataAddr := mustResourceInstanceAddr(`data.test_data_source.a`)
   523  
   524  	// This state is intended to represent the outcome of a previous apply that
   525  	// failed due to postcondition failure but had already updated the
   526  	// relevant object to be invalid.
   527  	//
   528  	// It could also potentially represent a similar situation where the
   529  	// previous apply succeeded but there has been a change outside of
   530  	// Terraform that made it invalid, although technically in that scenario
   531  	// the state data would become invalid only during the planning step. For
   532  	// our purposes here that's close enough because we don't have a real
   533  	// remote system in place anyway.
   534  	priorState := states.BuildState(func(s *states.SyncState) {
   535  		s.SetResourceInstanceCurrent(
   536  			managedAddr,
   537  			&states.ResourceInstanceObjectSrc{
   538  				// NOTE: "valid" is false here but is true in the configuration
   539  				// above, which is intended to represent that applying the
   540  				// configuration change would make this object become valid.
   541  				AttrsJSON: []byte(`{"id":"boop","valid":false}`),
   542  				Status:    states.ObjectReady,
   543  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   544  		)
   545  	})
   546  
   547  	ctx := testContext2(t, &ContextOpts{
   548  		Providers: map[addrs.Provider]providers.Factory{
   549  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   550  		},
   551  	})
   552  
   553  	plan, diags := ctx.Plan(m, priorState, DefaultPlanOpts)
   554  	assertNoErrors(t, diags)
   555  
   556  	if rc := plan.Changes.ResourceInstance(dataAddr); rc != nil {
   557  		if got, want := rc.Action, plans.Read; got != want {
   558  			t.Errorf("wrong action for %s\ngot:  %s\nwant: %s", dataAddr, got, want)
   559  		}
   560  		if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseDependencyPending; got != want {
   561  			t.Errorf("wrong action reason for %s\ngot:  %s\nwant: %s", dataAddr, got, want)
   562  		}
   563  	} else {
   564  		t.Fatalf("no planned change for %s", dataAddr)
   565  	}
   566  
   567  	if rc := plan.Changes.ResourceInstance(managedAddr); rc != nil {
   568  		if got, want := rc.Action, plans.Update; got != want {
   569  			t.Errorf("wrong action for %s\ngot:  %s\nwant: %s", managedAddr, got, want)
   570  		}
   571  		if got, want := rc.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   572  			t.Errorf("wrong action reason for %s\ngot:  %s\nwant: %s", managedAddr, got, want)
   573  		}
   574  	} else {
   575  		t.Fatalf("no planned change for %s", managedAddr)
   576  	}
   577  
   578  	// This is primarily a plan-time test, since the special handling of
   579  	// data resources is a plan-time concern, but we'll still try applying the
   580  	// plan here just to make sure it's valid.
   581  	newState, diags := ctx.Apply(plan, m)
   582  	assertNoErrors(t, diags)
   583  
   584  	if rs := newState.ResourceInstance(dataAddr); rs != nil {
   585  		if !rs.HasCurrent() {
   586  			t.Errorf("no final state for %s", dataAddr)
   587  		}
   588  	} else {
   589  		t.Errorf("no final state for %s", dataAddr)
   590  	}
   591  
   592  	if rs := newState.ResourceInstance(managedAddr); rs != nil {
   593  		if !rs.HasCurrent() {
   594  			t.Errorf("no final state for %s", managedAddr)
   595  		}
   596  	} else {
   597  		t.Errorf("no final state for %s", managedAddr)
   598  	}
   599  
   600  	if got, want := validVal, cty.True; got != want {
   601  		t.Errorf("wrong final valid value\ngot:  %#v\nwant: %#v", got, want)
   602  	}
   603  
   604  }
   605  
   606  func TestContext2Plan_managedResourceChecksOtherManagedResourceChange(t *testing.T) {
   607  	// This tests the incorrect situation where a managed resource checks
   608  	// another managed resource indirectly via a data resource.
   609  	// This doesn't work because Terraform can't tell that the data resource
   610  	// outcome will be updated by a separate managed resource change and so
   611  	// we expect it to fail.
   612  	// This would ideally have worked except that we previously included a
   613  	// special case in the rules for data resources where they only consider
   614  	// direct dependencies when deciding whether to defer (except when the
   615  	// data resource itself has conditions) and so they can potentially
   616  	// read "too early" if the user creates the explicitly-not-recommended
   617  	// situation of a data resource and a managed resource in the same
   618  	// configuration both representing the same remote object.
   619  
   620  	p := testProvider("test")
   621  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   622  		Provider: providers.Schema{
   623  			Block: &configschema.Block{},
   624  		},
   625  		ResourceTypes: map[string]providers.Schema{
   626  			"test_resource": {
   627  				Block: &configschema.Block{
   628  					Attributes: map[string]*configschema.Attribute{
   629  						"id": {
   630  							Type:     cty.String,
   631  							Computed: true,
   632  						},
   633  						"valid": {
   634  							Type:     cty.Bool,
   635  							Required: true,
   636  						},
   637  					},
   638  				},
   639  			},
   640  		},
   641  		DataSources: map[string]providers.Schema{
   642  			"test_data_source": {
   643  				Block: &configschema.Block{
   644  					Attributes: map[string]*configschema.Attribute{
   645  						"id": {
   646  							Type:     cty.String,
   647  							Required: true,
   648  						},
   649  						"valid": {
   650  							Type:     cty.Bool,
   651  							Computed: true,
   652  						},
   653  					},
   654  				},
   655  			},
   656  		},
   657  	}
   658  	var mu sync.Mutex
   659  	validVal := cty.False
   660  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   661  		// NOTE: This assumes that the prior state declared below will have
   662  		// "valid" set to false already, and thus will match validVal above.
   663  		resp.NewState = req.PriorState
   664  		return resp
   665  	}
   666  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   667  		cfg := req.Config.AsValueMap()
   668  		if cfg["id"].AsString() == "main" {
   669  			mu.Lock()
   670  			cfg["valid"] = validVal
   671  			mu.Unlock()
   672  		}
   673  		resp.State = cty.ObjectVal(cfg)
   674  		return resp
   675  	}
   676  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   677  		cfg := req.Config.AsValueMap()
   678  		prior := req.PriorState.AsValueMap()
   679  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
   680  			"id":    prior["id"],
   681  			"valid": cfg["valid"],
   682  		})
   683  		return resp
   684  	}
   685  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   686  		planned := req.PlannedState.AsValueMap()
   687  
   688  		if planned["id"].AsString() == "main" {
   689  			mu.Lock()
   690  			validVal = planned["valid"]
   691  			mu.Unlock()
   692  		}
   693  
   694  		resp.NewState = req.PlannedState
   695  		return resp
   696  	}
   697  
   698  	m := testModuleInline(t, map[string]string{
   699  		"main.tf": `
   700  
   701  resource "test_resource" "a" {
   702    valid = true
   703  }
   704  
   705  locals {
   706  	# NOTE: We intentionally read through a local value here because a
   707  	# direct reference from data.test_data_source.a to test_resource.a would
   708  	# cause Terraform to defer the data resource to the apply phase due to
   709  	# there being a pending change for the managed resource. We're explicitly
   710  	# testing the failure case where the data resource read happens too
   711  	# eagerly, which is what results from the reference being only indirect
   712  	# so Terraform can't "see" that the data resource result might be affected
   713  	# by changes to the managed resource.
   714  	object_id = test_resource.a.id
   715  }
   716  
   717  data "test_data_source" "a" {
   718  	id = local.object_id
   719  }
   720  
   721  resource "test_resource" "b" {
   722  	valid = true
   723  
   724  	lifecycle {
   725  		precondition {
   726  			condition     = data.test_data_source.a.valid
   727  			error_message = "Not valid!"
   728  		}
   729  	}
   730  }
   731  `})
   732  
   733  	managedAddrA := mustResourceInstanceAddr(`test_resource.a`)
   734  	managedAddrB := mustResourceInstanceAddr(`test_resource.b`)
   735  
   736  	// This state is intended to represent the outcome of a previous apply that
   737  	// failed due to postcondition failure but had already updated the
   738  	// relevant object to be invalid.
   739  	//
   740  	// It could also potentially represent a similar situation where the
   741  	// previous apply succeeded but there has been a change outside of
   742  	// Terraform that made it invalid, although technically in that scenario
   743  	// the state data would become invalid only during the planning step. For
   744  	// our purposes here that's close enough because we don't have a real
   745  	// remote system in place anyway.
   746  	priorState := states.BuildState(func(s *states.SyncState) {
   747  		s.SetResourceInstanceCurrent(
   748  			managedAddrA,
   749  			&states.ResourceInstanceObjectSrc{
   750  				// NOTE: "valid" is false here but is true in the configuration
   751  				// above, which is intended to represent that applying the
   752  				// configuration change would make this object become valid.
   753  				AttrsJSON: []byte(`{"id":"main","valid":false}`),
   754  				Status:    states.ObjectReady,
   755  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   756  		)
   757  		s.SetResourceInstanceCurrent(
   758  			managedAddrB,
   759  			&states.ResourceInstanceObjectSrc{
   760  				AttrsJSON: []byte(`{"id":"checker","valid":true}`),
   761  				Status:    states.ObjectReady,
   762  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   763  		)
   764  	})
   765  
   766  	ctx := testContext2(t, &ContextOpts{
   767  		Providers: map[addrs.Provider]providers.Factory{
   768  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   769  		},
   770  	})
   771  
   772  	_, diags := ctx.Plan(m, priorState, DefaultPlanOpts)
   773  	if !diags.HasErrors() {
   774  		t.Fatalf("unexpected successful plan; should've failed with non-passing precondition")
   775  	}
   776  
   777  	if got, want := diags.Err().Error(), "Resource precondition failed: Not valid!"; !strings.Contains(got, want) {
   778  		t.Errorf("Missing expected error message\ngot: %s\nwant substring: %s", got, want)
   779  	}
   780  }
   781  
   782  func TestContext2Plan_destroyWithRefresh(t *testing.T) {
   783  	m := testModuleInline(t, map[string]string{
   784  		"main.tf": `
   785  resource "test_object" "a" {
   786  }
   787  `,
   788  	})
   789  
   790  	p := simpleMockProvider()
   791  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   792  		Provider: providers.Schema{Block: simpleTestSchema()},
   793  		ResourceTypes: map[string]providers.Schema{
   794  			"test_object": {
   795  				Block: &configschema.Block{
   796  					Attributes: map[string]*configschema.Attribute{
   797  						"arg": {Type: cty.String, Optional: true},
   798  					},
   799  				},
   800  			},
   801  		},
   802  	}
   803  
   804  	// This is called from the first instance of this provider, so we can't
   805  	// check p.ReadResourceCalled after plan.
   806  	readResourceCalled := false
   807  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   808  		readResourceCalled = true
   809  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
   810  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
   811  				return cty.StringVal("current"), nil
   812  			}
   813  			return v, nil
   814  		})
   815  		if err != nil {
   816  			// shouldn't get here
   817  			t.Fatalf("ReadResourceFn transform failed")
   818  			return providers.ReadResourceResponse{}
   819  		}
   820  		return providers.ReadResourceResponse{
   821  			NewState: newVal,
   822  		}
   823  	}
   824  
   825  	upgradeResourceStateCalled := false
   826  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   827  		upgradeResourceStateCalled = true
   828  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
   829  
   830  		// In the destroy-with-refresh codepath we end up calling
   831  		// UpgradeResourceState twice, because we do so once during refreshing
   832  		// (as part making a normal plan) and then again during the plan-destroy
   833  		// walk. The second call recieves the result of the earlier refresh,
   834  		// so we need to tolerate both "before" and "current" as possible
   835  		// inputs here.
   836  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
   837  			if !bytes.Contains(req.RawStateJSON, []byte("current")) {
   838  				t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object or the 'current' object\n%s", req.RawStateJSON)
   839  			}
   840  		}
   841  
   842  		// We'll put something different in "arg" as part of upgrading, just
   843  		// so that we can verify below that PrevRunState contains the upgraded
   844  		// (but NOT refreshed) version of the object.
   845  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
   846  			"arg": cty.StringVal("upgraded"),
   847  		})
   848  		return resp
   849  	}
   850  
   851  	addr := mustResourceInstanceAddr("test_object.a")
   852  	state := states.BuildState(func(s *states.SyncState) {
   853  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   854  			AttrsJSON: []byte(`{"arg":"before"}`),
   855  			Status:    states.ObjectReady,
   856  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   857  	})
   858  
   859  	ctx := testContext2(t, &ContextOpts{
   860  		Providers: map[addrs.Provider]providers.Factory{
   861  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   862  		},
   863  	})
   864  
   865  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   866  		Mode:        plans.DestroyMode,
   867  		SkipRefresh: false, // the default
   868  	})
   869  	assertNoErrors(t, diags)
   870  
   871  	if !upgradeResourceStateCalled {
   872  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
   873  	}
   874  	if !readResourceCalled {
   875  		t.Errorf("Provider's ReadResource wasn't called; should've been")
   876  	}
   877  
   878  	if plan.PriorState == nil {
   879  		t.Fatal("missing plan state")
   880  	}
   881  
   882  	for _, c := range plan.Changes.Resources {
   883  		if c.Action != plans.Delete {
   884  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
   885  		}
   886  	}
   887  
   888  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
   889  		t.Errorf("%s has no previous run state at all after plan", addr)
   890  	} else {
   891  		if instState.Current == nil {
   892  			t.Errorf("%s has no current object in the previous run state", addr)
   893  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
   894  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   895  		}
   896  	}
   897  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
   898  		t.Errorf("%s has no prior state at all after plan", addr)
   899  	} else {
   900  		if instState.Current == nil {
   901  			t.Errorf("%s has no current object in the prior state", addr)
   902  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
   903  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   904  		}
   905  	}
   906  }
   907  
   908  func TestContext2Plan_destroySkipRefresh(t *testing.T) {
   909  	m := testModuleInline(t, map[string]string{
   910  		"main.tf": `
   911  resource "test_object" "a" {
   912  }
   913  `,
   914  	})
   915  
   916  	p := simpleMockProvider()
   917  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   918  		Provider: providers.Schema{Block: simpleTestSchema()},
   919  		ResourceTypes: map[string]providers.Schema{
   920  			"test_object": {
   921  				Block: &configschema.Block{
   922  					Attributes: map[string]*configschema.Attribute{
   923  						"arg": {Type: cty.String, Optional: true},
   924  					},
   925  				},
   926  			},
   927  		},
   928  	}
   929  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   930  		t.Helper()
   931  		t.Errorf("unexpected call to ReadResource")
   932  		resp.NewState = req.PriorState
   933  		return resp
   934  	}
   935  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   936  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
   937  		// We should've been given the prior state JSON as our input to upgrade.
   938  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
   939  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
   940  		}
   941  
   942  		// We'll put something different in "arg" as part of upgrading, just
   943  		// so that we can verify below that PrevRunState contains the upgraded
   944  		// (but NOT refreshed) version of the object.
   945  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
   946  			"arg": cty.StringVal("upgraded"),
   947  		})
   948  		return resp
   949  	}
   950  
   951  	addr := mustResourceInstanceAddr("test_object.a")
   952  	state := states.BuildState(func(s *states.SyncState) {
   953  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   954  			AttrsJSON: []byte(`{"arg":"before"}`),
   955  			Status:    states.ObjectReady,
   956  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   957  	})
   958  
   959  	ctx := testContext2(t, &ContextOpts{
   960  		Providers: map[addrs.Provider]providers.Factory{
   961  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   962  		},
   963  	})
   964  
   965  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   966  		Mode:        plans.DestroyMode,
   967  		SkipRefresh: true,
   968  	})
   969  	assertNoErrors(t, diags)
   970  
   971  	if !p.UpgradeResourceStateCalled {
   972  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
   973  	}
   974  	if p.ReadResourceCalled {
   975  		t.Errorf("Provider's ReadResource was called; shouldn't have been")
   976  	}
   977  
   978  	if plan.PriorState == nil {
   979  		t.Fatal("missing plan state")
   980  	}
   981  
   982  	for _, c := range plan.Changes.Resources {
   983  		if c.Action != plans.Delete {
   984  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
   985  		}
   986  	}
   987  
   988  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
   989  		t.Errorf("%s has no previous run state at all after plan", addr)
   990  	} else {
   991  		if instState.Current == nil {
   992  			t.Errorf("%s has no current object in the previous run state", addr)
   993  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
   994  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   995  		}
   996  	}
   997  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
   998  		t.Errorf("%s has no prior state at all after plan", addr)
   999  	} else {
  1000  		if instState.Current == nil {
  1001  			t.Errorf("%s has no current object in the prior state", addr)
  1002  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1003  			// NOTE: The prior state should still have been _upgraded_, even
  1004  			// though we skipped running refresh after upgrading it.
  1005  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1006  		}
  1007  	}
  1008  }
  1009  
  1010  func TestContext2Plan_unmarkingSensitiveAttributeForOutput(t *testing.T) {
  1011  	m := testModuleInline(t, map[string]string{
  1012  		"main.tf": `
  1013  resource "test_resource" "foo" {
  1014  }
  1015  
  1016  output "result" {
  1017    value = nonsensitive(test_resource.foo.sensitive_attr)
  1018  }
  1019  `,
  1020  	})
  1021  
  1022  	p := new(MockProvider)
  1023  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1024  		ResourceTypes: map[string]*configschema.Block{
  1025  			"test_resource": {
  1026  				Attributes: map[string]*configschema.Attribute{
  1027  					"id": {
  1028  						Type:     cty.String,
  1029  						Computed: true,
  1030  					},
  1031  					"sensitive_attr": {
  1032  						Type:      cty.String,
  1033  						Computed:  true,
  1034  						Sensitive: true,
  1035  					},
  1036  				},
  1037  			},
  1038  		},
  1039  	})
  1040  
  1041  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1042  		return providers.PlanResourceChangeResponse{
  1043  			PlannedState: cty.UnknownVal(cty.Object(map[string]cty.Type{
  1044  				"id":             cty.String,
  1045  				"sensitive_attr": cty.String,
  1046  			})),
  1047  		}
  1048  	}
  1049  
  1050  	state := states.NewState()
  1051  
  1052  	ctx := testContext2(t, &ContextOpts{
  1053  		Providers: map[addrs.Provider]providers.Factory{
  1054  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1055  		},
  1056  	})
  1057  
  1058  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  1059  	assertNoErrors(t, diags)
  1060  
  1061  	for _, res := range plan.Changes.Resources {
  1062  		if res.Action != plans.Create {
  1063  			t.Fatalf("expected create, got: %q %s", res.Addr, res.Action)
  1064  		}
  1065  	}
  1066  }
  1067  
  1068  func TestContext2Plan_destroyNoProviderConfig(t *testing.T) {
  1069  	// providers do not need to be configured during a destroy plan
  1070  	p := simpleMockProvider()
  1071  	p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
  1072  		v := req.Config.GetAttr("test_string")
  1073  		if v.IsNull() || !v.IsKnown() || v.AsString() != "ok" {
  1074  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid provider configuration: %#v", req.Config))
  1075  		}
  1076  		return resp
  1077  	}
  1078  
  1079  	m := testModuleInline(t, map[string]string{
  1080  		"main.tf": `
  1081  locals {
  1082    value = "ok"
  1083  }
  1084  
  1085  provider "test" {
  1086    test_string = local.value
  1087  }
  1088  `,
  1089  	})
  1090  
  1091  	addr := mustResourceInstanceAddr("test_object.a")
  1092  	state := states.BuildState(func(s *states.SyncState) {
  1093  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1094  			AttrsJSON: []byte(`{"test_string":"foo"}`),
  1095  			Status:    states.ObjectReady,
  1096  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1097  	})
  1098  
  1099  	ctx := testContext2(t, &ContextOpts{
  1100  		Providers: map[addrs.Provider]providers.Factory{
  1101  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1102  		},
  1103  	})
  1104  
  1105  	_, diags := ctx.Plan(m, state, &PlanOpts{
  1106  		Mode: plans.DestroyMode,
  1107  	})
  1108  	assertNoErrors(t, diags)
  1109  }
  1110  
  1111  func TestContext2Plan_movedResourceBasic(t *testing.T) {
  1112  	addrA := mustResourceInstanceAddr("test_object.a")
  1113  	addrB := mustResourceInstanceAddr("test_object.b")
  1114  	m := testModuleInline(t, map[string]string{
  1115  		"main.tf": `
  1116  			resource "test_object" "b" {
  1117  			}
  1118  
  1119  			moved {
  1120  				from = test_object.a
  1121  				to   = test_object.b
  1122  			}
  1123  		`,
  1124  	})
  1125  
  1126  	state := states.BuildState(func(s *states.SyncState) {
  1127  		// The prior state tracks test_object.a, which we should treat as
  1128  		// test_object.b because of the "moved" block in the config.
  1129  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1130  			AttrsJSON: []byte(`{}`),
  1131  			Status:    states.ObjectReady,
  1132  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1133  	})
  1134  
  1135  	p := simpleMockProvider()
  1136  	ctx := testContext2(t, &ContextOpts{
  1137  		Providers: map[addrs.Provider]providers.Factory{
  1138  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1139  		},
  1140  	})
  1141  
  1142  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1143  		Mode: plans.NormalMode,
  1144  		ForceReplace: []addrs.AbsResourceInstance{
  1145  			addrA,
  1146  		},
  1147  	})
  1148  	if diags.HasErrors() {
  1149  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1150  	}
  1151  
  1152  	t.Run(addrA.String(), func(t *testing.T) {
  1153  		instPlan := plan.Changes.ResourceInstance(addrA)
  1154  		if instPlan != nil {
  1155  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  1156  		}
  1157  	})
  1158  	t.Run(addrB.String(), func(t *testing.T) {
  1159  		instPlan := plan.Changes.ResourceInstance(addrB)
  1160  		if instPlan == nil {
  1161  			t.Fatalf("no plan for %s at all", addrB)
  1162  		}
  1163  
  1164  		if got, want := instPlan.Addr, addrB; !got.Equal(want) {
  1165  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1166  		}
  1167  		if got, want := instPlan.PrevRunAddr, addrA; !got.Equal(want) {
  1168  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1169  		}
  1170  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1171  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1172  		}
  1173  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1174  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1175  		}
  1176  	})
  1177  }
  1178  
  1179  func TestContext2Plan_movedResourceCollision(t *testing.T) {
  1180  	addrNoKey := mustResourceInstanceAddr("test_object.a")
  1181  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
  1182  	m := testModuleInline(t, map[string]string{
  1183  		"main.tf": `
  1184  			resource "test_object" "a" {
  1185  				# No "count" set, so test_object.a[0] will want
  1186  				# to implicitly move to test_object.a, but will get
  1187  				# blocked by the existing object at that address.
  1188  			}
  1189  		`,
  1190  	})
  1191  
  1192  	state := states.BuildState(func(s *states.SyncState) {
  1193  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  1194  			AttrsJSON: []byte(`{}`),
  1195  			Status:    states.ObjectReady,
  1196  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1197  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
  1198  			AttrsJSON: []byte(`{}`),
  1199  			Status:    states.ObjectReady,
  1200  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1201  	})
  1202  
  1203  	p := simpleMockProvider()
  1204  	ctx := testContext2(t, &ContextOpts{
  1205  		Providers: map[addrs.Provider]providers.Factory{
  1206  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1207  		},
  1208  	})
  1209  
  1210  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1211  		Mode: plans.NormalMode,
  1212  	})
  1213  	if diags.HasErrors() {
  1214  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1215  	}
  1216  
  1217  	// We should have a warning, though! We'll lightly abuse the "for RPC"
  1218  	// feature of diagnostics to get some more-readily-comparable diagnostic
  1219  	// values.
  1220  	gotDiags := diags.ForRPC()
  1221  	wantDiags := tfdiags.Diagnostics{
  1222  		tfdiags.Sourceless(
  1223  			tfdiags.Warning,
  1224  			"Unresolved resource instance address changes",
  1225  			`Terraform tried to adjust resource instance addresses in the prior state based on change information recorded in the configuration, but some adjustments did not succeed due to existing objects already at the intended addresses:
  1226    - test_object.a[0] could not move to test_object.a
  1227  
  1228  Terraform has planned to destroy these objects. If Terraform's proposed changes aren't appropriate, you must first resolve the conflicts using the "terraform state" subcommands and then create a new plan.`,
  1229  		),
  1230  	}.ForRPC()
  1231  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1232  		t.Errorf("wrong diagnostics\n%s", diff)
  1233  	}
  1234  
  1235  	t.Run(addrNoKey.String(), func(t *testing.T) {
  1236  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  1237  		if instPlan == nil {
  1238  			t.Fatalf("no plan for %s at all", addrNoKey)
  1239  		}
  1240  
  1241  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  1242  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1243  		}
  1244  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  1245  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1246  		}
  1247  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1248  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1249  		}
  1250  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1251  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1252  		}
  1253  	})
  1254  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  1255  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  1256  		if instPlan == nil {
  1257  			t.Fatalf("no plan for %s at all", addrZeroKey)
  1258  		}
  1259  
  1260  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  1261  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1262  		}
  1263  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  1264  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1265  		}
  1266  		if got, want := instPlan.Action, plans.Delete; got != want {
  1267  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1268  		}
  1269  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
  1270  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1271  		}
  1272  	})
  1273  }
  1274  
  1275  func TestContext2Plan_movedResourceCollisionDestroy(t *testing.T) {
  1276  	// This is like TestContext2Plan_movedResourceCollision but intended to
  1277  	// ensure we still produce the expected warning (and produce it only once)
  1278  	// when we're creating a destroy plan, rather than a normal plan.
  1279  	// (This case is interesting at the time of writing because we happen to
  1280  	// use a normal plan as a trick to refresh before creating a destroy plan.
  1281  	// This test will probably become uninteresting if a future change to
  1282  	// the destroy-time planning behavior handles refreshing in a different
  1283  	// way, which avoids this pre-processing step of running a normal plan
  1284  	// first.)
  1285  
  1286  	addrNoKey := mustResourceInstanceAddr("test_object.a")
  1287  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
  1288  	m := testModuleInline(t, map[string]string{
  1289  		"main.tf": `
  1290  			resource "test_object" "a" {
  1291  				# No "count" set, so test_object.a[0] will want
  1292  				# to implicitly move to test_object.a, but will get
  1293  				# blocked by the existing object at that address.
  1294  			}
  1295  		`,
  1296  	})
  1297  
  1298  	state := states.BuildState(func(s *states.SyncState) {
  1299  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  1300  			AttrsJSON: []byte(`{}`),
  1301  			Status:    states.ObjectReady,
  1302  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1303  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
  1304  			AttrsJSON: []byte(`{}`),
  1305  			Status:    states.ObjectReady,
  1306  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1307  	})
  1308  
  1309  	p := simpleMockProvider()
  1310  	ctx := testContext2(t, &ContextOpts{
  1311  		Providers: map[addrs.Provider]providers.Factory{
  1312  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1313  		},
  1314  	})
  1315  
  1316  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1317  		Mode: plans.DestroyMode,
  1318  	})
  1319  	if diags.HasErrors() {
  1320  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1321  	}
  1322  
  1323  	// We should have a warning, though! We'll lightly abuse the "for RPC"
  1324  	// feature of diagnostics to get some more-readily-comparable diagnostic
  1325  	// values.
  1326  	gotDiags := diags.ForRPC()
  1327  	wantDiags := tfdiags.Diagnostics{
  1328  		tfdiags.Sourceless(
  1329  			tfdiags.Warning,
  1330  			"Unresolved resource instance address changes",
  1331  			// NOTE: This message is _lightly_ confusing in the destroy case,
  1332  			// because it says "Terraform has planned to destroy these objects"
  1333  			// but this is a plan to destroy all objects, anyway. We expect the
  1334  			// conflict situation to be pretty rare though, and even rarer in
  1335  			// a "terraform destroy", so we'll just live with that for now
  1336  			// unless we see evidence that lots of folks are being confused by
  1337  			// it in practice.
  1338  			`Terraform tried to adjust resource instance addresses in the prior state based on change information recorded in the configuration, but some adjustments did not succeed due to existing objects already at the intended addresses:
  1339    - test_object.a[0] could not move to test_object.a
  1340  
  1341  Terraform has planned to destroy these objects. If Terraform's proposed changes aren't appropriate, you must first resolve the conflicts using the "terraform state" subcommands and then create a new plan.`,
  1342  		),
  1343  	}.ForRPC()
  1344  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1345  		// If we get here with a diff that makes it seem like the above warning
  1346  		// is being reported twice, the likely cause is not correctly handling
  1347  		// the warnings from the hidden normal plan we run as part of preparing
  1348  		// for a destroy plan, unless that strategy has changed in the meantime
  1349  		// since we originally wrote this test.
  1350  		t.Errorf("wrong diagnostics\n%s", diff)
  1351  	}
  1352  
  1353  	t.Run(addrNoKey.String(), func(t *testing.T) {
  1354  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  1355  		if instPlan == nil {
  1356  			t.Fatalf("no plan for %s at all", addrNoKey)
  1357  		}
  1358  
  1359  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  1360  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1361  		}
  1362  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  1363  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1364  		}
  1365  		if got, want := instPlan.Action, plans.Delete; got != want {
  1366  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1367  		}
  1368  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1369  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1370  		}
  1371  	})
  1372  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  1373  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  1374  		if instPlan == nil {
  1375  			t.Fatalf("no plan for %s at all", addrZeroKey)
  1376  		}
  1377  
  1378  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  1379  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1380  		}
  1381  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  1382  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1383  		}
  1384  		if got, want := instPlan.Action, plans.Delete; got != want {
  1385  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1386  		}
  1387  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1388  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1389  		}
  1390  	})
  1391  }
  1392  
  1393  func TestContext2Plan_movedResourceUntargeted(t *testing.T) {
  1394  	addrA := mustResourceInstanceAddr("test_object.a")
  1395  	addrB := mustResourceInstanceAddr("test_object.b")
  1396  	m := testModuleInline(t, map[string]string{
  1397  		"main.tf": `
  1398  			resource "test_object" "b" {
  1399  			}
  1400  
  1401  			moved {
  1402  				from = test_object.a
  1403  				to   = test_object.b
  1404  			}
  1405  		`,
  1406  	})
  1407  
  1408  	state := states.BuildState(func(s *states.SyncState) {
  1409  		// The prior state tracks test_object.a, which we should treat as
  1410  		// test_object.b because of the "moved" block in the config.
  1411  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1412  			AttrsJSON: []byte(`{}`),
  1413  			Status:    states.ObjectReady,
  1414  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1415  	})
  1416  
  1417  	p := simpleMockProvider()
  1418  	ctx := testContext2(t, &ContextOpts{
  1419  		Providers: map[addrs.Provider]providers.Factory{
  1420  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1421  		},
  1422  	})
  1423  
  1424  	t.Run("without targeting instance A", func(t *testing.T) {
  1425  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1426  			Mode: plans.NormalMode,
  1427  			Targets: []addrs.Targetable{
  1428  				// NOTE: addrA isn't included here, but it's pending move to addrB
  1429  				// and so this plan request is invalid.
  1430  				addrB,
  1431  			},
  1432  		})
  1433  		diags.Sort()
  1434  
  1435  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1436  		// more easily comparable than the various different diagnostics types
  1437  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1438  		// comparison-friendly, by discarding all of the dynamic type information.
  1439  		gotDiags := diags.ForRPC()
  1440  		wantDiags := tfdiags.Diagnostics{
  1441  			tfdiags.Sourceless(
  1442  				tfdiags.Warning,
  1443  				"Resource targeting is in effect",
  1444  				`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
  1445  
  1446  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`,
  1447  			),
  1448  			tfdiags.Sourceless(
  1449  				tfdiags.Error,
  1450  				"Moved resource instances excluded by targeting",
  1451  				`Resource instances in your current state have moved to new addresses in the latest configuration. Terraform must include those resource instances while planning in order to ensure a correct result, but your -target=... options to not fully cover all of those resource instances.
  1452  
  1453  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1454    -target="test_object.a"
  1455  
  1456  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1457  			),
  1458  		}.ForRPC()
  1459  
  1460  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1461  			t.Errorf("wrong diagnostics\n%s", diff)
  1462  		}
  1463  	})
  1464  	t.Run("without targeting instance B", func(t *testing.T) {
  1465  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1466  			Mode: plans.NormalMode,
  1467  			Targets: []addrs.Targetable{
  1468  				addrA,
  1469  				// NOTE: addrB isn't included here, but it's pending move from
  1470  				// addrA and so this plan request is invalid.
  1471  			},
  1472  		})
  1473  		diags.Sort()
  1474  
  1475  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1476  		// more easily comparable than the various different diagnostics types
  1477  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1478  		// comparison-friendly, by discarding all of the dynamic type information.
  1479  		gotDiags := diags.ForRPC()
  1480  		wantDiags := tfdiags.Diagnostics{
  1481  			tfdiags.Sourceless(
  1482  				tfdiags.Warning,
  1483  				"Resource targeting is in effect",
  1484  				`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
  1485  
  1486  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`,
  1487  			),
  1488  			tfdiags.Sourceless(
  1489  				tfdiags.Error,
  1490  				"Moved resource instances excluded by targeting",
  1491  				`Resource instances in your current state have moved to new addresses in the latest configuration. Terraform must include those resource instances while planning in order to ensure a correct result, but your -target=... options to not fully cover all of those resource instances.
  1492  
  1493  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1494    -target="test_object.b"
  1495  
  1496  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1497  			),
  1498  		}.ForRPC()
  1499  
  1500  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1501  			t.Errorf("wrong diagnostics\n%s", diff)
  1502  		}
  1503  	})
  1504  	t.Run("without targeting either instance", func(t *testing.T) {
  1505  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1506  			Mode: plans.NormalMode,
  1507  			Targets: []addrs.Targetable{
  1508  				mustResourceInstanceAddr("test_object.unrelated"),
  1509  				// NOTE: neither addrA nor addrB are included here, but there's
  1510  				// a pending move between them and so this is invalid.
  1511  			},
  1512  		})
  1513  		diags.Sort()
  1514  
  1515  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1516  		// more easily comparable than the various different diagnostics types
  1517  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1518  		// comparison-friendly, by discarding all of the dynamic type information.
  1519  		gotDiags := diags.ForRPC()
  1520  		wantDiags := tfdiags.Diagnostics{
  1521  			tfdiags.Sourceless(
  1522  				tfdiags.Warning,
  1523  				"Resource targeting is in effect",
  1524  				`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
  1525  
  1526  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`,
  1527  			),
  1528  			tfdiags.Sourceless(
  1529  				tfdiags.Error,
  1530  				"Moved resource instances excluded by targeting",
  1531  				`Resource instances in your current state have moved to new addresses in the latest configuration. Terraform must include those resource instances while planning in order to ensure a correct result, but your -target=... options to not fully cover all of those resource instances.
  1532  
  1533  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1534    -target="test_object.a"
  1535    -target="test_object.b"
  1536  
  1537  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1538  			),
  1539  		}.ForRPC()
  1540  
  1541  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1542  			t.Errorf("wrong diagnostics\n%s", diff)
  1543  		}
  1544  	})
  1545  	t.Run("with both addresses in the target set", func(t *testing.T) {
  1546  		// The error messages in the other subtests above suggest adding
  1547  		// addresses to the set of targets. This additional test makes sure that
  1548  		// following that advice actually leads to a valid result.
  1549  
  1550  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1551  			Mode: plans.NormalMode,
  1552  			Targets: []addrs.Targetable{
  1553  				// This time we're including both addresses in the target,
  1554  				// to get the same effect an end-user would get if following
  1555  				// the advice in our error message in the other subtests.
  1556  				addrA,
  1557  				addrB,
  1558  			},
  1559  		})
  1560  		diags.Sort()
  1561  
  1562  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1563  		// more easily comparable than the various different diagnostics types
  1564  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1565  		// comparison-friendly, by discarding all of the dynamic type information.
  1566  		gotDiags := diags.ForRPC()
  1567  		wantDiags := tfdiags.Diagnostics{
  1568  			// Still get the warning about the -target option...
  1569  			tfdiags.Sourceless(
  1570  				tfdiags.Warning,
  1571  				"Resource targeting is in effect",
  1572  				`You are creating a plan with the -target option, which means that the result of this plan may not represent all of the changes requested by the current configuration.
  1573  
  1574  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when Terraform specifically suggests to use it as part of an error message.`,
  1575  			),
  1576  			// ...but now we have no error about test_object.a
  1577  		}.ForRPC()
  1578  
  1579  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1580  			t.Errorf("wrong diagnostics\n%s", diff)
  1581  		}
  1582  	})
  1583  }
  1584  
  1585  func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) {
  1586  	addrA := mustResourceInstanceAddr("test_object.a")
  1587  	addrB := mustResourceInstanceAddr("test_object.b")
  1588  	m := testModuleInline(t, map[string]string{
  1589  		"main.tf": `
  1590  			resource "test_object" "b" {
  1591  			}
  1592  
  1593  			moved {
  1594  				from = test_object.a
  1595  				to   = test_object.b
  1596  			}
  1597  		`,
  1598  	})
  1599  
  1600  	state := states.BuildState(func(s *states.SyncState) {
  1601  		// The prior state tracks test_object.a, which we should treat as
  1602  		// test_object.b because of the "moved" block in the config.
  1603  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1604  			AttrsJSON: []byte(`{}`),
  1605  			Status:    states.ObjectReady,
  1606  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1607  	})
  1608  
  1609  	p := simpleMockProvider()
  1610  	ctx := testContext2(t, &ContextOpts{
  1611  		Providers: map[addrs.Provider]providers.Factory{
  1612  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1613  		},
  1614  	})
  1615  
  1616  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1617  		Mode: plans.RefreshOnlyMode,
  1618  	})
  1619  	if diags.HasErrors() {
  1620  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1621  	}
  1622  
  1623  	t.Run(addrA.String(), func(t *testing.T) {
  1624  		instPlan := plan.Changes.ResourceInstance(addrA)
  1625  		if instPlan != nil {
  1626  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  1627  		}
  1628  	})
  1629  	t.Run(addrB.String(), func(t *testing.T) {
  1630  		instPlan := plan.Changes.ResourceInstance(addrB)
  1631  		if instPlan != nil {
  1632  			t.Fatalf("unexpected plan for %s", addrB)
  1633  		}
  1634  	})
  1635  	t.Run("drift", func(t *testing.T) {
  1636  		var drifted *plans.ResourceInstanceChangeSrc
  1637  		for _, dr := range plan.DriftedResources {
  1638  			if dr.Addr.Equal(addrB) {
  1639  				drifted = dr
  1640  				break
  1641  			}
  1642  		}
  1643  
  1644  		if drifted == nil {
  1645  			t.Fatalf("instance %s is missing from the drifted resource changes", addrB)
  1646  		}
  1647  
  1648  		if got, want := drifted.PrevRunAddr, addrA; !got.Equal(want) {
  1649  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1650  		}
  1651  		if got, want := drifted.Action, plans.NoOp; got != want {
  1652  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1653  		}
  1654  	})
  1655  }
  1656  
  1657  func TestContext2Plan_refreshOnlyMode(t *testing.T) {
  1658  	addr := mustResourceInstanceAddr("test_object.a")
  1659  
  1660  	// The configuration, the prior state, and the refresh result intentionally
  1661  	// have different values for "test_string" so we can observe that the
  1662  	// refresh took effect but the configuration change wasn't considered.
  1663  	m := testModuleInline(t, map[string]string{
  1664  		"main.tf": `
  1665  			resource "test_object" "a" {
  1666  				arg = "after"
  1667  			}
  1668  
  1669  			output "out" {
  1670  				value = test_object.a.arg
  1671  			}
  1672  		`,
  1673  	})
  1674  	state := states.BuildState(func(s *states.SyncState) {
  1675  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1676  			AttrsJSON: []byte(`{"arg":"before"}`),
  1677  			Status:    states.ObjectReady,
  1678  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1679  	})
  1680  
  1681  	p := simpleMockProvider()
  1682  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1683  		Provider: providers.Schema{Block: simpleTestSchema()},
  1684  		ResourceTypes: map[string]providers.Schema{
  1685  			"test_object": {
  1686  				Block: &configschema.Block{
  1687  					Attributes: map[string]*configschema.Attribute{
  1688  						"arg": {Type: cty.String, Optional: true},
  1689  					},
  1690  				},
  1691  			},
  1692  		},
  1693  	}
  1694  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1695  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1696  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1697  				return cty.StringVal("current"), nil
  1698  			}
  1699  			return v, nil
  1700  		})
  1701  		if err != nil {
  1702  			// shouldn't get here
  1703  			t.Fatalf("ReadResourceFn transform failed")
  1704  			return providers.ReadResourceResponse{}
  1705  		}
  1706  		return providers.ReadResourceResponse{
  1707  			NewState: newVal,
  1708  		}
  1709  	}
  1710  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1711  		// We should've been given the prior state JSON as our input to upgrade.
  1712  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1713  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1714  		}
  1715  
  1716  		// We'll put something different in "arg" as part of upgrading, just
  1717  		// so that we can verify below that PrevRunState contains the upgraded
  1718  		// (but NOT refreshed) version of the object.
  1719  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1720  			"arg": cty.StringVal("upgraded"),
  1721  		})
  1722  		return resp
  1723  	}
  1724  
  1725  	ctx := testContext2(t, &ContextOpts{
  1726  		Providers: map[addrs.Provider]providers.Factory{
  1727  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1728  		},
  1729  	})
  1730  
  1731  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1732  		Mode: plans.RefreshOnlyMode,
  1733  	})
  1734  	if diags.HasErrors() {
  1735  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1736  	}
  1737  
  1738  	if !p.UpgradeResourceStateCalled {
  1739  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1740  	}
  1741  	if !p.ReadResourceCalled {
  1742  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1743  	}
  1744  
  1745  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1746  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1747  	}
  1748  
  1749  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1750  		t.Errorf("%s has no prior state at all after plan", addr)
  1751  	} else {
  1752  		if instState.Current == nil {
  1753  			t.Errorf("%s has no current object after plan", addr)
  1754  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1755  			// Should've saved the result of refreshing
  1756  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1757  		}
  1758  	}
  1759  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1760  		t.Errorf("%s has no previous run state at all after plan", addr)
  1761  	} else {
  1762  		if instState.Current == nil {
  1763  			t.Errorf("%s has no current object in the previous run state", addr)
  1764  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1765  			// Should've saved the result of upgrading
  1766  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1767  		}
  1768  	}
  1769  
  1770  	// The output value should also have updated. If not, it's likely that we
  1771  	// skipped updating the working state to match the refreshed state when we
  1772  	// were evaluating the resource.
  1773  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1774  		t.Errorf("no change planned for output value 'out'")
  1775  	} else {
  1776  		outChange, err := outChangeSrc.Decode()
  1777  		if err != nil {
  1778  			t.Fatalf("failed to decode output value 'out': %s", err)
  1779  		}
  1780  		got := outChange.After
  1781  		want := cty.StringVal("current")
  1782  		if !want.RawEquals(got) {
  1783  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1784  		}
  1785  	}
  1786  }
  1787  
  1788  func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) {
  1789  	addr := mustResourceInstanceAddr("test_object.a")
  1790  	deposedKey := states.DeposedKey("byebye")
  1791  
  1792  	// The configuration, the prior state, and the refresh result intentionally
  1793  	// have different values for "test_string" so we can observe that the
  1794  	// refresh took effect but the configuration change wasn't considered.
  1795  	m := testModuleInline(t, map[string]string{
  1796  		"main.tf": `
  1797  			resource "test_object" "a" {
  1798  				arg = "after"
  1799  			}
  1800  
  1801  			output "out" {
  1802  				value = test_object.a.arg
  1803  			}
  1804  		`,
  1805  	})
  1806  	state := states.BuildState(func(s *states.SyncState) {
  1807  		// Note that we're intentionally recording a _deposed_ object here,
  1808  		// and not including a current object, so a normal (non-refresh)
  1809  		// plan would normally plan to create a new object _and_ destroy
  1810  		// the deposed one, but refresh-only mode should prevent that.
  1811  		s.SetResourceInstanceDeposed(addr, deposedKey, &states.ResourceInstanceObjectSrc{
  1812  			AttrsJSON: []byte(`{"arg":"before"}`),
  1813  			Status:    states.ObjectReady,
  1814  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1815  	})
  1816  
  1817  	p := simpleMockProvider()
  1818  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1819  		Provider: providers.Schema{Block: simpleTestSchema()},
  1820  		ResourceTypes: map[string]providers.Schema{
  1821  			"test_object": {
  1822  				Block: &configschema.Block{
  1823  					Attributes: map[string]*configschema.Attribute{
  1824  						"arg": {Type: cty.String, Optional: true},
  1825  					},
  1826  				},
  1827  			},
  1828  		},
  1829  	}
  1830  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1831  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1832  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1833  				return cty.StringVal("current"), nil
  1834  			}
  1835  			return v, nil
  1836  		})
  1837  		if err != nil {
  1838  			// shouldn't get here
  1839  			t.Fatalf("ReadResourceFn transform failed")
  1840  			return providers.ReadResourceResponse{}
  1841  		}
  1842  		return providers.ReadResourceResponse{
  1843  			NewState: newVal,
  1844  		}
  1845  	}
  1846  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1847  		// We should've been given the prior state JSON as our input to upgrade.
  1848  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1849  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1850  		}
  1851  
  1852  		// We'll put something different in "arg" as part of upgrading, just
  1853  		// so that we can verify below that PrevRunState contains the upgraded
  1854  		// (but NOT refreshed) version of the object.
  1855  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1856  			"arg": cty.StringVal("upgraded"),
  1857  		})
  1858  		return resp
  1859  	}
  1860  
  1861  	ctx := testContext2(t, &ContextOpts{
  1862  		Providers: map[addrs.Provider]providers.Factory{
  1863  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1864  		},
  1865  	})
  1866  
  1867  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1868  		Mode: plans.RefreshOnlyMode,
  1869  	})
  1870  	if diags.HasErrors() {
  1871  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1872  	}
  1873  
  1874  	if !p.UpgradeResourceStateCalled {
  1875  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1876  	}
  1877  	if !p.ReadResourceCalled {
  1878  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1879  	}
  1880  
  1881  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1882  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1883  	}
  1884  
  1885  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1886  		t.Errorf("%s has no prior state at all after plan", addr)
  1887  	} else {
  1888  		if obj := instState.Deposed[deposedKey]; obj == nil {
  1889  			t.Errorf("%s has no deposed object after plan", addr)
  1890  		} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1891  			// Should've saved the result of refreshing
  1892  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1893  		}
  1894  	}
  1895  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1896  		t.Errorf("%s has no previous run state at all after plan", addr)
  1897  	} else {
  1898  		if obj := instState.Deposed[deposedKey]; obj == nil {
  1899  			t.Errorf("%s has no deposed object in the previous run state", addr)
  1900  		} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1901  			// Should've saved the result of upgrading
  1902  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1903  		}
  1904  	}
  1905  
  1906  	// The output value should also have updated. If not, it's likely that we
  1907  	// skipped updating the working state to match the refreshed state when we
  1908  	// were evaluating the resource.
  1909  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1910  		t.Errorf("no change planned for output value 'out'")
  1911  	} else {
  1912  		outChange, err := outChangeSrc.Decode()
  1913  		if err != nil {
  1914  			t.Fatalf("failed to decode output value 'out': %s", err)
  1915  		}
  1916  		got := outChange.After
  1917  		want := cty.UnknownVal(cty.String)
  1918  		if !want.RawEquals(got) {
  1919  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1920  		}
  1921  	}
  1922  
  1923  	// Deposed objects should not be represented in drift.
  1924  	if len(plan.DriftedResources) > 0 {
  1925  		t.Errorf("unexpected drifted resources (%d)", len(plan.DriftedResources))
  1926  	}
  1927  }
  1928  
  1929  func TestContext2Plan_refreshOnlyMode_orphan(t *testing.T) {
  1930  	addr := mustAbsResourceAddr("test_object.a")
  1931  
  1932  	// The configuration, the prior state, and the refresh result intentionally
  1933  	// have different values for "test_string" so we can observe that the
  1934  	// refresh took effect but the configuration change wasn't considered.
  1935  	m := testModuleInline(t, map[string]string{
  1936  		"main.tf": `
  1937  			resource "test_object" "a" {
  1938  				arg = "after"
  1939  				count = 1
  1940  			}
  1941  
  1942  			output "out" {
  1943  				value = test_object.a.*.arg
  1944  			}
  1945  		`,
  1946  	})
  1947  	state := states.BuildState(func(s *states.SyncState) {
  1948  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{
  1949  			AttrsJSON: []byte(`{"arg":"before"}`),
  1950  			Status:    states.ObjectReady,
  1951  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1952  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(1)), &states.ResourceInstanceObjectSrc{
  1953  			AttrsJSON: []byte(`{"arg":"before"}`),
  1954  			Status:    states.ObjectReady,
  1955  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1956  	})
  1957  
  1958  	p := simpleMockProvider()
  1959  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1960  		Provider: providers.Schema{Block: simpleTestSchema()},
  1961  		ResourceTypes: map[string]providers.Schema{
  1962  			"test_object": {
  1963  				Block: &configschema.Block{
  1964  					Attributes: map[string]*configschema.Attribute{
  1965  						"arg": {Type: cty.String, Optional: true},
  1966  					},
  1967  				},
  1968  			},
  1969  		},
  1970  	}
  1971  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1972  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1973  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1974  				return cty.StringVal("current"), nil
  1975  			}
  1976  			return v, nil
  1977  		})
  1978  		if err != nil {
  1979  			// shouldn't get here
  1980  			t.Fatalf("ReadResourceFn transform failed")
  1981  			return providers.ReadResourceResponse{}
  1982  		}
  1983  		return providers.ReadResourceResponse{
  1984  			NewState: newVal,
  1985  		}
  1986  	}
  1987  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1988  		// We should've been given the prior state JSON as our input to upgrade.
  1989  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1990  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1991  		}
  1992  
  1993  		// We'll put something different in "arg" as part of upgrading, just
  1994  		// so that we can verify below that PrevRunState contains the upgraded
  1995  		// (but NOT refreshed) version of the object.
  1996  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1997  			"arg": cty.StringVal("upgraded"),
  1998  		})
  1999  		return resp
  2000  	}
  2001  
  2002  	ctx := testContext2(t, &ContextOpts{
  2003  		Providers: map[addrs.Provider]providers.Factory{
  2004  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2005  		},
  2006  	})
  2007  
  2008  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2009  		Mode: plans.RefreshOnlyMode,
  2010  	})
  2011  	if diags.HasErrors() {
  2012  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2013  	}
  2014  
  2015  	if !p.UpgradeResourceStateCalled {
  2016  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  2017  	}
  2018  	if !p.ReadResourceCalled {
  2019  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  2020  	}
  2021  
  2022  	if got, want := len(plan.Changes.Resources), 0; got != want {
  2023  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  2024  	}
  2025  
  2026  	if rState := plan.PriorState.Resource(addr); rState == nil {
  2027  		t.Errorf("%s has no prior state at all after plan", addr)
  2028  	} else {
  2029  		for i := 0; i < 2; i++ {
  2030  			instKey := addrs.IntKey(i)
  2031  			if obj := rState.Instance(instKey).Current; obj == nil {
  2032  				t.Errorf("%s%s has no object after plan", addr, instKey)
  2033  			} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  2034  				// Should've saved the result of refreshing
  2035  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  2036  			}
  2037  		}
  2038  	}
  2039  	if rState := plan.PrevRunState.Resource(addr); rState == nil {
  2040  		t.Errorf("%s has no prior state at all after plan", addr)
  2041  	} else {
  2042  		for i := 0; i < 2; i++ {
  2043  			instKey := addrs.IntKey(i)
  2044  			if obj := rState.Instance(instKey).Current; obj == nil {
  2045  				t.Errorf("%s%s has no object after plan", addr, instKey)
  2046  			} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  2047  				// Should've saved the result of upgrading
  2048  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  2049  			}
  2050  		}
  2051  	}
  2052  
  2053  	// The output value should also have updated. If not, it's likely that we
  2054  	// skipped updating the working state to match the refreshed state when we
  2055  	// were evaluating the resource.
  2056  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  2057  		t.Errorf("no change planned for output value 'out'")
  2058  	} else {
  2059  		outChange, err := outChangeSrc.Decode()
  2060  		if err != nil {
  2061  			t.Fatalf("failed to decode output value 'out': %s", err)
  2062  		}
  2063  		got := outChange.After
  2064  		want := cty.TupleVal([]cty.Value{cty.StringVal("current"), cty.StringVal("current")})
  2065  		if !want.RawEquals(got) {
  2066  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  2067  		}
  2068  	}
  2069  }
  2070  
  2071  func TestContext2Plan_invalidSensitiveModuleOutput(t *testing.T) {
  2072  	m := testModuleInline(t, map[string]string{
  2073  		"child/main.tf": `
  2074  output "out" {
  2075    value = sensitive("xyz")
  2076  }`,
  2077  		"main.tf": `
  2078  module "child" {
  2079    source = "./child"
  2080  }
  2081  
  2082  output "root" {
  2083    value = module.child.out
  2084  }`,
  2085  	})
  2086  
  2087  	ctx := testContext2(t, &ContextOpts{})
  2088  
  2089  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  2090  	if !diags.HasErrors() {
  2091  		t.Fatal("succeeded; want errors")
  2092  	}
  2093  	if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
  2094  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  2095  	}
  2096  }
  2097  
  2098  func TestContext2Plan_planDataSourceSensitiveNested(t *testing.T) {
  2099  	m := testModuleInline(t, map[string]string{
  2100  		"main.tf": `
  2101  resource "test_instance" "bar" {
  2102  }
  2103  
  2104  data "test_data_source" "foo" {
  2105    foo {
  2106      bar = test_instance.bar.sensitive
  2107    }
  2108  }
  2109  `,
  2110  	})
  2111  
  2112  	p := new(MockProvider)
  2113  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2114  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
  2115  			"sensitive": cty.UnknownVal(cty.String),
  2116  		})
  2117  		return resp
  2118  	}
  2119  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2120  		ResourceTypes: map[string]*configschema.Block{
  2121  			"test_instance": {
  2122  				Attributes: map[string]*configschema.Attribute{
  2123  					"sensitive": {
  2124  						Type:      cty.String,
  2125  						Computed:  true,
  2126  						Sensitive: true,
  2127  					},
  2128  				},
  2129  			},
  2130  		},
  2131  		DataSources: map[string]*configschema.Block{
  2132  			"test_data_source": {
  2133  				Attributes: map[string]*configschema.Attribute{
  2134  					"id": {
  2135  						Type:     cty.String,
  2136  						Computed: true,
  2137  					},
  2138  				},
  2139  				BlockTypes: map[string]*configschema.NestedBlock{
  2140  					"foo": {
  2141  						Block: configschema.Block{
  2142  							Attributes: map[string]*configschema.Attribute{
  2143  								"bar": {Type: cty.String, Optional: true},
  2144  							},
  2145  						},
  2146  						Nesting: configschema.NestingSet,
  2147  					},
  2148  				},
  2149  			},
  2150  		},
  2151  	})
  2152  
  2153  	state := states.NewState()
  2154  	root := state.EnsureModule(addrs.RootModuleInstance)
  2155  	root.SetResourceInstanceCurrent(
  2156  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
  2157  		&states.ResourceInstanceObjectSrc{
  2158  			Status:    states.ObjectReady,
  2159  			AttrsJSON: []byte(`{"string":"data_id", "foo":[{"bar":"old"}]}`),
  2160  			AttrSensitivePaths: []cty.PathValueMarks{
  2161  				{
  2162  					Path:  cty.GetAttrPath("foo"),
  2163  					Marks: cty.NewValueMarks(marks.Sensitive),
  2164  				},
  2165  			},
  2166  		},
  2167  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2168  	)
  2169  	root.SetResourceInstanceCurrent(
  2170  		mustResourceInstanceAddr("test_instance.bar").Resource,
  2171  		&states.ResourceInstanceObjectSrc{
  2172  			Status:    states.ObjectReady,
  2173  			AttrsJSON: []byte(`{"sensitive":"old"}`),
  2174  			AttrSensitivePaths: []cty.PathValueMarks{
  2175  				{
  2176  					Path:  cty.GetAttrPath("sensitive"),
  2177  					Marks: cty.NewValueMarks(marks.Sensitive),
  2178  				},
  2179  			},
  2180  		},
  2181  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2182  	)
  2183  
  2184  	ctx := testContext2(t, &ContextOpts{
  2185  		Providers: map[addrs.Provider]providers.Factory{
  2186  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2187  		},
  2188  	})
  2189  
  2190  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2191  	assertNoErrors(t, diags)
  2192  
  2193  	for _, res := range plan.Changes.Resources {
  2194  		switch res.Addr.String() {
  2195  		case "test_instance.bar":
  2196  			if res.Action != plans.Update {
  2197  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2198  			}
  2199  		case "data.test_data_source.foo":
  2200  			if res.Action != plans.Read {
  2201  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2202  			}
  2203  		default:
  2204  			t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2205  		}
  2206  	}
  2207  }
  2208  
  2209  func TestContext2Plan_forceReplace(t *testing.T) {
  2210  	addrA := mustResourceInstanceAddr("test_object.a")
  2211  	addrB := mustResourceInstanceAddr("test_object.b")
  2212  	m := testModuleInline(t, map[string]string{
  2213  		"main.tf": `
  2214  			resource "test_object" "a" {
  2215  			}
  2216  			resource "test_object" "b" {
  2217  			}
  2218  		`,
  2219  	})
  2220  
  2221  	state := states.BuildState(func(s *states.SyncState) {
  2222  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  2223  			AttrsJSON: []byte(`{}`),
  2224  			Status:    states.ObjectReady,
  2225  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2226  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
  2227  			AttrsJSON: []byte(`{}`),
  2228  			Status:    states.ObjectReady,
  2229  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2230  	})
  2231  
  2232  	p := simpleMockProvider()
  2233  	ctx := testContext2(t, &ContextOpts{
  2234  		Providers: map[addrs.Provider]providers.Factory{
  2235  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2236  		},
  2237  	})
  2238  
  2239  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2240  		Mode: plans.NormalMode,
  2241  		ForceReplace: []addrs.AbsResourceInstance{
  2242  			addrA,
  2243  		},
  2244  	})
  2245  	if diags.HasErrors() {
  2246  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2247  	}
  2248  
  2249  	t.Run(addrA.String(), func(t *testing.T) {
  2250  		instPlan := plan.Changes.ResourceInstance(addrA)
  2251  		if instPlan == nil {
  2252  			t.Fatalf("no plan for %s at all", addrA)
  2253  		}
  2254  
  2255  		if got, want := instPlan.Action, plans.DeleteThenCreate; got != want {
  2256  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2257  		}
  2258  		if got, want := instPlan.ActionReason, plans.ResourceInstanceReplaceByRequest; got != want {
  2259  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2260  		}
  2261  	})
  2262  	t.Run(addrB.String(), func(t *testing.T) {
  2263  		instPlan := plan.Changes.ResourceInstance(addrB)
  2264  		if instPlan == nil {
  2265  			t.Fatalf("no plan for %s at all", addrB)
  2266  		}
  2267  
  2268  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2269  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2270  		}
  2271  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2272  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2273  		}
  2274  	})
  2275  }
  2276  
  2277  func TestContext2Plan_forceReplaceIncompleteAddr(t *testing.T) {
  2278  	addr0 := mustResourceInstanceAddr("test_object.a[0]")
  2279  	addr1 := mustResourceInstanceAddr("test_object.a[1]")
  2280  	addrBare := mustResourceInstanceAddr("test_object.a")
  2281  	m := testModuleInline(t, map[string]string{
  2282  		"main.tf": `
  2283  			resource "test_object" "a" {
  2284  				count = 2
  2285  			}
  2286  		`,
  2287  	})
  2288  
  2289  	state := states.BuildState(func(s *states.SyncState) {
  2290  		s.SetResourceInstanceCurrent(addr0, &states.ResourceInstanceObjectSrc{
  2291  			AttrsJSON: []byte(`{}`),
  2292  			Status:    states.ObjectReady,
  2293  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2294  		s.SetResourceInstanceCurrent(addr1, &states.ResourceInstanceObjectSrc{
  2295  			AttrsJSON: []byte(`{}`),
  2296  			Status:    states.ObjectReady,
  2297  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2298  	})
  2299  
  2300  	p := simpleMockProvider()
  2301  	ctx := testContext2(t, &ContextOpts{
  2302  		Providers: map[addrs.Provider]providers.Factory{
  2303  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2304  		},
  2305  	})
  2306  
  2307  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2308  		Mode: plans.NormalMode,
  2309  		ForceReplace: []addrs.AbsResourceInstance{
  2310  			addrBare,
  2311  		},
  2312  	})
  2313  	if diags.HasErrors() {
  2314  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2315  	}
  2316  	diagsErr := diags.ErrWithWarnings()
  2317  	if diagsErr == nil {
  2318  		t.Fatalf("no warnings were returned")
  2319  	}
  2320  	if got, want := diagsErr.Error(), "Incompletely-matched force-replace resource instance"; !strings.Contains(got, want) {
  2321  		t.Errorf("missing expected warning\ngot:\n%s\n\nwant substring: %s", got, want)
  2322  	}
  2323  
  2324  	t.Run(addr0.String(), func(t *testing.T) {
  2325  		instPlan := plan.Changes.ResourceInstance(addr0)
  2326  		if instPlan == nil {
  2327  			t.Fatalf("no plan for %s at all", addr0)
  2328  		}
  2329  
  2330  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2331  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2332  		}
  2333  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2334  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2335  		}
  2336  	})
  2337  	t.Run(addr1.String(), func(t *testing.T) {
  2338  		instPlan := plan.Changes.ResourceInstance(addr1)
  2339  		if instPlan == nil {
  2340  			t.Fatalf("no plan for %s at all", addr1)
  2341  		}
  2342  
  2343  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2344  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2345  		}
  2346  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2347  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2348  		}
  2349  	})
  2350  }
  2351  
  2352  // Verify that adding a module instance does force existing module data sources
  2353  // to be deferred
  2354  func TestContext2Plan_noChangeDataSourceAddingModuleInstance(t *testing.T) {
  2355  	m := testModuleInline(t, map[string]string{
  2356  		"main.tf": `
  2357  locals {
  2358    data = {
  2359      a = "a"
  2360      b = "b"
  2361    }
  2362  }
  2363  
  2364  module "one" {
  2365    source   = "./mod"
  2366    for_each = local.data
  2367    input = each.value
  2368  }
  2369  
  2370  module "two" {
  2371    source   = "./mod"
  2372    for_each = module.one
  2373    input = each.value.output
  2374  }
  2375  `,
  2376  		"mod/main.tf": `
  2377  variable "input" {
  2378  }
  2379  
  2380  resource "test_resource" "x" {
  2381    value = var.input
  2382  }
  2383  
  2384  data "test_data_source" "d" {
  2385    foo = test_resource.x.id
  2386  }
  2387  
  2388  output "output" {
  2389    value = test_resource.x.id
  2390  }
  2391  `,
  2392  	})
  2393  
  2394  	p := testProvider("test")
  2395  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  2396  		State: cty.ObjectVal(map[string]cty.Value{
  2397  			"id":  cty.StringVal("data"),
  2398  			"foo": cty.StringVal("foo"),
  2399  		}),
  2400  	}
  2401  	state := states.NewState()
  2402  	modOne := addrs.RootModuleInstance.Child("one", addrs.StringKey("a"))
  2403  	modTwo := addrs.RootModuleInstance.Child("two", addrs.StringKey("a"))
  2404  	one := state.EnsureModule(modOne)
  2405  	two := state.EnsureModule(modTwo)
  2406  	one.SetResourceInstanceCurrent(
  2407  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2408  		&states.ResourceInstanceObjectSrc{
  2409  			Status:    states.ObjectReady,
  2410  			AttrsJSON: []byte(`{"id":"foo","value":"a"}`),
  2411  		},
  2412  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2413  	)
  2414  	one.SetResourceInstanceCurrent(
  2415  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2416  		&states.ResourceInstanceObjectSrc{
  2417  			Status:    states.ObjectReady,
  2418  			AttrsJSON: []byte(`{"id":"data"}`),
  2419  		},
  2420  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2421  	)
  2422  	two.SetResourceInstanceCurrent(
  2423  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2424  		&states.ResourceInstanceObjectSrc{
  2425  			Status:    states.ObjectReady,
  2426  			AttrsJSON: []byte(`{"id":"foo","value":"foo"}`),
  2427  		},
  2428  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2429  	)
  2430  	two.SetResourceInstanceCurrent(
  2431  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2432  		&states.ResourceInstanceObjectSrc{
  2433  			Status:    states.ObjectReady,
  2434  			AttrsJSON: []byte(`{"id":"data"}`),
  2435  		},
  2436  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2437  	)
  2438  
  2439  	ctx := testContext2(t, &ContextOpts{
  2440  		Providers: map[addrs.Provider]providers.Factory{
  2441  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2442  		},
  2443  	})
  2444  
  2445  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2446  	assertNoErrors(t, diags)
  2447  
  2448  	for _, res := range plan.Changes.Resources {
  2449  		// both existing data sources should be read during plan
  2450  		if res.Addr.Module[0].InstanceKey == addrs.StringKey("b") {
  2451  			continue
  2452  		}
  2453  
  2454  		if res.Addr.Resource.Resource.Mode == addrs.DataResourceMode && res.Action != plans.NoOp {
  2455  			t.Errorf("unexpected %s plan for %s", res.Action, res.Addr)
  2456  		}
  2457  	}
  2458  }
  2459  
  2460  func TestContext2Plan_moduleExpandOrphansResourceInstance(t *testing.T) {
  2461  	// This test deals with the situation where a user has changed the
  2462  	// repetition/expansion mode for a module call while there are already
  2463  	// resource instances from the previous declaration in the state.
  2464  	//
  2465  	// This is conceptually just the same as removing the resources
  2466  	// from the module configuration only for that instance, but the
  2467  	// implementation of it ends up a little different because it's
  2468  	// an entry in the resource address's _module path_ that we'll find
  2469  	// missing, rather than the resource's own instance key, and so
  2470  	// our analyses need to handle that situation by indicating that all
  2471  	// of the resources under the missing module instance have zero
  2472  	// instances, regardless of which resource in that module we might
  2473  	// be asking about, and do so without tripping over any missing
  2474  	// registrations in the instance expander that might lead to panics
  2475  	// if we aren't careful.
  2476  	//
  2477  	// (For some history here, see https://github.com/hashicorp/terraform/issues/30110 )
  2478  
  2479  	addrNoKey := mustResourceInstanceAddr("module.child.test_object.a[0]")
  2480  	addrZeroKey := mustResourceInstanceAddr("module.child[0].test_object.a[0]")
  2481  	m := testModuleInline(t, map[string]string{
  2482  		"main.tf": `
  2483  			module "child" {
  2484  				source = "./child"
  2485  				count = 1
  2486  			}
  2487  		`,
  2488  		"child/main.tf": `
  2489  			resource "test_object" "a" {
  2490  				count = 1
  2491  			}
  2492  		`,
  2493  	})
  2494  
  2495  	state := states.BuildState(func(s *states.SyncState) {
  2496  		// Notice that addrNoKey is the address which lacks any instance key
  2497  		// for module.child, and so that module instance doesn't match the
  2498  		// call declared above with count = 1, and therefore the resource
  2499  		// inside is "orphaned" even though the resource block actually
  2500  		// still exists there.
  2501  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  2502  			AttrsJSON: []byte(`{}`),
  2503  			Status:    states.ObjectReady,
  2504  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2505  	})
  2506  
  2507  	p := simpleMockProvider()
  2508  	ctx := testContext2(t, &ContextOpts{
  2509  		Providers: map[addrs.Provider]providers.Factory{
  2510  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2511  		},
  2512  	})
  2513  
  2514  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2515  		Mode: plans.NormalMode,
  2516  	})
  2517  	if diags.HasErrors() {
  2518  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2519  	}
  2520  
  2521  	t.Run(addrNoKey.String(), func(t *testing.T) {
  2522  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  2523  		if instPlan == nil {
  2524  			t.Fatalf("no plan for %s at all", addrNoKey)
  2525  		}
  2526  
  2527  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  2528  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  2529  		}
  2530  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  2531  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  2532  		}
  2533  		if got, want := instPlan.Action, plans.Delete; got != want {
  2534  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2535  		}
  2536  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoModule; got != want {
  2537  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2538  		}
  2539  	})
  2540  
  2541  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  2542  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  2543  		if instPlan == nil {
  2544  			t.Fatalf("no plan for %s at all", addrZeroKey)
  2545  		}
  2546  
  2547  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  2548  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  2549  		}
  2550  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  2551  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  2552  		}
  2553  		if got, want := instPlan.Action, plans.Create; got != want {
  2554  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2555  		}
  2556  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2557  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2558  		}
  2559  	})
  2560  }
  2561  
  2562  func TestContext2Plan_resourcePreconditionPostcondition(t *testing.T) {
  2563  	m := testModuleInline(t, map[string]string{
  2564  		"main.tf": `
  2565  variable "boop" {
  2566    type = string
  2567  }
  2568  
  2569  resource "test_resource" "a" {
  2570    value = var.boop
  2571    lifecycle {
  2572      precondition {
  2573        condition     = var.boop == "boop"
  2574        error_message = "Wrong boop."
  2575      }
  2576      postcondition {
  2577        condition     = self.output != ""
  2578        error_message = "Output must not be blank."
  2579      }
  2580    }
  2581  }
  2582  
  2583  `,
  2584  	})
  2585  
  2586  	p := testProvider("test")
  2587  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2588  		ResourceTypes: map[string]*configschema.Block{
  2589  			"test_resource": {
  2590  				Attributes: map[string]*configschema.Attribute{
  2591  					"value": {
  2592  						Type:     cty.String,
  2593  						Required: true,
  2594  					},
  2595  					"output": {
  2596  						Type:     cty.String,
  2597  						Computed: true,
  2598  					},
  2599  				},
  2600  			},
  2601  		},
  2602  	})
  2603  
  2604  	ctx := testContext2(t, &ContextOpts{
  2605  		Providers: map[addrs.Provider]providers.Factory{
  2606  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2607  		},
  2608  	})
  2609  
  2610  	t.Run("conditions pass", func(t *testing.T) {
  2611  		p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2612  			m := req.ProposedNewState.AsValueMap()
  2613  			m["output"] = cty.StringVal("bar")
  2614  
  2615  			resp.PlannedState = cty.ObjectVal(m)
  2616  			resp.LegacyTypeSystem = true
  2617  			return resp
  2618  		}
  2619  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2620  			Mode: plans.NormalMode,
  2621  			SetVariables: InputValues{
  2622  				"boop": &InputValue{
  2623  					Value:      cty.StringVal("boop"),
  2624  					SourceType: ValueFromCLIArg,
  2625  				},
  2626  			},
  2627  		})
  2628  		assertNoErrors(t, diags)
  2629  		for _, res := range plan.Changes.Resources {
  2630  			switch res.Addr.String() {
  2631  			case "test_resource.a":
  2632  				if res.Action != plans.Create {
  2633  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2634  				}
  2635  			default:
  2636  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2637  			}
  2638  		}
  2639  	})
  2640  
  2641  	t.Run("precondition fail", func(t *testing.T) {
  2642  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2643  			Mode: plans.NormalMode,
  2644  			SetVariables: InputValues{
  2645  				"boop": &InputValue{
  2646  					Value:      cty.StringVal("nope"),
  2647  					SourceType: ValueFromCLIArg,
  2648  				},
  2649  			},
  2650  		})
  2651  		if !diags.HasErrors() {
  2652  			t.Fatal("succeeded; want errors")
  2653  		}
  2654  		if got, want := diags.Err().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2655  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  2656  		}
  2657  		if p.PlanResourceChangeCalled {
  2658  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  2659  		}
  2660  	})
  2661  
  2662  	t.Run("precondition fail refresh-only", func(t *testing.T) {
  2663  		state := states.BuildState(func(s *states.SyncState) {
  2664  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2665  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2666  				Status:    states.ObjectReady,
  2667  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2668  		})
  2669  		_, diags := ctx.Plan(m, state, &PlanOpts{
  2670  			Mode: plans.RefreshOnlyMode,
  2671  			SetVariables: InputValues{
  2672  				"boop": &InputValue{
  2673  					Value:      cty.StringVal("nope"),
  2674  					SourceType: ValueFromCLIArg,
  2675  				},
  2676  			},
  2677  		})
  2678  		assertNoErrors(t, diags)
  2679  		if len(diags) == 0 {
  2680  			t.Fatalf("no diags, but should have warnings")
  2681  		}
  2682  		if got, want := diags.ErrWithWarnings().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2683  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  2684  		}
  2685  		if !p.ReadResourceCalled {
  2686  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  2687  		}
  2688  	})
  2689  
  2690  	t.Run("postcondition fail", func(t *testing.T) {
  2691  		p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2692  			m := req.ProposedNewState.AsValueMap()
  2693  			m["output"] = cty.StringVal("")
  2694  
  2695  			resp.PlannedState = cty.ObjectVal(m)
  2696  			resp.LegacyTypeSystem = true
  2697  			return resp
  2698  		}
  2699  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2700  			Mode: plans.NormalMode,
  2701  			SetVariables: InputValues{
  2702  				"boop": &InputValue{
  2703  					Value:      cty.StringVal("boop"),
  2704  					SourceType: ValueFromCLIArg,
  2705  				},
  2706  			},
  2707  		})
  2708  		if !diags.HasErrors() {
  2709  			t.Fatal("succeeded; want errors")
  2710  		}
  2711  		if got, want := diags.Err().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
  2712  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  2713  		}
  2714  		if !p.PlanResourceChangeCalled {
  2715  			t.Errorf("Provider's PlanResourceChange wasn't called; should've been")
  2716  		}
  2717  	})
  2718  
  2719  	t.Run("postcondition fail refresh-only", func(t *testing.T) {
  2720  		state := states.BuildState(func(s *states.SyncState) {
  2721  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2722  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2723  				Status:    states.ObjectReady,
  2724  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2725  		})
  2726  		p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  2727  			newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2728  				if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "output"}) {
  2729  					return cty.StringVal(""), nil
  2730  				}
  2731  				return v, nil
  2732  			})
  2733  			if err != nil {
  2734  				// shouldn't get here
  2735  				t.Fatalf("ReadResourceFn transform failed")
  2736  				return providers.ReadResourceResponse{}
  2737  			}
  2738  			return providers.ReadResourceResponse{
  2739  				NewState: newVal,
  2740  			}
  2741  		}
  2742  		_, diags := ctx.Plan(m, state, &PlanOpts{
  2743  			Mode: plans.RefreshOnlyMode,
  2744  			SetVariables: InputValues{
  2745  				"boop": &InputValue{
  2746  					Value:      cty.StringVal("boop"),
  2747  					SourceType: ValueFromCLIArg,
  2748  				},
  2749  			},
  2750  		})
  2751  		assertNoErrors(t, diags)
  2752  		if len(diags) == 0 {
  2753  			t.Fatalf("no diags, but should have warnings")
  2754  		}
  2755  		if got, want := diags.ErrWithWarnings().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
  2756  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  2757  		}
  2758  		if !p.ReadResourceCalled {
  2759  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  2760  		}
  2761  		if p.PlanResourceChangeCalled {
  2762  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  2763  		}
  2764  	})
  2765  
  2766  	t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) {
  2767  		state := states.BuildState(func(s *states.SyncState) {
  2768  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2769  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2770  				Status:    states.ObjectReady,
  2771  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  2772  		})
  2773  		p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  2774  			newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2775  				if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "output"}) {
  2776  					return cty.StringVal(""), nil
  2777  				}
  2778  				return v, nil
  2779  			})
  2780  			if err != nil {
  2781  				// shouldn't get here
  2782  				t.Fatalf("ReadResourceFn transform failed")
  2783  				return providers.ReadResourceResponse{}
  2784  			}
  2785  			return providers.ReadResourceResponse{
  2786  				NewState: newVal,
  2787  			}
  2788  		}
  2789  		_, diags := ctx.Plan(m, state, &PlanOpts{
  2790  			Mode: plans.RefreshOnlyMode,
  2791  			SetVariables: InputValues{
  2792  				"boop": &InputValue{
  2793  					Value:      cty.StringVal("nope"),
  2794  					SourceType: ValueFromCLIArg,
  2795  				},
  2796  			},
  2797  		})
  2798  		assertNoErrors(t, diags)
  2799  		if got, want := len(diags), 2; got != want {
  2800  			t.Errorf("wrong number of warnings, got %d, want %d", got, want)
  2801  		}
  2802  		warnings := diags.ErrWithWarnings().Error()
  2803  		wantWarnings := []string{
  2804  			"Resource precondition failed: Wrong boop.",
  2805  			"Resource postcondition failed: Output must not be blank.",
  2806  		}
  2807  		for _, want := range wantWarnings {
  2808  			if !strings.Contains(warnings, want) {
  2809  				t.Errorf("missing warning:\ngot:  %s\nwant to contain: %q", warnings, want)
  2810  			}
  2811  		}
  2812  		if !p.ReadResourceCalled {
  2813  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  2814  		}
  2815  		if p.PlanResourceChangeCalled {
  2816  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  2817  		}
  2818  	})
  2819  }
  2820  
  2821  func TestContext2Plan_dataSourcePreconditionPostcondition(t *testing.T) {
  2822  	m := testModuleInline(t, map[string]string{
  2823  		"main.tf": `
  2824  variable "boop" {
  2825    type = string
  2826  }
  2827  
  2828  data "test_data_source" "a" {
  2829    foo = var.boop
  2830    lifecycle {
  2831      precondition {
  2832        condition     = var.boop == "boop"
  2833        error_message = "Wrong boop."
  2834      }
  2835      postcondition {
  2836        condition     = length(self.results) > 0
  2837        error_message = "Results cannot be empty."
  2838      }
  2839    }
  2840  }
  2841  
  2842  resource "test_resource" "a" {
  2843    value    = data.test_data_source.a.results[0]
  2844  }
  2845  `,
  2846  	})
  2847  
  2848  	p := testProvider("test")
  2849  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2850  		ResourceTypes: map[string]*configschema.Block{
  2851  			"test_resource": {
  2852  				Attributes: map[string]*configschema.Attribute{
  2853  					"value": {
  2854  						Type:     cty.String,
  2855  						Required: true,
  2856  					},
  2857  				},
  2858  			},
  2859  		},
  2860  		DataSources: map[string]*configschema.Block{
  2861  			"test_data_source": {
  2862  				Attributes: map[string]*configschema.Attribute{
  2863  					"foo": {
  2864  						Type:     cty.String,
  2865  						Required: true,
  2866  					},
  2867  					"results": {
  2868  						Type:     cty.List(cty.String),
  2869  						Computed: true,
  2870  					},
  2871  				},
  2872  			},
  2873  		},
  2874  	})
  2875  
  2876  	ctx := testContext2(t, &ContextOpts{
  2877  		Providers: map[addrs.Provider]providers.Factory{
  2878  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2879  		},
  2880  	})
  2881  
  2882  	t.Run("conditions pass", func(t *testing.T) {
  2883  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  2884  			State: cty.ObjectVal(map[string]cty.Value{
  2885  				"foo":     cty.StringVal("boop"),
  2886  				"results": cty.ListVal([]cty.Value{cty.StringVal("boop")}),
  2887  			}),
  2888  		}
  2889  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2890  			Mode: plans.NormalMode,
  2891  			SetVariables: InputValues{
  2892  				"boop": &InputValue{
  2893  					Value:      cty.StringVal("boop"),
  2894  					SourceType: ValueFromCLIArg,
  2895  				},
  2896  			},
  2897  		})
  2898  		assertNoErrors(t, diags)
  2899  		for _, res := range plan.Changes.Resources {
  2900  			switch res.Addr.String() {
  2901  			case "test_resource.a":
  2902  				if res.Action != plans.Create {
  2903  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2904  				}
  2905  			case "data.test_data_source.a":
  2906  				if res.Action != plans.Read {
  2907  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2908  				}
  2909  			default:
  2910  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2911  			}
  2912  		}
  2913  
  2914  		addr := mustResourceInstanceAddr("data.test_data_source.a")
  2915  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  2916  			t.Errorf("no check result for %s", addr)
  2917  		} else {
  2918  			wantResult := &states.CheckResultObject{
  2919  				Status: checks.StatusPass,
  2920  			}
  2921  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  2922  				t.Errorf("wrong check result for %s\n%s", addr, diff)
  2923  			}
  2924  		}
  2925  	})
  2926  
  2927  	t.Run("precondition fail", func(t *testing.T) {
  2928  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2929  			Mode: plans.NormalMode,
  2930  			SetVariables: InputValues{
  2931  				"boop": &InputValue{
  2932  					Value:      cty.StringVal("nope"),
  2933  					SourceType: ValueFromCLIArg,
  2934  				},
  2935  			},
  2936  		})
  2937  		if !diags.HasErrors() {
  2938  			t.Fatal("succeeded; want errors")
  2939  		}
  2940  		if got, want := diags.Err().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2941  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  2942  		}
  2943  		if p.ReadDataSourceCalled {
  2944  			t.Errorf("Provider's ReadResource was called; should'nt've been")
  2945  		}
  2946  	})
  2947  
  2948  	t.Run("precondition fail refresh-only", func(t *testing.T) {
  2949  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2950  			Mode: plans.RefreshOnlyMode,
  2951  			SetVariables: InputValues{
  2952  				"boop": &InputValue{
  2953  					Value:      cty.StringVal("nope"),
  2954  					SourceType: ValueFromCLIArg,
  2955  				},
  2956  			},
  2957  		})
  2958  		assertNoErrors(t, diags)
  2959  		if len(diags) == 0 {
  2960  			t.Fatalf("no diags, but should have warnings")
  2961  		}
  2962  		if got, want := diags.ErrWithWarnings().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2963  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  2964  		}
  2965  		for _, res := range plan.Changes.Resources {
  2966  			switch res.Addr.String() {
  2967  			case "test_resource.a":
  2968  				if res.Action != plans.Create {
  2969  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2970  				}
  2971  			case "data.test_data_source.a":
  2972  				if res.Action != plans.Read {
  2973  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2974  				}
  2975  			default:
  2976  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2977  			}
  2978  		}
  2979  	})
  2980  
  2981  	t.Run("postcondition fail", func(t *testing.T) {
  2982  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  2983  			State: cty.ObjectVal(map[string]cty.Value{
  2984  				"foo":     cty.StringVal("boop"),
  2985  				"results": cty.ListValEmpty(cty.String),
  2986  			}),
  2987  		}
  2988  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2989  			Mode: plans.NormalMode,
  2990  			SetVariables: InputValues{
  2991  				"boop": &InputValue{
  2992  					Value:      cty.StringVal("boop"),
  2993  					SourceType: ValueFromCLIArg,
  2994  				},
  2995  			},
  2996  		})
  2997  		if !diags.HasErrors() {
  2998  			t.Fatal("succeeded; want errors")
  2999  		}
  3000  		if got, want := diags.Err().Error(), "Resource postcondition failed: Results cannot be empty."; got != want {
  3001  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3002  		}
  3003  		if !p.ReadDataSourceCalled {
  3004  			t.Errorf("Provider's ReadDataSource wasn't called; should've been")
  3005  		}
  3006  	})
  3007  
  3008  	t.Run("postcondition fail refresh-only", func(t *testing.T) {
  3009  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3010  			State: cty.ObjectVal(map[string]cty.Value{
  3011  				"foo":     cty.StringVal("boop"),
  3012  				"results": cty.ListValEmpty(cty.String),
  3013  			}),
  3014  		}
  3015  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3016  			Mode: plans.RefreshOnlyMode,
  3017  			SetVariables: InputValues{
  3018  				"boop": &InputValue{
  3019  					Value:      cty.StringVal("boop"),
  3020  					SourceType: ValueFromCLIArg,
  3021  				},
  3022  			},
  3023  		})
  3024  		assertNoErrors(t, diags)
  3025  		if got, want := diags.ErrWithWarnings().Error(), "Resource postcondition failed: Results cannot be empty."; got != want {
  3026  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3027  		}
  3028  		addr := mustResourceInstanceAddr("data.test_data_source.a")
  3029  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3030  			t.Errorf("no check result for %s", addr)
  3031  		} else {
  3032  			wantResult := &states.CheckResultObject{
  3033  				Status: checks.StatusFail,
  3034  				FailureMessages: []string{
  3035  					"Results cannot be empty.",
  3036  				},
  3037  			}
  3038  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3039  				t.Errorf("wrong check result\n%s", diff)
  3040  			}
  3041  		}
  3042  	})
  3043  
  3044  	t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) {
  3045  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3046  			State: cty.ObjectVal(map[string]cty.Value{
  3047  				"foo":     cty.StringVal("nope"),
  3048  				"results": cty.ListValEmpty(cty.String),
  3049  			}),
  3050  		}
  3051  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3052  			Mode: plans.RefreshOnlyMode,
  3053  			SetVariables: InputValues{
  3054  				"boop": &InputValue{
  3055  					Value:      cty.StringVal("nope"),
  3056  					SourceType: ValueFromCLIArg,
  3057  				},
  3058  			},
  3059  		})
  3060  		assertNoErrors(t, diags)
  3061  		if got, want := len(diags), 2; got != want {
  3062  			t.Errorf("wrong number of warnings, got %d, want %d", got, want)
  3063  		}
  3064  		warnings := diags.ErrWithWarnings().Error()
  3065  		wantWarnings := []string{
  3066  			"Resource precondition failed: Wrong boop.",
  3067  			"Resource postcondition failed: Results cannot be empty.",
  3068  		}
  3069  		for _, want := range wantWarnings {
  3070  			if !strings.Contains(warnings, want) {
  3071  				t.Errorf("missing warning:\ngot:  %s\nwant to contain: %q", warnings, want)
  3072  			}
  3073  		}
  3074  	})
  3075  }
  3076  
  3077  func TestContext2Plan_outputPrecondition(t *testing.T) {
  3078  	m := testModuleInline(t, map[string]string{
  3079  		"main.tf": `
  3080  variable "boop" {
  3081    type = string
  3082  }
  3083  
  3084  output "a" {
  3085    value = var.boop
  3086    precondition {
  3087      condition     = var.boop == "boop"
  3088      error_message = "Wrong boop."
  3089    }
  3090  }
  3091  `,
  3092  	})
  3093  
  3094  	p := testProvider("test")
  3095  
  3096  	ctx := testContext2(t, &ContextOpts{
  3097  		Providers: map[addrs.Provider]providers.Factory{
  3098  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3099  		},
  3100  	})
  3101  
  3102  	t.Run("condition pass", func(t *testing.T) {
  3103  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3104  			Mode: plans.NormalMode,
  3105  			SetVariables: InputValues{
  3106  				"boop": &InputValue{
  3107  					Value:      cty.StringVal("boop"),
  3108  					SourceType: ValueFromCLIArg,
  3109  				},
  3110  			},
  3111  		})
  3112  		assertNoErrors(t, diags)
  3113  		addr := addrs.RootModuleInstance.OutputValue("a")
  3114  		outputPlan := plan.Changes.OutputValue(addr)
  3115  		if outputPlan == nil {
  3116  			t.Fatalf("no plan for %s at all", addr)
  3117  		}
  3118  		if got, want := outputPlan.Addr, addr; !got.Equal(want) {
  3119  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  3120  		}
  3121  		if got, want := outputPlan.Action, plans.Create; got != want {
  3122  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  3123  		}
  3124  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3125  			t.Errorf("no check result for %s", addr)
  3126  		} else {
  3127  			wantResult := &states.CheckResultObject{
  3128  				Status: checks.StatusPass,
  3129  			}
  3130  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3131  				t.Errorf("wrong check result\n%s", diff)
  3132  			}
  3133  		}
  3134  	})
  3135  
  3136  	t.Run("condition fail", func(t *testing.T) {
  3137  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3138  			Mode: plans.NormalMode,
  3139  			SetVariables: InputValues{
  3140  				"boop": &InputValue{
  3141  					Value:      cty.StringVal("nope"),
  3142  					SourceType: ValueFromCLIArg,
  3143  				},
  3144  			},
  3145  		})
  3146  		if !diags.HasErrors() {
  3147  			t.Fatal("succeeded; want errors")
  3148  		}
  3149  		if got, want := diags.Err().Error(), "Module output value precondition failed: Wrong boop."; got != want {
  3150  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3151  		}
  3152  	})
  3153  
  3154  	t.Run("condition fail refresh-only", func(t *testing.T) {
  3155  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3156  			Mode: plans.RefreshOnlyMode,
  3157  			SetVariables: InputValues{
  3158  				"boop": &InputValue{
  3159  					Value:      cty.StringVal("nope"),
  3160  					SourceType: ValueFromCLIArg,
  3161  				},
  3162  			},
  3163  		})
  3164  		assertNoErrors(t, diags)
  3165  		if len(diags) == 0 {
  3166  			t.Fatalf("no diags, but should have warnings")
  3167  		}
  3168  		if got, want := diags.ErrWithWarnings().Error(), "Module output value precondition failed: Wrong boop."; got != want {
  3169  			t.Errorf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  3170  		}
  3171  		addr := addrs.RootModuleInstance.OutputValue("a")
  3172  		outputPlan := plan.Changes.OutputValue(addr)
  3173  		if outputPlan == nil {
  3174  			t.Fatalf("no plan for %s at all", addr)
  3175  		}
  3176  		if got, want := outputPlan.Addr, addr; !got.Equal(want) {
  3177  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  3178  		}
  3179  		if got, want := outputPlan.Action, plans.Create; got != want {
  3180  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  3181  		}
  3182  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3183  			t.Errorf("no condition result for %s", addr)
  3184  		} else {
  3185  			wantResult := &states.CheckResultObject{
  3186  				Status:          checks.StatusFail,
  3187  				FailureMessages: []string{"Wrong boop."},
  3188  			}
  3189  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3190  				t.Errorf("wrong condition result\n%s", diff)
  3191  			}
  3192  		}
  3193  	})
  3194  }
  3195  
  3196  func TestContext2Plan_preconditionErrors(t *testing.T) {
  3197  	testCases := []struct {
  3198  		condition   string
  3199  		wantSummary string
  3200  		wantDetail  string
  3201  	}{
  3202  		{
  3203  			"data.test_data_source",
  3204  			"Invalid reference",
  3205  			`The "data" object must be followed by two attribute names`,
  3206  		},
  3207  		{
  3208  			"self.value",
  3209  			`Invalid "self" reference`,
  3210  			"only in resource provisioner, connection, and postcondition blocks",
  3211  		},
  3212  		{
  3213  			"data.foo.bar",
  3214  			"Reference to undeclared resource",
  3215  			`A data resource "foo" "bar" has not been declared in the root module`,
  3216  		},
  3217  		{
  3218  			"test_resource.b.value",
  3219  			"Invalid condition result",
  3220  			"Condition expression must return either true or false",
  3221  		},
  3222  		{
  3223  			"test_resource.c.value",
  3224  			"Invalid condition result",
  3225  			"Invalid condition result value: a bool is required",
  3226  		},
  3227  	}
  3228  
  3229  	p := testProvider("test")
  3230  	ctx := testContext2(t, &ContextOpts{
  3231  		Providers: map[addrs.Provider]providers.Factory{
  3232  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3233  		},
  3234  	})
  3235  
  3236  	for _, tc := range testCases {
  3237  		t.Run(tc.condition, func(t *testing.T) {
  3238  			main := fmt.Sprintf(`
  3239  			resource "test_resource" "a" {
  3240  				value = var.boop
  3241  				lifecycle {
  3242  					precondition {
  3243  						condition     = %s
  3244  						error_message = "Not relevant."
  3245  					}
  3246  				}
  3247  			}
  3248  
  3249  			resource "test_resource" "b" {
  3250  				value = null
  3251  			}
  3252  
  3253  			resource "test_resource" "c" {
  3254  				value = "bar"
  3255  			}
  3256  			`, tc.condition)
  3257  			m := testModuleInline(t, map[string]string{"main.tf": main})
  3258  
  3259  			_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  3260  			if !diags.HasErrors() {
  3261  				t.Fatal("succeeded; want errors")
  3262  			}
  3263  			diag := diags[0]
  3264  			if got, want := diag.Description().Summary, tc.wantSummary; got != want {
  3265  				t.Errorf("unexpected summary\n got: %s\nwant: %s", got, want)
  3266  			}
  3267  			if got, want := diag.Description().Detail, tc.wantDetail; !strings.Contains(got, want) {
  3268  				t.Errorf("unexpected summary\ngot: %s\nwant to contain %q", got, want)
  3269  			}
  3270  		})
  3271  	}
  3272  }
  3273  
  3274  func TestContext2Plan_preconditionSensitiveValues(t *testing.T) {
  3275  	p := testProvider("test")
  3276  	ctx := testContext2(t, &ContextOpts{
  3277  		Providers: map[addrs.Provider]providers.Factory{
  3278  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3279  		},
  3280  	})
  3281  
  3282  	m := testModuleInline(t, map[string]string{
  3283  		"main.tf": `
  3284  variable "boop" {
  3285    sensitive = true
  3286    type      = string
  3287  }
  3288  
  3289  output "a" {
  3290    sensitive = true
  3291    value     = var.boop
  3292  
  3293    precondition {
  3294      condition     = length(var.boop) <= 4
  3295      error_message = "Boop is too long, ${length(var.boop)} > 4"
  3296    }
  3297  }
  3298  `,
  3299  	})
  3300  
  3301  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3302  		Mode: plans.NormalMode,
  3303  		SetVariables: InputValues{
  3304  			"boop": &InputValue{
  3305  				Value:      cty.StringVal("bleep"),
  3306  				SourceType: ValueFromCLIArg,
  3307  			},
  3308  		},
  3309  	})
  3310  	if !diags.HasErrors() {
  3311  		t.Fatal("succeeded; want errors")
  3312  	}
  3313  	if got, want := len(diags), 2; got != want {
  3314  		t.Errorf("wrong number of diags, got %d, want %d", got, want)
  3315  	}
  3316  	for _, diag := range diags {
  3317  		desc := diag.Description()
  3318  		if desc.Summary == "Module output value precondition failed" {
  3319  			if got, want := desc.Detail, "This check failed, but has an invalid error message as described in the other accompanying messages."; !strings.Contains(got, want) {
  3320  				t.Errorf("unexpected detail\ngot: %s\nwant to contain %q", got, want)
  3321  			}
  3322  		} else if desc.Summary == "Error message refers to sensitive values" {
  3323  			if got, want := desc.Detail, "The error expression used to explain this condition refers to sensitive values, so Terraform will not display the resulting message."; !strings.Contains(got, want) {
  3324  				t.Errorf("unexpected detail\ngot: %s\nwant to contain %q", got, want)
  3325  			}
  3326  		} else {
  3327  			t.Errorf("unexpected summary\ngot: %s", desc.Summary)
  3328  		}
  3329  	}
  3330  }
  3331  
  3332  func TestContext2Plan_triggeredBy(t *testing.T) {
  3333  	m := testModuleInline(t, map[string]string{
  3334  		"main.tf": `
  3335  resource "test_object" "a" {
  3336    count = 1
  3337    test_string = "new"
  3338  }
  3339  resource "test_object" "b" {
  3340    count = 1
  3341    test_string = test_object.a[count.index].test_string
  3342    lifecycle {
  3343      # the change to test_string in the other resource should trigger replacement
  3344      replace_triggered_by = [ test_object.a[count.index].test_string ]
  3345    }
  3346  }
  3347  `,
  3348  	})
  3349  
  3350  	p := simpleMockProvider()
  3351  
  3352  	ctx := testContext2(t, &ContextOpts{
  3353  		Providers: map[addrs.Provider]providers.Factory{
  3354  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3355  		},
  3356  	})
  3357  
  3358  	state := states.BuildState(func(s *states.SyncState) {
  3359  		s.SetResourceInstanceCurrent(
  3360  			mustResourceInstanceAddr("test_object.a[0]"),
  3361  			&states.ResourceInstanceObjectSrc{
  3362  				AttrsJSON: []byte(`{"test_string":"old"}`),
  3363  				Status:    states.ObjectReady,
  3364  			},
  3365  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  3366  		)
  3367  		s.SetResourceInstanceCurrent(
  3368  			mustResourceInstanceAddr("test_object.b[0]"),
  3369  			&states.ResourceInstanceObjectSrc{
  3370  				AttrsJSON: []byte(`{}`),
  3371  				Status:    states.ObjectReady,
  3372  			},
  3373  			mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  3374  		)
  3375  	})
  3376  
  3377  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  3378  		Mode: plans.NormalMode,
  3379  	})
  3380  	if diags.HasErrors() {
  3381  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  3382  	}
  3383  	for _, c := range plan.Changes.Resources {
  3384  		switch c.Addr.String() {
  3385  		case "test_object.a[0]":
  3386  			if c.Action != plans.Update {
  3387  				t.Fatalf("unexpected %s change for %s\n", c.Action, c.Addr)
  3388  			}
  3389  		case "test_object.b[0]":
  3390  			if c.Action != plans.DeleteThenCreate {
  3391  				t.Fatalf("unexpected %s change for %s\n", c.Action, c.Addr)
  3392  			}
  3393  			if c.ActionReason != plans.ResourceInstanceReplaceByTriggers {
  3394  				t.Fatalf("incorrect reason for change: %s\n", c.ActionReason)
  3395  			}
  3396  		default:
  3397  			t.Fatal("unexpected change", c.Addr, c.Action)
  3398  		}
  3399  	}
  3400  }
  3401  
  3402  func TestContext2Plan_dataSchemaChange(t *testing.T) {
  3403  	// We can't decode the prior state when a data source upgrades the schema
  3404  	// in an incompatible way. Since prior state for data sources is purely
  3405  	// informational, decoding should be skipped altogether.
  3406  	m := testModuleInline(t, map[string]string{
  3407  		"main.tf": `
  3408  data "test_object" "a" {
  3409    obj {
  3410      # args changes from a list to a map
  3411      args = {
  3412        val = "string"
  3413  	}
  3414    }
  3415  }
  3416  `,
  3417  	})
  3418  
  3419  	p := new(MockProvider)
  3420  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  3421  		DataSources: map[string]*configschema.Block{
  3422  			"test_object": {
  3423  				Attributes: map[string]*configschema.Attribute{
  3424  					"id": {
  3425  						Type:     cty.String,
  3426  						Computed: true,
  3427  					},
  3428  				},
  3429  				BlockTypes: map[string]*configschema.NestedBlock{
  3430  					"obj": {
  3431  						Block: configschema.Block{
  3432  							Attributes: map[string]*configschema.Attribute{
  3433  								"args": {Type: cty.Map(cty.String), Optional: true},
  3434  							},
  3435  						},
  3436  						Nesting: configschema.NestingSet,
  3437  					},
  3438  				},
  3439  			},
  3440  		},
  3441  	})
  3442  
  3443  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
  3444  		resp.State = req.Config
  3445  		return resp
  3446  	}
  3447  
  3448  	state := states.BuildState(func(s *states.SyncState) {
  3449  		s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a`), &states.ResourceInstanceObjectSrc{
  3450  			AttrsJSON: []byte(`{"id":"old","obj":[{"args":["string"]}]}`),
  3451  			Status:    states.ObjectReady,
  3452  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  3453  	})
  3454  
  3455  	ctx := testContext2(t, &ContextOpts{
  3456  		Providers: map[addrs.Provider]providers.Factory{
  3457  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3458  		},
  3459  	})
  3460  
  3461  	_, diags := ctx.Plan(m, state, DefaultPlanOpts)
  3462  	assertNoErrors(t, diags)
  3463  }
  3464  
  3465  func TestContext2Plan_applyGraphError(t *testing.T) {
  3466  	m := testModuleInline(t, map[string]string{
  3467  		"main.tf": `
  3468  resource "test_object" "a" {
  3469  }
  3470  resource "test_object" "b" {
  3471  	depends_on = [test_object.a]
  3472  }
  3473  `,
  3474  	})
  3475  
  3476  	p := simpleMockProvider()
  3477  
  3478  	// Here we introduce a cycle via state which only shows up in the apply
  3479  	// graph where the actual destroy instances are connected in the graph.
  3480  	// This could happen for example when a user has an existing state with
  3481  	// stored dependencies, and changes the config in such a way that
  3482  	// contradicts the stored dependencies.
  3483  	state := states.NewState()
  3484  	root := state.EnsureModule(addrs.RootModuleInstance)
  3485  	root.SetResourceInstanceCurrent(
  3486  		mustResourceInstanceAddr("test_object.a").Resource,
  3487  		&states.ResourceInstanceObjectSrc{
  3488  			Status:       states.ObjectTainted,
  3489  			AttrsJSON:    []byte(`{"test_string":"a"}`),
  3490  			Dependencies: []addrs.ConfigResource{mustResourceInstanceAddr("test_object.b").ContainingResource().Config()},
  3491  		},
  3492  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  3493  	)
  3494  	root.SetResourceInstanceCurrent(
  3495  		mustResourceInstanceAddr("test_object.b").Resource,
  3496  		&states.ResourceInstanceObjectSrc{
  3497  			Status:    states.ObjectTainted,
  3498  			AttrsJSON: []byte(`{"test_string":"b"}`),
  3499  		},
  3500  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  3501  	)
  3502  
  3503  	ctx := testContext2(t, &ContextOpts{
  3504  		Providers: map[addrs.Provider]providers.Factory{
  3505  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3506  		},
  3507  	})
  3508  
  3509  	_, diags := ctx.Plan(m, state, &PlanOpts{
  3510  		Mode: plans.NormalMode,
  3511  	})
  3512  	if !diags.HasErrors() {
  3513  		t.Fatal("cycle error not detected")
  3514  	}
  3515  
  3516  	msg := diags.ErrWithWarnings().Error()
  3517  	if !strings.Contains(msg, "Cycle") {
  3518  		t.Fatalf("no cycle error found:\n got: %s\n", msg)
  3519  	}
  3520  }