github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/terraform/context_plan2_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/davecgh/go-spew/spew"
    11  	"github.com/google/go-cmp/cmp"
    12  	"github.com/muratcelep/terraform/not-internal/addrs"
    13  	"github.com/muratcelep/terraform/not-internal/configs/configschema"
    14  	"github.com/muratcelep/terraform/not-internal/lang/marks"
    15  	"github.com/muratcelep/terraform/not-internal/plans"
    16  	"github.com/muratcelep/terraform/not-internal/providers"
    17  	"github.com/muratcelep/terraform/not-internal/states"
    18  	"github.com/muratcelep/terraform/not-internal/tfdiags"
    19  	"github.com/zclconf/go-cty/cty"
    20  )
    21  
    22  func TestContext2Plan_removedDuringRefresh(t *testing.T) {
    23  	// This tests the situation where an object tracked in the previous run
    24  	// state has been deleted outside of Terraform, which we should detect
    25  	// during the refresh step and thus ultimately produce a plan to recreate
    26  	// the object, since it's still present in the configuration.
    27  	m := testModuleInline(t, map[string]string{
    28  		"main.tf": `
    29  resource "test_object" "a" {
    30  }
    31  `,
    32  	})
    33  
    34  	p := simpleMockProvider()
    35  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    36  		Provider: providers.Schema{Block: simpleTestSchema()},
    37  		ResourceTypes: map[string]providers.Schema{
    38  			"test_object": {
    39  				Block: &configschema.Block{
    40  					Attributes: map[string]*configschema.Attribute{
    41  						"arg": {Type: cty.String, Optional: true},
    42  					},
    43  				},
    44  			},
    45  		},
    46  	}
    47  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
    48  		resp.NewState = cty.NullVal(req.PriorState.Type())
    49  		return resp
    50  	}
    51  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
    52  		// We should've been given the prior state JSON as our input to upgrade.
    53  		if !bytes.Contains(req.RawStateJSON, []byte("previous_run")) {
    54  			t.Fatalf("UpgradeResourceState request doesn't contain the previous run object\n%s", req.RawStateJSON)
    55  		}
    56  
    57  		// We'll put something different in "arg" as part of upgrading, just
    58  		// so that we can verify below that PrevRunState contains the upgraded
    59  		// (but NOT refreshed) version of the object.
    60  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
    61  			"arg": cty.StringVal("upgraded"),
    62  		})
    63  		return resp
    64  	}
    65  
    66  	addr := mustResourceInstanceAddr("test_object.a")
    67  	state := states.BuildState(func(s *states.SyncState) {
    68  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
    69  			AttrsJSON: []byte(`{"arg":"previous_run"}`),
    70  			Status:    states.ObjectTainted,
    71  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
    72  	})
    73  
    74  	ctx := testContext2(t, &ContextOpts{
    75  		Providers: map[addrs.Provider]providers.Factory{
    76  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
    77  		},
    78  	})
    79  
    80  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    81  	assertNoErrors(t, diags)
    82  
    83  	if !p.UpgradeResourceStateCalled {
    84  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
    85  	}
    86  	if !p.ReadResourceCalled {
    87  		t.Errorf("Provider's ReadResource wasn't called; should've been")
    88  	}
    89  
    90  	// The object should be absent from the plan's prior state, because that
    91  	// records the result of refreshing.
    92  	if got := plan.PriorState.ResourceInstance(addr); got != nil {
    93  		t.Errorf(
    94  			"instance %s is in the prior state after planning; should've been removed\n%s",
    95  			addr, spew.Sdump(got),
    96  		)
    97  	}
    98  
    99  	// However, the object should still be in the PrevRunState, because
   100  	// that reflects what we believed to exist before refreshing.
   101  	if got := plan.PrevRunState.ResourceInstance(addr); got == nil {
   102  		t.Errorf(
   103  			"instance %s is missing from the previous run state after planning; should've been preserved",
   104  			addr,
   105  		)
   106  	} else {
   107  		if !bytes.Contains(got.Current.AttrsJSON, []byte("upgraded")) {
   108  			t.Fatalf("previous run state has non-upgraded object\n%s", got.Current.AttrsJSON)
   109  		}
   110  	}
   111  
   112  	// This situation should result in a drifted resource change.
   113  	var drifted *plans.ResourceInstanceChangeSrc
   114  	for _, dr := range plan.DriftedResources {
   115  		if dr.Addr.Equal(addr) {
   116  			drifted = dr
   117  			break
   118  		}
   119  	}
   120  
   121  	if drifted == nil {
   122  		t.Errorf("instance %s is missing from the drifted resource changes", addr)
   123  	} else {
   124  		if got, want := drifted.Action, plans.Delete; got != want {
   125  			t.Errorf("unexpected instance %s drifted resource change action. got: %s, want: %s", addr, got, want)
   126  		}
   127  	}
   128  
   129  	// Because the configuration still mentions test_object.a, we should've
   130  	// planned to recreate it in order to fix the drift.
   131  	for _, c := range plan.Changes.Resources {
   132  		if c.Action != plans.Create {
   133  			t.Fatalf("expected Create action for missing %s, got %s", c.Addr, c.Action)
   134  		}
   135  	}
   136  }
   137  
   138  func TestContext2Plan_noChangeDataSourceSensitiveNestedSet(t *testing.T) {
   139  	m := testModuleInline(t, map[string]string{
   140  		"main.tf": `
   141  variable "bar" {
   142    sensitive = true
   143    default   = "baz"
   144  }
   145  
   146  data "test_data_source" "foo" {
   147    foo {
   148      bar = var.bar
   149    }
   150  }
   151  `,
   152  	})
   153  
   154  	p := new(MockProvider)
   155  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   156  		DataSources: map[string]*configschema.Block{
   157  			"test_data_source": {
   158  				Attributes: map[string]*configschema.Attribute{
   159  					"id": {
   160  						Type:     cty.String,
   161  						Computed: true,
   162  					},
   163  				},
   164  				BlockTypes: map[string]*configschema.NestedBlock{
   165  					"foo": {
   166  						Block: configschema.Block{
   167  							Attributes: map[string]*configschema.Attribute{
   168  								"bar": {Type: cty.String, Optional: true},
   169  							},
   170  						},
   171  						Nesting: configschema.NestingSet,
   172  					},
   173  				},
   174  			},
   175  		},
   176  	})
   177  
   178  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
   179  		State: cty.ObjectVal(map[string]cty.Value{
   180  			"id":  cty.StringVal("data_id"),
   181  			"foo": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}),
   182  		}),
   183  	}
   184  
   185  	state := states.NewState()
   186  	root := state.EnsureModule(addrs.RootModuleInstance)
   187  	root.SetResourceInstanceCurrent(
   188  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
   189  		&states.ResourceInstanceObjectSrc{
   190  			Status:    states.ObjectReady,
   191  			AttrsJSON: []byte(`{"id":"data_id", "foo":[{"bar":"baz"}]}`),
   192  			AttrSensitivePaths: []cty.PathValueMarks{
   193  				{
   194  					Path:  cty.GetAttrPath("foo"),
   195  					Marks: cty.NewValueMarks(marks.Sensitive),
   196  				},
   197  			},
   198  		},
   199  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   200  	)
   201  
   202  	ctx := testContext2(t, &ContextOpts{
   203  		Providers: map[addrs.Provider]providers.Factory{
   204  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   205  		},
   206  	})
   207  
   208  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   209  	assertNoErrors(t, diags)
   210  
   211  	for _, res := range plan.Changes.Resources {
   212  		if res.Action != plans.NoOp {
   213  			t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action)
   214  		}
   215  	}
   216  }
   217  
   218  func TestContext2Plan_orphanDataInstance(t *testing.T) {
   219  	// ensure the planned replacement of the data source is evaluated properly
   220  	m := testModuleInline(t, map[string]string{
   221  		"main.tf": `
   222  data "test_object" "a" {
   223    for_each = { new = "ok" }
   224  }
   225  
   226  output "out" {
   227    value = [ for k, _ in data.test_object.a: k ]
   228  }
   229  `,
   230  	})
   231  
   232  	p := simpleMockProvider()
   233  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   234  		resp.State = req.Config
   235  		return resp
   236  	}
   237  
   238  	state := states.BuildState(func(s *states.SyncState) {
   239  		s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a["old"]`), &states.ResourceInstanceObjectSrc{
   240  			AttrsJSON: []byte(`{"test_string":"foo"}`),
   241  			Status:    states.ObjectReady,
   242  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   243  	})
   244  
   245  	ctx := testContext2(t, &ContextOpts{
   246  		Providers: map[addrs.Provider]providers.Factory{
   247  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   248  		},
   249  	})
   250  
   251  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   252  	assertNoErrors(t, diags)
   253  
   254  	change, err := plan.Changes.Outputs[0].Decode()
   255  	if err != nil {
   256  		t.Fatal(err)
   257  	}
   258  
   259  	expected := cty.TupleVal([]cty.Value{cty.StringVal("new")})
   260  
   261  	if change.After.Equals(expected).False() {
   262  		t.Fatalf("expected %#v, got %#v\n", expected, change.After)
   263  	}
   264  }
   265  
   266  func TestContext2Plan_basicConfigurationAliases(t *testing.T) {
   267  	m := testModuleInline(t, map[string]string{
   268  		"main.tf": `
   269  provider "test" {
   270    alias = "z"
   271    test_string = "config"
   272  }
   273  
   274  module "mod" {
   275    source = "./mod"
   276    providers = {
   277      test.x = test.z
   278    }
   279  }
   280  `,
   281  
   282  		"mod/main.tf": `
   283  terraform {
   284    required_providers {
   285      test = {
   286        source = "registry.terraform.io/hashicorp/test"
   287        configuration_aliases = [ test.x ]
   288  	}
   289    }
   290  }
   291  
   292  resource "test_object" "a" {
   293    provider = test.x
   294  }
   295  
   296  `,
   297  	})
   298  
   299  	p := simpleMockProvider()
   300  
   301  	// The resource within the module should be using the provider configured
   302  	// from the root module. We should never see an empty configuration.
   303  	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   304  		if req.Config.GetAttr("test_string").IsNull() {
   305  			resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing test_string value"))
   306  		}
   307  		return resp
   308  	}
   309  
   310  	ctx := testContext2(t, &ContextOpts{
   311  		Providers: map[addrs.Provider]providers.Factory{
   312  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   313  		},
   314  	})
   315  
   316  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   317  	assertNoErrors(t, diags)
   318  }
   319  
   320  func TestContext2Plan_dataReferencesResourceInModules(t *testing.T) {
   321  	p := testProvider("test")
   322  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   323  		cfg := req.Config.AsValueMap()
   324  		cfg["id"] = cty.StringVal("d")
   325  		resp.State = cty.ObjectVal(cfg)
   326  		return resp
   327  	}
   328  
   329  	m := testModuleInline(t, map[string]string{
   330  		"main.tf": `
   331  locals {
   332    things = {
   333      old = "first"
   334      new = "second"
   335    }
   336  }
   337  
   338  module "mod" {
   339    source = "./mod"
   340    for_each = local.things
   341  }
   342  `,
   343  
   344  		"./mod/main.tf": `
   345  resource "test_resource" "a" {
   346  }
   347  
   348  data "test_data_source" "d" {
   349    depends_on = [test_resource.a]
   350  }
   351  
   352  resource "test_resource" "b" {
   353    value = data.test_data_source.d.id
   354  }
   355  `})
   356  
   357  	oldDataAddr := mustResourceInstanceAddr(`module.mod["old"].data.test_data_source.d`)
   358  
   359  	state := states.BuildState(func(s *states.SyncState) {
   360  		s.SetResourceInstanceCurrent(
   361  			mustResourceInstanceAddr(`module.mod["old"].test_resource.a`),
   362  			&states.ResourceInstanceObjectSrc{
   363  				AttrsJSON: []byte(`{"id":"a"}`),
   364  				Status:    states.ObjectReady,
   365  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   366  		)
   367  		s.SetResourceInstanceCurrent(
   368  			mustResourceInstanceAddr(`module.mod["old"].test_resource.b`),
   369  			&states.ResourceInstanceObjectSrc{
   370  				AttrsJSON: []byte(`{"id":"b","value":"d"}`),
   371  				Status:    states.ObjectReady,
   372  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   373  		)
   374  		s.SetResourceInstanceCurrent(
   375  			oldDataAddr,
   376  			&states.ResourceInstanceObjectSrc{
   377  				AttrsJSON: []byte(`{"id":"d"}`),
   378  				Status:    states.ObjectReady,
   379  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   380  		)
   381  	})
   382  
   383  	ctx := testContext2(t, &ContextOpts{
   384  		Providers: map[addrs.Provider]providers.Factory{
   385  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   386  		},
   387  	})
   388  
   389  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   390  	assertNoErrors(t, diags)
   391  
   392  	oldMod := oldDataAddr.Module
   393  
   394  	for _, c := range plan.Changes.Resources {
   395  		// there should be no changes from the old module instance
   396  		if c.Addr.Module.Equal(oldMod) && c.Action != plans.NoOp {
   397  			t.Errorf("unexpected change %s for %s\n", c.Action, c.Addr)
   398  		}
   399  	}
   400  }
   401  
   402  func TestContext2Plan_destroyWithRefresh(t *testing.T) {
   403  	m := testModuleInline(t, map[string]string{
   404  		"main.tf": `
   405  resource "test_object" "a" {
   406  }
   407  `,
   408  	})
   409  
   410  	p := simpleMockProvider()
   411  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   412  		Provider: providers.Schema{Block: simpleTestSchema()},
   413  		ResourceTypes: map[string]providers.Schema{
   414  			"test_object": {
   415  				Block: &configschema.Block{
   416  					Attributes: map[string]*configschema.Attribute{
   417  						"arg": {Type: cty.String, Optional: true},
   418  					},
   419  				},
   420  			},
   421  		},
   422  	}
   423  
   424  	// This is called from the first instance of this provider, so we can't
   425  	// check p.ReadResourceCalled after plan.
   426  	readResourceCalled := false
   427  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   428  		readResourceCalled = true
   429  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
   430  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
   431  				return cty.StringVal("current"), nil
   432  			}
   433  			return v, nil
   434  		})
   435  		if err != nil {
   436  			// shouldn't get here
   437  			t.Fatalf("ReadResourceFn transform failed")
   438  			return providers.ReadResourceResponse{}
   439  		}
   440  		return providers.ReadResourceResponse{
   441  			NewState: newVal,
   442  		}
   443  	}
   444  
   445  	upgradeResourceStateCalled := false
   446  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   447  		upgradeResourceStateCalled = true
   448  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
   449  
   450  		// In the destroy-with-refresh codepath we end up calling
   451  		// UpgradeResourceState twice, because we do so once during refreshing
   452  		// (as part making a normal plan) and then again during the plan-destroy
   453  		// walk. The second call recieves the result of the earlier refresh,
   454  		// so we need to tolerate both "before" and "current" as possible
   455  		// inputs here.
   456  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
   457  			if !bytes.Contains(req.RawStateJSON, []byte("current")) {
   458  				t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object or the 'current' object\n%s", req.RawStateJSON)
   459  			}
   460  		}
   461  
   462  		// We'll put something different in "arg" as part of upgrading, just
   463  		// so that we can verify below that PrevRunState contains the upgraded
   464  		// (but NOT refreshed) version of the object.
   465  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
   466  			"arg": cty.StringVal("upgraded"),
   467  		})
   468  		return resp
   469  	}
   470  
   471  	addr := mustResourceInstanceAddr("test_object.a")
   472  	state := states.BuildState(func(s *states.SyncState) {
   473  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   474  			AttrsJSON: []byte(`{"arg":"before"}`),
   475  			Status:    states.ObjectReady,
   476  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   477  	})
   478  
   479  	ctx := testContext2(t, &ContextOpts{
   480  		Providers: map[addrs.Provider]providers.Factory{
   481  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   482  		},
   483  	})
   484  
   485  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   486  		Mode:        plans.DestroyMode,
   487  		SkipRefresh: false, // the default
   488  	})
   489  	assertNoErrors(t, diags)
   490  
   491  	if !upgradeResourceStateCalled {
   492  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
   493  	}
   494  	if !readResourceCalled {
   495  		t.Errorf("Provider's ReadResource wasn't called; should've been")
   496  	}
   497  
   498  	if plan.PriorState == nil {
   499  		t.Fatal("missing plan state")
   500  	}
   501  
   502  	for _, c := range plan.Changes.Resources {
   503  		if c.Action != plans.Delete {
   504  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
   505  		}
   506  	}
   507  
   508  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
   509  		t.Errorf("%s has no previous run state at all after plan", addr)
   510  	} else {
   511  		if instState.Current == nil {
   512  			t.Errorf("%s has no current object in the previous run state", addr)
   513  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
   514  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   515  		}
   516  	}
   517  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
   518  		t.Errorf("%s has no prior state at all after plan", addr)
   519  	} else {
   520  		if instState.Current == nil {
   521  			t.Errorf("%s has no current object in the prior state", addr)
   522  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
   523  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   524  		}
   525  	}
   526  }
   527  
   528  func TestContext2Plan_destroySkipRefresh(t *testing.T) {
   529  	m := testModuleInline(t, map[string]string{
   530  		"main.tf": `
   531  resource "test_object" "a" {
   532  }
   533  `,
   534  	})
   535  
   536  	p := simpleMockProvider()
   537  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   538  		Provider: providers.Schema{Block: simpleTestSchema()},
   539  		ResourceTypes: map[string]providers.Schema{
   540  			"test_object": {
   541  				Block: &configschema.Block{
   542  					Attributes: map[string]*configschema.Attribute{
   543  						"arg": {Type: cty.String, Optional: true},
   544  					},
   545  				},
   546  			},
   547  		},
   548  	}
   549  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   550  		t.Helper()
   551  		t.Errorf("unexpected call to ReadResource")
   552  		resp.NewState = req.PriorState
   553  		return resp
   554  	}
   555  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   556  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
   557  		// We should've been given the prior state JSON as our input to upgrade.
   558  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
   559  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
   560  		}
   561  
   562  		// We'll put something different in "arg" as part of upgrading, just
   563  		// so that we can verify below that PrevRunState contains the upgraded
   564  		// (but NOT refreshed) version of the object.
   565  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
   566  			"arg": cty.StringVal("upgraded"),
   567  		})
   568  		return resp
   569  	}
   570  
   571  	addr := mustResourceInstanceAddr("test_object.a")
   572  	state := states.BuildState(func(s *states.SyncState) {
   573  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   574  			AttrsJSON: []byte(`{"arg":"before"}`),
   575  			Status:    states.ObjectReady,
   576  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   577  	})
   578  
   579  	ctx := testContext2(t, &ContextOpts{
   580  		Providers: map[addrs.Provider]providers.Factory{
   581  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   582  		},
   583  	})
   584  
   585  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   586  		Mode:        plans.DestroyMode,
   587  		SkipRefresh: true,
   588  	})
   589  	assertNoErrors(t, diags)
   590  
   591  	if !p.UpgradeResourceStateCalled {
   592  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
   593  	}
   594  	if p.ReadResourceCalled {
   595  		t.Errorf("Provider's ReadResource was called; shouldn't have been")
   596  	}
   597  
   598  	if plan.PriorState == nil {
   599  		t.Fatal("missing plan state")
   600  	}
   601  
   602  	for _, c := range plan.Changes.Resources {
   603  		if c.Action != plans.Delete {
   604  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
   605  		}
   606  	}
   607  
   608  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
   609  		t.Errorf("%s has no previous run state at all after plan", addr)
   610  	} else {
   611  		if instState.Current == nil {
   612  			t.Errorf("%s has no current object in the previous run state", addr)
   613  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
   614  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   615  		}
   616  	}
   617  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
   618  		t.Errorf("%s has no prior state at all after plan", addr)
   619  	} else {
   620  		if instState.Current == nil {
   621  			t.Errorf("%s has no current object in the prior state", addr)
   622  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
   623  			// NOTE: The prior state should still have been _upgraded_, even
   624  			// though we skipped running refresh after upgrading it.
   625  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
   626  		}
   627  	}
   628  }
   629  
   630  func TestContext2Plan_unmarkingSensitiveAttributeForOutput(t *testing.T) {
   631  	m := testModuleInline(t, map[string]string{
   632  		"main.tf": `
   633  resource "test_resource" "foo" {
   634  }
   635  
   636  output "result" {
   637    value = nonsensitive(test_resource.foo.sensitive_attr)
   638  }
   639  `,
   640  	})
   641  
   642  	p := new(MockProvider)
   643  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   644  		ResourceTypes: map[string]*configschema.Block{
   645  			"test_resource": {
   646  				Attributes: map[string]*configschema.Attribute{
   647  					"id": {
   648  						Type:     cty.String,
   649  						Computed: true,
   650  					},
   651  					"sensitive_attr": {
   652  						Type:      cty.String,
   653  						Computed:  true,
   654  						Sensitive: true,
   655  					},
   656  				},
   657  			},
   658  		},
   659  	})
   660  
   661  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
   662  		return providers.PlanResourceChangeResponse{
   663  			PlannedState: cty.UnknownVal(cty.Object(map[string]cty.Type{
   664  				"id":             cty.String,
   665  				"sensitive_attr": cty.String,
   666  			})),
   667  		}
   668  	}
   669  
   670  	state := states.NewState()
   671  
   672  	ctx := testContext2(t, &ContextOpts{
   673  		Providers: map[addrs.Provider]providers.Factory{
   674  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   675  		},
   676  	})
   677  
   678  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   679  	assertNoErrors(t, diags)
   680  
   681  	for _, res := range plan.Changes.Resources {
   682  		if res.Action != plans.Create {
   683  			t.Fatalf("expected create, got: %q %s", res.Addr, res.Action)
   684  		}
   685  	}
   686  }
   687  
   688  func TestContext2Plan_destroyNoProviderConfig(t *testing.T) {
   689  	// providers do not need to be configured during a destroy plan
   690  	p := simpleMockProvider()
   691  	p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
   692  		v := req.Config.GetAttr("test_string")
   693  		if v.IsNull() || !v.IsKnown() || v.AsString() != "ok" {
   694  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid provider configuration: %#v", req.Config))
   695  		}
   696  		return resp
   697  	}
   698  
   699  	m := testModuleInline(t, map[string]string{
   700  		"main.tf": `
   701  locals {
   702    value = "ok"
   703  }
   704  
   705  provider "test" {
   706    test_string = local.value
   707  }
   708  `,
   709  	})
   710  
   711  	addr := mustResourceInstanceAddr("test_object.a")
   712  	state := states.BuildState(func(s *states.SyncState) {
   713  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   714  			AttrsJSON: []byte(`{"test_string":"foo"}`),
   715  			Status:    states.ObjectReady,
   716  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   717  	})
   718  
   719  	ctx := testContext2(t, &ContextOpts{
   720  		Providers: map[addrs.Provider]providers.Factory{
   721  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   722  		},
   723  	})
   724  
   725  	_, diags := ctx.Plan(m, state, &PlanOpts{
   726  		Mode: plans.DestroyMode,
   727  	})
   728  	assertNoErrors(t, diags)
   729  }
   730  
   731  func TestContext2Plan_movedResourceBasic(t *testing.T) {
   732  	addrA := mustResourceInstanceAddr("test_object.a")
   733  	addrB := mustResourceInstanceAddr("test_object.b")
   734  	m := testModuleInline(t, map[string]string{
   735  		"main.tf": `
   736  			resource "test_object" "b" {
   737  			}
   738  
   739  			moved {
   740  				from = test_object.a
   741  				to   = test_object.b
   742  			}
   743  		`,
   744  	})
   745  
   746  	state := states.BuildState(func(s *states.SyncState) {
   747  		// The prior state tracks test_object.a, which we should treat as
   748  		// test_object.b because of the "moved" block in the config.
   749  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
   750  			AttrsJSON: []byte(`{}`),
   751  			Status:    states.ObjectReady,
   752  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   753  	})
   754  
   755  	p := simpleMockProvider()
   756  	ctx := testContext2(t, &ContextOpts{
   757  		Providers: map[addrs.Provider]providers.Factory{
   758  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   759  		},
   760  	})
   761  
   762  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   763  		Mode: plans.NormalMode,
   764  		ForceReplace: []addrs.AbsResourceInstance{
   765  			addrA,
   766  		},
   767  	})
   768  	if diags.HasErrors() {
   769  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
   770  	}
   771  
   772  	t.Run(addrA.String(), func(t *testing.T) {
   773  		instPlan := plan.Changes.ResourceInstance(addrA)
   774  		if instPlan != nil {
   775  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
   776  		}
   777  	})
   778  	t.Run(addrB.String(), func(t *testing.T) {
   779  		instPlan := plan.Changes.ResourceInstance(addrB)
   780  		if instPlan == nil {
   781  			t.Fatalf("no plan for %s at all", addrB)
   782  		}
   783  
   784  		if got, want := instPlan.Addr, addrB; !got.Equal(want) {
   785  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
   786  		}
   787  		if got, want := instPlan.PrevRunAddr, addrA; !got.Equal(want) {
   788  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
   789  		}
   790  		if got, want := instPlan.Action, plans.NoOp; got != want {
   791  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
   792  		}
   793  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   794  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
   795  		}
   796  	})
   797  }
   798  
   799  func TestContext2Plan_movedResourceCollision(t *testing.T) {
   800  	addrNoKey := mustResourceInstanceAddr("test_object.a")
   801  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
   802  	m := testModuleInline(t, map[string]string{
   803  		"main.tf": `
   804  			resource "test_object" "a" {
   805  				# No "count" set, so test_object.a[0] will want
   806  				# to implicitly move to test_object.a, but will get
   807  				# blocked by the existing object at that address.
   808  			}
   809  		`,
   810  	})
   811  
   812  	state := states.BuildState(func(s *states.SyncState) {
   813  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
   814  			AttrsJSON: []byte(`{}`),
   815  			Status:    states.ObjectReady,
   816  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   817  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
   818  			AttrsJSON: []byte(`{}`),
   819  			Status:    states.ObjectReady,
   820  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   821  	})
   822  
   823  	p := simpleMockProvider()
   824  	ctx := testContext2(t, &ContextOpts{
   825  		Providers: map[addrs.Provider]providers.Factory{
   826  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   827  		},
   828  	})
   829  
   830  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   831  		Mode: plans.NormalMode,
   832  	})
   833  	if diags.HasErrors() {
   834  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
   835  	}
   836  
   837  	// We should have a warning, though! We'll lightly abuse the "for RPC"
   838  	// feature of diagnostics to get some more-readily-comparable diagnostic
   839  	// values.
   840  	gotDiags := diags.ForRPC()
   841  	wantDiags := tfdiags.Diagnostics{
   842  		tfdiags.Sourceless(
   843  			tfdiags.Warning,
   844  			"Unresolved resource instance address changes",
   845  			`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:
   846    - test_object.a[0] could not move to test_object.a
   847  
   848  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.`,
   849  		),
   850  	}.ForRPC()
   851  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
   852  		t.Errorf("wrong diagnostics\n%s", diff)
   853  	}
   854  
   855  	t.Run(addrNoKey.String(), func(t *testing.T) {
   856  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
   857  		if instPlan == nil {
   858  			t.Fatalf("no plan for %s at all", addrNoKey)
   859  		}
   860  
   861  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
   862  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
   863  		}
   864  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
   865  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
   866  		}
   867  		if got, want := instPlan.Action, plans.NoOp; got != want {
   868  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
   869  		}
   870  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   871  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
   872  		}
   873  	})
   874  	t.Run(addrZeroKey.String(), func(t *testing.T) {
   875  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
   876  		if instPlan == nil {
   877  			t.Fatalf("no plan for %s at all", addrZeroKey)
   878  		}
   879  
   880  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
   881  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
   882  		}
   883  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
   884  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
   885  		}
   886  		if got, want := instPlan.Action, plans.Delete; got != want {
   887  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
   888  		}
   889  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
   890  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
   891  		}
   892  	})
   893  }
   894  
   895  func TestContext2Plan_movedResourceCollisionDestroy(t *testing.T) {
   896  	// This is like TestContext2Plan_movedResourceCollision but intended to
   897  	// ensure we still produce the expected warning (and produce it only once)
   898  	// when we're creating a destroy plan, rather than a normal plan.
   899  	// (This case is interesting at the time of writing because we happen to
   900  	// use a normal plan as a trick to refresh before creating a destroy plan.
   901  	// This test will probably become uninteresting if a future change to
   902  	// the destroy-time planning behavior handles refreshing in a different
   903  	// way, which avoids this pre-processing step of running a normal plan
   904  	// first.)
   905  
   906  	addrNoKey := mustResourceInstanceAddr("test_object.a")
   907  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
   908  	m := testModuleInline(t, map[string]string{
   909  		"main.tf": `
   910  			resource "test_object" "a" {
   911  				# No "count" set, so test_object.a[0] will want
   912  				# to implicitly move to test_object.a, but will get
   913  				# blocked by the existing object at that address.
   914  			}
   915  		`,
   916  	})
   917  
   918  	state := states.BuildState(func(s *states.SyncState) {
   919  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
   920  			AttrsJSON: []byte(`{}`),
   921  			Status:    states.ObjectReady,
   922  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   923  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
   924  			AttrsJSON: []byte(`{}`),
   925  			Status:    states.ObjectReady,
   926  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   927  	})
   928  
   929  	p := simpleMockProvider()
   930  	ctx := testContext2(t, &ContextOpts{
   931  		Providers: map[addrs.Provider]providers.Factory{
   932  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   933  		},
   934  	})
   935  
   936  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   937  		Mode: plans.DestroyMode,
   938  	})
   939  	if diags.HasErrors() {
   940  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
   941  	}
   942  
   943  	// We should have a warning, though! We'll lightly abuse the "for RPC"
   944  	// feature of diagnostics to get some more-readily-comparable diagnostic
   945  	// values.
   946  	gotDiags := diags.ForRPC()
   947  	wantDiags := tfdiags.Diagnostics{
   948  		tfdiags.Sourceless(
   949  			tfdiags.Warning,
   950  			"Unresolved resource instance address changes",
   951  			// NOTE: This message is _lightly_ confusing in the destroy case,
   952  			// because it says "Terraform has planned to destroy these objects"
   953  			// but this is a plan to destroy all objects, anyway. We expect the
   954  			// conflict situation to be pretty rare though, and even rarer in
   955  			// a "terraform destroy", so we'll just live with that for now
   956  			// unless we see evidence that lots of folks are being confused by
   957  			// it in practice.
   958  			`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:
   959    - test_object.a[0] could not move to test_object.a
   960  
   961  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.`,
   962  		),
   963  	}.ForRPC()
   964  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
   965  		// If we get here with a diff that makes it seem like the above warning
   966  		// is being reported twice, the likely cause is not correctly handling
   967  		// the warnings from the hidden normal plan we run as part of preparing
   968  		// for a destroy plan, unless that strategy has changed in the meantime
   969  		// since we originally wrote this test.
   970  		t.Errorf("wrong diagnostics\n%s", diff)
   971  	}
   972  
   973  	t.Run(addrNoKey.String(), func(t *testing.T) {
   974  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
   975  		if instPlan == nil {
   976  			t.Fatalf("no plan for %s at all", addrNoKey)
   977  		}
   978  
   979  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
   980  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
   981  		}
   982  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
   983  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
   984  		}
   985  		if got, want := instPlan.Action, plans.Delete; got != want {
   986  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
   987  		}
   988  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   989  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
   990  		}
   991  	})
   992  	t.Run(addrZeroKey.String(), func(t *testing.T) {
   993  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
   994  		if instPlan == nil {
   995  			t.Fatalf("no plan for %s at all", addrZeroKey)
   996  		}
   997  
   998  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
   999  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1000  		}
  1001  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  1002  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1003  		}
  1004  		if got, want := instPlan.Action, plans.Delete; got != want {
  1005  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1006  		}
  1007  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1008  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1009  		}
  1010  	})
  1011  }
  1012  
  1013  func TestContext2Plan_movedResourceUntargeted(t *testing.T) {
  1014  	addrA := mustResourceInstanceAddr("test_object.a")
  1015  	addrB := mustResourceInstanceAddr("test_object.b")
  1016  	m := testModuleInline(t, map[string]string{
  1017  		"main.tf": `
  1018  			resource "test_object" "b" {
  1019  			}
  1020  
  1021  			moved {
  1022  				from = test_object.a
  1023  				to   = test_object.b
  1024  			}
  1025  		`,
  1026  	})
  1027  
  1028  	state := states.BuildState(func(s *states.SyncState) {
  1029  		// The prior state tracks test_object.a, which we should treat as
  1030  		// test_object.b because of the "moved" block in the config.
  1031  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1032  			AttrsJSON: []byte(`{}`),
  1033  			Status:    states.ObjectReady,
  1034  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1035  	})
  1036  
  1037  	p := simpleMockProvider()
  1038  	ctx := testContext2(t, &ContextOpts{
  1039  		Providers: map[addrs.Provider]providers.Factory{
  1040  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1041  		},
  1042  	})
  1043  
  1044  	t.Run("without targeting instance A", func(t *testing.T) {
  1045  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1046  			Mode: plans.NormalMode,
  1047  			Targets: []addrs.Targetable{
  1048  				// NOTE: addrA isn't included here, but it's pending move to addrB
  1049  				// and so this plan request is invalid.
  1050  				addrB,
  1051  			},
  1052  		})
  1053  		diags.Sort()
  1054  
  1055  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1056  		// more easily comparable than the various different diagnostics types
  1057  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1058  		// comparison-friendly, by discarding all of the dynamic type information.
  1059  		gotDiags := diags.ForRPC()
  1060  		wantDiags := tfdiags.Diagnostics{
  1061  			tfdiags.Sourceless(
  1062  				tfdiags.Warning,
  1063  				"Resource targeting is in effect",
  1064  				`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.
  1065  
  1066  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.`,
  1067  			),
  1068  			tfdiags.Sourceless(
  1069  				tfdiags.Error,
  1070  				"Moved resource instances excluded by targeting",
  1071  				`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.
  1072  
  1073  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1074    -target="test_object.a"
  1075  
  1076  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1077  			),
  1078  		}.ForRPC()
  1079  
  1080  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1081  			t.Errorf("wrong diagnostics\n%s", diff)
  1082  		}
  1083  	})
  1084  	t.Run("without targeting instance B", func(t *testing.T) {
  1085  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1086  			Mode: plans.NormalMode,
  1087  			Targets: []addrs.Targetable{
  1088  				addrA,
  1089  				// NOTE: addrB isn't included here, but it's pending move from
  1090  				// addrA and so this plan request is invalid.
  1091  			},
  1092  		})
  1093  		diags.Sort()
  1094  
  1095  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1096  		// more easily comparable than the various different diagnostics types
  1097  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1098  		// comparison-friendly, by discarding all of the dynamic type information.
  1099  		gotDiags := diags.ForRPC()
  1100  		wantDiags := tfdiags.Diagnostics{
  1101  			tfdiags.Sourceless(
  1102  				tfdiags.Warning,
  1103  				"Resource targeting is in effect",
  1104  				`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.
  1105  
  1106  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.`,
  1107  			),
  1108  			tfdiags.Sourceless(
  1109  				tfdiags.Error,
  1110  				"Moved resource instances excluded by targeting",
  1111  				`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.
  1112  
  1113  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1114    -target="test_object.b"
  1115  
  1116  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1117  			),
  1118  		}.ForRPC()
  1119  
  1120  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1121  			t.Errorf("wrong diagnostics\n%s", diff)
  1122  		}
  1123  	})
  1124  	t.Run("without targeting either instance", func(t *testing.T) {
  1125  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1126  			Mode: plans.NormalMode,
  1127  			Targets: []addrs.Targetable{
  1128  				mustResourceInstanceAddr("test_object.unrelated"),
  1129  				// NOTE: neither addrA nor addrB are included here, but there's
  1130  				// a pending move between them and so this is invalid.
  1131  			},
  1132  		})
  1133  		diags.Sort()
  1134  
  1135  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1136  		// more easily comparable than the various different diagnostics types
  1137  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1138  		// comparison-friendly, by discarding all of the dynamic type information.
  1139  		gotDiags := diags.ForRPC()
  1140  		wantDiags := tfdiags.Diagnostics{
  1141  			tfdiags.Sourceless(
  1142  				tfdiags.Warning,
  1143  				"Resource targeting is in effect",
  1144  				`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.
  1145  
  1146  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.`,
  1147  			),
  1148  			tfdiags.Sourceless(
  1149  				tfdiags.Error,
  1150  				"Moved resource instances excluded by targeting",
  1151  				`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.
  1152  
  1153  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1154    -target="test_object.a"
  1155    -target="test_object.b"
  1156  
  1157  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1158  			),
  1159  		}.ForRPC()
  1160  
  1161  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1162  			t.Errorf("wrong diagnostics\n%s", diff)
  1163  		}
  1164  	})
  1165  	t.Run("with both addresses in the target set", func(t *testing.T) {
  1166  		// The error messages in the other subtests above suggest adding
  1167  		// addresses to the set of targets. This additional test makes sure that
  1168  		// following that advice actually leads to a valid result.
  1169  
  1170  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1171  			Mode: plans.NormalMode,
  1172  			Targets: []addrs.Targetable{
  1173  				// This time we're including both addresses in the target,
  1174  				// to get the same effect an end-user would get if following
  1175  				// the advice in our error message in the other subtests.
  1176  				addrA,
  1177  				addrB,
  1178  			},
  1179  		})
  1180  		diags.Sort()
  1181  
  1182  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1183  		// more easily comparable than the various different diagnostics types
  1184  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1185  		// comparison-friendly, by discarding all of the dynamic type information.
  1186  		gotDiags := diags.ForRPC()
  1187  		wantDiags := tfdiags.Diagnostics{
  1188  			// Still get the warning about the -target option...
  1189  			tfdiags.Sourceless(
  1190  				tfdiags.Warning,
  1191  				"Resource targeting is in effect",
  1192  				`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.
  1193  
  1194  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.`,
  1195  			),
  1196  			// ...but now we have no error about test_object.a
  1197  		}.ForRPC()
  1198  
  1199  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1200  			t.Errorf("wrong diagnostics\n%s", diff)
  1201  		}
  1202  	})
  1203  }
  1204  
  1205  func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) {
  1206  	addrA := mustResourceInstanceAddr("test_object.a")
  1207  	addrB := mustResourceInstanceAddr("test_object.b")
  1208  	m := testModuleInline(t, map[string]string{
  1209  		"main.tf": `
  1210  			resource "test_object" "b" {
  1211  			}
  1212  
  1213  			moved {
  1214  				from = test_object.a
  1215  				to   = test_object.b
  1216  			}
  1217  		`,
  1218  	})
  1219  
  1220  	state := states.BuildState(func(s *states.SyncState) {
  1221  		// The prior state tracks test_object.a, which we should treat as
  1222  		// test_object.b because of the "moved" block in the config.
  1223  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1224  			AttrsJSON: []byte(`{}`),
  1225  			Status:    states.ObjectReady,
  1226  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1227  	})
  1228  
  1229  	p := simpleMockProvider()
  1230  	ctx := testContext2(t, &ContextOpts{
  1231  		Providers: map[addrs.Provider]providers.Factory{
  1232  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1233  		},
  1234  	})
  1235  
  1236  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1237  		Mode: plans.RefreshOnlyMode,
  1238  	})
  1239  	if diags.HasErrors() {
  1240  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1241  	}
  1242  
  1243  	t.Run(addrA.String(), func(t *testing.T) {
  1244  		instPlan := plan.Changes.ResourceInstance(addrA)
  1245  		if instPlan != nil {
  1246  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  1247  		}
  1248  	})
  1249  	t.Run(addrB.String(), func(t *testing.T) {
  1250  		instPlan := plan.Changes.ResourceInstance(addrB)
  1251  		if instPlan != nil {
  1252  			t.Fatalf("unexpected plan for %s", addrB)
  1253  		}
  1254  	})
  1255  	t.Run("drift", func(t *testing.T) {
  1256  		var drifted *plans.ResourceInstanceChangeSrc
  1257  		for _, dr := range plan.DriftedResources {
  1258  			if dr.Addr.Equal(addrB) {
  1259  				drifted = dr
  1260  				break
  1261  			}
  1262  		}
  1263  
  1264  		if drifted == nil {
  1265  			t.Fatalf("instance %s is missing from the drifted resource changes", addrB)
  1266  		}
  1267  
  1268  		if got, want := drifted.PrevRunAddr, addrA; !got.Equal(want) {
  1269  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1270  		}
  1271  		if got, want := drifted.Action, plans.NoOp; got != want {
  1272  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1273  		}
  1274  	})
  1275  }
  1276  
  1277  func TestContext2Plan_refreshOnlyMode(t *testing.T) {
  1278  	addr := mustResourceInstanceAddr("test_object.a")
  1279  
  1280  	// The configuration, the prior state, and the refresh result intentionally
  1281  	// have different values for "test_string" so we can observe that the
  1282  	// refresh took effect but the configuration change wasn't considered.
  1283  	m := testModuleInline(t, map[string]string{
  1284  		"main.tf": `
  1285  			resource "test_object" "a" {
  1286  				arg = "after"
  1287  			}
  1288  
  1289  			output "out" {
  1290  				value = test_object.a.arg
  1291  			}
  1292  		`,
  1293  	})
  1294  	state := states.BuildState(func(s *states.SyncState) {
  1295  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1296  			AttrsJSON: []byte(`{"arg":"before"}`),
  1297  			Status:    states.ObjectReady,
  1298  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1299  	})
  1300  
  1301  	p := simpleMockProvider()
  1302  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1303  		Provider: providers.Schema{Block: simpleTestSchema()},
  1304  		ResourceTypes: map[string]providers.Schema{
  1305  			"test_object": {
  1306  				Block: &configschema.Block{
  1307  					Attributes: map[string]*configschema.Attribute{
  1308  						"arg": {Type: cty.String, Optional: true},
  1309  					},
  1310  				},
  1311  			},
  1312  		},
  1313  	}
  1314  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1315  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1316  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1317  				return cty.StringVal("current"), nil
  1318  			}
  1319  			return v, nil
  1320  		})
  1321  		if err != nil {
  1322  			// shouldn't get here
  1323  			t.Fatalf("ReadResourceFn transform failed")
  1324  			return providers.ReadResourceResponse{}
  1325  		}
  1326  		return providers.ReadResourceResponse{
  1327  			NewState: newVal,
  1328  		}
  1329  	}
  1330  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1331  		// We should've been given the prior state JSON as our input to upgrade.
  1332  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1333  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1334  		}
  1335  
  1336  		// We'll put something different in "arg" as part of upgrading, just
  1337  		// so that we can verify below that PrevRunState contains the upgraded
  1338  		// (but NOT refreshed) version of the object.
  1339  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1340  			"arg": cty.StringVal("upgraded"),
  1341  		})
  1342  		return resp
  1343  	}
  1344  
  1345  	ctx := testContext2(t, &ContextOpts{
  1346  		Providers: map[addrs.Provider]providers.Factory{
  1347  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1348  		},
  1349  	})
  1350  
  1351  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1352  		Mode: plans.RefreshOnlyMode,
  1353  	})
  1354  	if diags.HasErrors() {
  1355  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1356  	}
  1357  
  1358  	if !p.UpgradeResourceStateCalled {
  1359  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1360  	}
  1361  	if !p.ReadResourceCalled {
  1362  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1363  	}
  1364  
  1365  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1366  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1367  	}
  1368  
  1369  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1370  		t.Errorf("%s has no prior state at all after plan", addr)
  1371  	} else {
  1372  		if instState.Current == nil {
  1373  			t.Errorf("%s has no current object after plan", addr)
  1374  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1375  			// Should've saved the result of refreshing
  1376  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1377  		}
  1378  	}
  1379  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1380  		t.Errorf("%s has no previous run state at all after plan", addr)
  1381  	} else {
  1382  		if instState.Current == nil {
  1383  			t.Errorf("%s has no current object in the previous run state", addr)
  1384  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1385  			// Should've saved the result of upgrading
  1386  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1387  		}
  1388  	}
  1389  
  1390  	// The output value should also have updated. If not, it's likely that we
  1391  	// skipped updating the working state to match the refreshed state when we
  1392  	// were evaluating the resource.
  1393  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1394  		t.Errorf("no change planned for output value 'out'")
  1395  	} else {
  1396  		outChange, err := outChangeSrc.Decode()
  1397  		if err != nil {
  1398  			t.Fatalf("failed to decode output value 'out': %s", err)
  1399  		}
  1400  		got := outChange.After
  1401  		want := cty.StringVal("current")
  1402  		if !want.RawEquals(got) {
  1403  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1404  		}
  1405  	}
  1406  }
  1407  
  1408  func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) {
  1409  	addr := mustResourceInstanceAddr("test_object.a")
  1410  	deposedKey := states.DeposedKey("byebye")
  1411  
  1412  	// The configuration, the prior state, and the refresh result intentionally
  1413  	// have different values for "test_string" so we can observe that the
  1414  	// refresh took effect but the configuration change wasn't considered.
  1415  	m := testModuleInline(t, map[string]string{
  1416  		"main.tf": `
  1417  			resource "test_object" "a" {
  1418  				arg = "after"
  1419  			}
  1420  
  1421  			output "out" {
  1422  				value = test_object.a.arg
  1423  			}
  1424  		`,
  1425  	})
  1426  	state := states.BuildState(func(s *states.SyncState) {
  1427  		// Note that we're intentionally recording a _deposed_ object here,
  1428  		// and not including a current object, so a normal (non-refresh)
  1429  		// plan would normally plan to create a new object _and_ destroy
  1430  		// the deposed one, but refresh-only mode should prevent that.
  1431  		s.SetResourceInstanceDeposed(addr, deposedKey, &states.ResourceInstanceObjectSrc{
  1432  			AttrsJSON: []byte(`{"arg":"before"}`),
  1433  			Status:    states.ObjectReady,
  1434  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1435  	})
  1436  
  1437  	p := simpleMockProvider()
  1438  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1439  		Provider: providers.Schema{Block: simpleTestSchema()},
  1440  		ResourceTypes: map[string]providers.Schema{
  1441  			"test_object": {
  1442  				Block: &configschema.Block{
  1443  					Attributes: map[string]*configschema.Attribute{
  1444  						"arg": {Type: cty.String, Optional: true},
  1445  					},
  1446  				},
  1447  			},
  1448  		},
  1449  	}
  1450  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1451  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1452  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1453  				return cty.StringVal("current"), nil
  1454  			}
  1455  			return v, nil
  1456  		})
  1457  		if err != nil {
  1458  			// shouldn't get here
  1459  			t.Fatalf("ReadResourceFn transform failed")
  1460  			return providers.ReadResourceResponse{}
  1461  		}
  1462  		return providers.ReadResourceResponse{
  1463  			NewState: newVal,
  1464  		}
  1465  	}
  1466  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1467  		// We should've been given the prior state JSON as our input to upgrade.
  1468  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1469  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1470  		}
  1471  
  1472  		// We'll put something different in "arg" as part of upgrading, just
  1473  		// so that we can verify below that PrevRunState contains the upgraded
  1474  		// (but NOT refreshed) version of the object.
  1475  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1476  			"arg": cty.StringVal("upgraded"),
  1477  		})
  1478  		return resp
  1479  	}
  1480  
  1481  	ctx := testContext2(t, &ContextOpts{
  1482  		Providers: map[addrs.Provider]providers.Factory{
  1483  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1484  		},
  1485  	})
  1486  
  1487  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1488  		Mode: plans.RefreshOnlyMode,
  1489  	})
  1490  	if diags.HasErrors() {
  1491  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1492  	}
  1493  
  1494  	if !p.UpgradeResourceStateCalled {
  1495  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1496  	}
  1497  	if !p.ReadResourceCalled {
  1498  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1499  	}
  1500  
  1501  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1502  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1503  	}
  1504  
  1505  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1506  		t.Errorf("%s has no prior state at all after plan", addr)
  1507  	} else {
  1508  		if obj := instState.Deposed[deposedKey]; obj == nil {
  1509  			t.Errorf("%s has no deposed object after plan", addr)
  1510  		} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1511  			// Should've saved the result of refreshing
  1512  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1513  		}
  1514  	}
  1515  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1516  		t.Errorf("%s has no previous run state at all after plan", addr)
  1517  	} else {
  1518  		if obj := instState.Deposed[deposedKey]; obj == nil {
  1519  			t.Errorf("%s has no deposed object in the previous run state", addr)
  1520  		} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1521  			// Should've saved the result of upgrading
  1522  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1523  		}
  1524  	}
  1525  
  1526  	// The output value should also have updated. If not, it's likely that we
  1527  	// skipped updating the working state to match the refreshed state when we
  1528  	// were evaluating the resource.
  1529  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1530  		t.Errorf("no change planned for output value 'out'")
  1531  	} else {
  1532  		outChange, err := outChangeSrc.Decode()
  1533  		if err != nil {
  1534  			t.Fatalf("failed to decode output value 'out': %s", err)
  1535  		}
  1536  		got := outChange.After
  1537  		want := cty.UnknownVal(cty.String)
  1538  		if !want.RawEquals(got) {
  1539  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1540  		}
  1541  	}
  1542  
  1543  	// Deposed objects should not be represented in drift.
  1544  	if len(plan.DriftedResources) > 0 {
  1545  		t.Errorf("unexpected drifted resources (%d)", len(plan.DriftedResources))
  1546  	}
  1547  }
  1548  
  1549  func TestContext2Plan_refreshOnlyMode_orphan(t *testing.T) {
  1550  	addr := mustAbsResourceAddr("test_object.a")
  1551  
  1552  	// The configuration, the prior state, and the refresh result intentionally
  1553  	// have different values for "test_string" so we can observe that the
  1554  	// refresh took effect but the configuration change wasn't considered.
  1555  	m := testModuleInline(t, map[string]string{
  1556  		"main.tf": `
  1557  			resource "test_object" "a" {
  1558  				arg = "after"
  1559  				count = 1
  1560  			}
  1561  
  1562  			output "out" {
  1563  				value = test_object.a.*.arg
  1564  			}
  1565  		`,
  1566  	})
  1567  	state := states.BuildState(func(s *states.SyncState) {
  1568  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{
  1569  			AttrsJSON: []byte(`{"arg":"before"}`),
  1570  			Status:    states.ObjectReady,
  1571  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1572  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(1)), &states.ResourceInstanceObjectSrc{
  1573  			AttrsJSON: []byte(`{"arg":"before"}`),
  1574  			Status:    states.ObjectReady,
  1575  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1576  	})
  1577  
  1578  	p := simpleMockProvider()
  1579  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1580  		Provider: providers.Schema{Block: simpleTestSchema()},
  1581  		ResourceTypes: map[string]providers.Schema{
  1582  			"test_object": {
  1583  				Block: &configschema.Block{
  1584  					Attributes: map[string]*configschema.Attribute{
  1585  						"arg": {Type: cty.String, Optional: true},
  1586  					},
  1587  				},
  1588  			},
  1589  		},
  1590  	}
  1591  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1592  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1593  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1594  				return cty.StringVal("current"), nil
  1595  			}
  1596  			return v, nil
  1597  		})
  1598  		if err != nil {
  1599  			// shouldn't get here
  1600  			t.Fatalf("ReadResourceFn transform failed")
  1601  			return providers.ReadResourceResponse{}
  1602  		}
  1603  		return providers.ReadResourceResponse{
  1604  			NewState: newVal,
  1605  		}
  1606  	}
  1607  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1608  		// We should've been given the prior state JSON as our input to upgrade.
  1609  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1610  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1611  		}
  1612  
  1613  		// We'll put something different in "arg" as part of upgrading, just
  1614  		// so that we can verify below that PrevRunState contains the upgraded
  1615  		// (but NOT refreshed) version of the object.
  1616  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1617  			"arg": cty.StringVal("upgraded"),
  1618  		})
  1619  		return resp
  1620  	}
  1621  
  1622  	ctx := testContext2(t, &ContextOpts{
  1623  		Providers: map[addrs.Provider]providers.Factory{
  1624  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1625  		},
  1626  	})
  1627  
  1628  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1629  		Mode: plans.RefreshOnlyMode,
  1630  	})
  1631  	if diags.HasErrors() {
  1632  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1633  	}
  1634  
  1635  	if !p.UpgradeResourceStateCalled {
  1636  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1637  	}
  1638  	if !p.ReadResourceCalled {
  1639  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1640  	}
  1641  
  1642  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1643  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1644  	}
  1645  
  1646  	if rState := plan.PriorState.Resource(addr); rState == nil {
  1647  		t.Errorf("%s has no prior state at all after plan", addr)
  1648  	} else {
  1649  		for i := 0; i < 2; i++ {
  1650  			instKey := addrs.IntKey(i)
  1651  			if obj := rState.Instance(instKey).Current; obj == nil {
  1652  				t.Errorf("%s%s has no object after plan", addr, instKey)
  1653  			} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1654  				// Should've saved the result of refreshing
  1655  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  1656  			}
  1657  		}
  1658  	}
  1659  	if rState := plan.PrevRunState.Resource(addr); rState == nil {
  1660  		t.Errorf("%s has no prior state at all after plan", addr)
  1661  	} else {
  1662  		for i := 0; i < 2; i++ {
  1663  			instKey := addrs.IntKey(i)
  1664  			if obj := rState.Instance(instKey).Current; obj == nil {
  1665  				t.Errorf("%s%s has no object after plan", addr, instKey)
  1666  			} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1667  				// Should've saved the result of upgrading
  1668  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  1669  			}
  1670  		}
  1671  	}
  1672  
  1673  	// The output value should also have updated. If not, it's likely that we
  1674  	// skipped updating the working state to match the refreshed state when we
  1675  	// were evaluating the resource.
  1676  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1677  		t.Errorf("no change planned for output value 'out'")
  1678  	} else {
  1679  		outChange, err := outChangeSrc.Decode()
  1680  		if err != nil {
  1681  			t.Fatalf("failed to decode output value 'out': %s", err)
  1682  		}
  1683  		got := outChange.After
  1684  		want := cty.TupleVal([]cty.Value{cty.StringVal("current"), cty.StringVal("current")})
  1685  		if !want.RawEquals(got) {
  1686  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1687  		}
  1688  	}
  1689  }
  1690  
  1691  func TestContext2Plan_invalidSensitiveModuleOutput(t *testing.T) {
  1692  	m := testModuleInline(t, map[string]string{
  1693  		"child/main.tf": `
  1694  output "out" {
  1695    value = sensitive("xyz")
  1696  }`,
  1697  		"main.tf": `
  1698  module "child" {
  1699    source = "./child"
  1700  }
  1701  
  1702  output "root" {
  1703    value = module.child.out
  1704  }`,
  1705  	})
  1706  
  1707  	ctx := testContext2(t, &ContextOpts{})
  1708  
  1709  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  1710  	if !diags.HasErrors() {
  1711  		t.Fatal("succeeded; want errors")
  1712  	}
  1713  	if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
  1714  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  1715  	}
  1716  }
  1717  
  1718  func TestContext2Plan_planDataSourceSensitiveNested(t *testing.T) {
  1719  	m := testModuleInline(t, map[string]string{
  1720  		"main.tf": `
  1721  resource "test_instance" "bar" {
  1722  }
  1723  
  1724  data "test_data_source" "foo" {
  1725    foo {
  1726      bar = test_instance.bar.sensitive
  1727    }
  1728  }
  1729  `,
  1730  	})
  1731  
  1732  	p := new(MockProvider)
  1733  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1734  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
  1735  			"sensitive": cty.UnknownVal(cty.String),
  1736  		})
  1737  		return resp
  1738  	}
  1739  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1740  		ResourceTypes: map[string]*configschema.Block{
  1741  			"test_instance": {
  1742  				Attributes: map[string]*configschema.Attribute{
  1743  					"sensitive": {
  1744  						Type:      cty.String,
  1745  						Computed:  true,
  1746  						Sensitive: true,
  1747  					},
  1748  				},
  1749  			},
  1750  		},
  1751  		DataSources: map[string]*configschema.Block{
  1752  			"test_data_source": {
  1753  				Attributes: map[string]*configschema.Attribute{
  1754  					"id": {
  1755  						Type:     cty.String,
  1756  						Computed: true,
  1757  					},
  1758  				},
  1759  				BlockTypes: map[string]*configschema.NestedBlock{
  1760  					"foo": {
  1761  						Block: configschema.Block{
  1762  							Attributes: map[string]*configschema.Attribute{
  1763  								"bar": {Type: cty.String, Optional: true},
  1764  							},
  1765  						},
  1766  						Nesting: configschema.NestingSet,
  1767  					},
  1768  				},
  1769  			},
  1770  		},
  1771  	})
  1772  
  1773  	state := states.NewState()
  1774  	root := state.EnsureModule(addrs.RootModuleInstance)
  1775  	root.SetResourceInstanceCurrent(
  1776  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
  1777  		&states.ResourceInstanceObjectSrc{
  1778  			Status:    states.ObjectReady,
  1779  			AttrsJSON: []byte(`{"string":"data_id", "foo":[{"bar":"old"}]}`),
  1780  			AttrSensitivePaths: []cty.PathValueMarks{
  1781  				{
  1782  					Path:  cty.GetAttrPath("foo"),
  1783  					Marks: cty.NewValueMarks(marks.Sensitive),
  1784  				},
  1785  			},
  1786  		},
  1787  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  1788  	)
  1789  	root.SetResourceInstanceCurrent(
  1790  		mustResourceInstanceAddr("test_instance.bar").Resource,
  1791  		&states.ResourceInstanceObjectSrc{
  1792  			Status:    states.ObjectReady,
  1793  			AttrsJSON: []byte(`{"sensitive":"old"}`),
  1794  			AttrSensitivePaths: []cty.PathValueMarks{
  1795  				{
  1796  					Path:  cty.GetAttrPath("sensitive"),
  1797  					Marks: cty.NewValueMarks(marks.Sensitive),
  1798  				},
  1799  			},
  1800  		},
  1801  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  1802  	)
  1803  
  1804  	ctx := testContext2(t, &ContextOpts{
  1805  		Providers: map[addrs.Provider]providers.Factory{
  1806  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1807  		},
  1808  	})
  1809  
  1810  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  1811  	assertNoErrors(t, diags)
  1812  
  1813  	for _, res := range plan.Changes.Resources {
  1814  		switch res.Addr.String() {
  1815  		case "test_instance.bar":
  1816  			if res.Action != plans.Update {
  1817  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  1818  			}
  1819  		case "data.test_data_source.foo":
  1820  			if res.Action != plans.Read {
  1821  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  1822  			}
  1823  		default:
  1824  			t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  1825  		}
  1826  	}
  1827  }
  1828  
  1829  func TestContext2Plan_forceReplace(t *testing.T) {
  1830  	addrA := mustResourceInstanceAddr("test_object.a")
  1831  	addrB := mustResourceInstanceAddr("test_object.b")
  1832  	m := testModuleInline(t, map[string]string{
  1833  		"main.tf": `
  1834  			resource "test_object" "a" {
  1835  			}
  1836  			resource "test_object" "b" {
  1837  			}
  1838  		`,
  1839  	})
  1840  
  1841  	state := states.BuildState(func(s *states.SyncState) {
  1842  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1843  			AttrsJSON: []byte(`{}`),
  1844  			Status:    states.ObjectReady,
  1845  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1846  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
  1847  			AttrsJSON: []byte(`{}`),
  1848  			Status:    states.ObjectReady,
  1849  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1850  	})
  1851  
  1852  	p := simpleMockProvider()
  1853  	ctx := testContext2(t, &ContextOpts{
  1854  		Providers: map[addrs.Provider]providers.Factory{
  1855  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1856  		},
  1857  	})
  1858  
  1859  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1860  		Mode: plans.NormalMode,
  1861  		ForceReplace: []addrs.AbsResourceInstance{
  1862  			addrA,
  1863  		},
  1864  	})
  1865  	if diags.HasErrors() {
  1866  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1867  	}
  1868  
  1869  	t.Run(addrA.String(), func(t *testing.T) {
  1870  		instPlan := plan.Changes.ResourceInstance(addrA)
  1871  		if instPlan == nil {
  1872  			t.Fatalf("no plan for %s at all", addrA)
  1873  		}
  1874  
  1875  		if got, want := instPlan.Action, plans.DeleteThenCreate; got != want {
  1876  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1877  		}
  1878  		if got, want := instPlan.ActionReason, plans.ResourceInstanceReplaceByRequest; got != want {
  1879  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1880  		}
  1881  	})
  1882  	t.Run(addrB.String(), func(t *testing.T) {
  1883  		instPlan := plan.Changes.ResourceInstance(addrB)
  1884  		if instPlan == nil {
  1885  			t.Fatalf("no plan for %s at all", addrB)
  1886  		}
  1887  
  1888  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1889  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1890  		}
  1891  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1892  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1893  		}
  1894  	})
  1895  }
  1896  
  1897  func TestContext2Plan_forceReplaceIncompleteAddr(t *testing.T) {
  1898  	addr0 := mustResourceInstanceAddr("test_object.a[0]")
  1899  	addr1 := mustResourceInstanceAddr("test_object.a[1]")
  1900  	addrBare := mustResourceInstanceAddr("test_object.a")
  1901  	m := testModuleInline(t, map[string]string{
  1902  		"main.tf": `
  1903  			resource "test_object" "a" {
  1904  				count = 2
  1905  			}
  1906  		`,
  1907  	})
  1908  
  1909  	state := states.BuildState(func(s *states.SyncState) {
  1910  		s.SetResourceInstanceCurrent(addr0, &states.ResourceInstanceObjectSrc{
  1911  			AttrsJSON: []byte(`{}`),
  1912  			Status:    states.ObjectReady,
  1913  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1914  		s.SetResourceInstanceCurrent(addr1, &states.ResourceInstanceObjectSrc{
  1915  			AttrsJSON: []byte(`{}`),
  1916  			Status:    states.ObjectReady,
  1917  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
  1918  	})
  1919  
  1920  	p := simpleMockProvider()
  1921  	ctx := testContext2(t, &ContextOpts{
  1922  		Providers: map[addrs.Provider]providers.Factory{
  1923  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1924  		},
  1925  	})
  1926  
  1927  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1928  		Mode: plans.NormalMode,
  1929  		ForceReplace: []addrs.AbsResourceInstance{
  1930  			addrBare,
  1931  		},
  1932  	})
  1933  	if diags.HasErrors() {
  1934  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1935  	}
  1936  	diagsErr := diags.ErrWithWarnings()
  1937  	if diagsErr == nil {
  1938  		t.Fatalf("no warnings were returned")
  1939  	}
  1940  	if got, want := diagsErr.Error(), "Incompletely-matched force-replace resource instance"; !strings.Contains(got, want) {
  1941  		t.Errorf("missing expected warning\ngot:\n%s\n\nwant substring: %s", got, want)
  1942  	}
  1943  
  1944  	t.Run(addr0.String(), func(t *testing.T) {
  1945  		instPlan := plan.Changes.ResourceInstance(addr0)
  1946  		if instPlan == nil {
  1947  			t.Fatalf("no plan for %s at all", addr0)
  1948  		}
  1949  
  1950  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1951  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1952  		}
  1953  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1954  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1955  		}
  1956  	})
  1957  	t.Run(addr1.String(), func(t *testing.T) {
  1958  		instPlan := plan.Changes.ResourceInstance(addr1)
  1959  		if instPlan == nil {
  1960  			t.Fatalf("no plan for %s at all", addr1)
  1961  		}
  1962  
  1963  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1964  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1965  		}
  1966  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1967  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1968  		}
  1969  	})
  1970  }
  1971  
  1972  // Verify that adding a module instance does force existing module data sources
  1973  // to be deferred
  1974  func TestContext2Plan_noChangeDataSourceAddingModuleInstance(t *testing.T) {
  1975  	m := testModuleInline(t, map[string]string{
  1976  		"main.tf": `
  1977  locals {
  1978    data = {
  1979      a = "a"
  1980      b = "b"
  1981    }
  1982  }
  1983  
  1984  module "one" {
  1985    source   = "./mod"
  1986    for_each = local.data
  1987    input = each.value
  1988  }
  1989  
  1990  module "two" {
  1991    source   = "./mod"
  1992    for_each = module.one
  1993    input = each.value.output
  1994  }
  1995  `,
  1996  		"mod/main.tf": `
  1997  variable "input" {
  1998  }
  1999  
  2000  resource "test_resource" "x" {
  2001    value = var.input
  2002  }
  2003  
  2004  data "test_data_source" "d" {
  2005    foo = test_resource.x.id
  2006  }
  2007  
  2008  output "output" {
  2009    value = test_resource.x.id
  2010  }
  2011  `,
  2012  	})
  2013  
  2014  	p := testProvider("test")
  2015  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  2016  		State: cty.ObjectVal(map[string]cty.Value{
  2017  			"id":  cty.StringVal("data"),
  2018  			"foo": cty.StringVal("foo"),
  2019  		}),
  2020  	}
  2021  	state := states.NewState()
  2022  	modOne := addrs.RootModuleInstance.Child("one", addrs.StringKey("a"))
  2023  	modTwo := addrs.RootModuleInstance.Child("two", addrs.StringKey("a"))
  2024  	one := state.EnsureModule(modOne)
  2025  	two := state.EnsureModule(modTwo)
  2026  	one.SetResourceInstanceCurrent(
  2027  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2028  		&states.ResourceInstanceObjectSrc{
  2029  			Status:    states.ObjectReady,
  2030  			AttrsJSON: []byte(`{"id":"foo","value":"a"}`),
  2031  		},
  2032  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2033  	)
  2034  	one.SetResourceInstanceCurrent(
  2035  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2036  		&states.ResourceInstanceObjectSrc{
  2037  			Status:    states.ObjectReady,
  2038  			AttrsJSON: []byte(`{"id":"data"}`),
  2039  		},
  2040  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2041  	)
  2042  	two.SetResourceInstanceCurrent(
  2043  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2044  		&states.ResourceInstanceObjectSrc{
  2045  			Status:    states.ObjectReady,
  2046  			AttrsJSON: []byte(`{"id":"foo","value":"foo"}`),
  2047  		},
  2048  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2049  	)
  2050  	two.SetResourceInstanceCurrent(
  2051  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2052  		&states.ResourceInstanceObjectSrc{
  2053  			Status:    states.ObjectReady,
  2054  			AttrsJSON: []byte(`{"id":"data"}`),
  2055  		},
  2056  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
  2057  	)
  2058  
  2059  	ctx := testContext2(t, &ContextOpts{
  2060  		Providers: map[addrs.Provider]providers.Factory{
  2061  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2062  		},
  2063  	})
  2064  
  2065  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2066  	assertNoErrors(t, diags)
  2067  
  2068  	for _, res := range plan.Changes.Resources {
  2069  		// both existing data sources should be read during plan
  2070  		if res.Addr.Module[0].InstanceKey == addrs.StringKey("b") {
  2071  			continue
  2072  		}
  2073  
  2074  		if res.Addr.Resource.Resource.Mode == addrs.DataResourceMode && res.Action != plans.NoOp {
  2075  			t.Errorf("unexpected %s plan for %s", res.Action, res.Addr)
  2076  		}
  2077  	}
  2078  }