github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_plan2_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"bytes"
    10  	"errors"
    11  	"fmt"
    12  	"strconv"
    13  	"strings"
    14  	"sync"
    15  	"testing"
    16  
    17  	"github.com/davecgh/go-spew/spew"
    18  	"github.com/google/go-cmp/cmp"
    19  	"github.com/zclconf/go-cty/cty"
    20  
    21  	// "github.com/hashicorp/hcl/v2"
    22  	"github.com/opentofu/opentofu/internal/addrs"
    23  	"github.com/opentofu/opentofu/internal/checks"
    24  
    25  	// "github.com/opentofu/opentofu/internal/configs"
    26  	"github.com/opentofu/opentofu/internal/configs/configschema"
    27  	"github.com/opentofu/opentofu/internal/lang/marks"
    28  	"github.com/opentofu/opentofu/internal/plans"
    29  	"github.com/opentofu/opentofu/internal/providers"
    30  	"github.com/opentofu/opentofu/internal/states"
    31  	"github.com/opentofu/opentofu/internal/tfdiags"
    32  )
    33  
    34  func TestContext2Plan_removedDuringRefresh(t *testing.T) {
    35  	// This tests the situation where an object tracked in the previous run
    36  	// state has been deleted outside OpenTofu, which we should detect
    37  	// during the refresh step and thus ultimately produce a plan to recreate
    38  	// the object, since it's still present in the configuration.
    39  	m := testModuleInline(t, map[string]string{
    40  		"main.tf": `
    41  resource "test_object" "a" {
    42  }
    43  `,
    44  	})
    45  
    46  	p := simpleMockProvider()
    47  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
    48  		Provider: providers.Schema{Block: simpleTestSchema()},
    49  		ResourceTypes: map[string]providers.Schema{
    50  			"test_object": {
    51  				Block: &configschema.Block{
    52  					Attributes: map[string]*configschema.Attribute{
    53  						"arg": {Type: cty.String, Optional: true},
    54  					},
    55  				},
    56  			},
    57  		},
    58  	}
    59  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
    60  		resp.NewState = cty.NullVal(req.PriorState.Type())
    61  		return resp
    62  	}
    63  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
    64  		// We should've been given the prior state JSON as our input to upgrade.
    65  		if !bytes.Contains(req.RawStateJSON, []byte("previous_run")) {
    66  			t.Fatalf("UpgradeResourceState request doesn't contain the previous run object\n%s", req.RawStateJSON)
    67  		}
    68  
    69  		// We'll put something different in "arg" as part of upgrading, just
    70  		// so that we can verify below that PrevRunState contains the upgraded
    71  		// (but NOT refreshed) version of the object.
    72  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
    73  			"arg": cty.StringVal("upgraded"),
    74  		})
    75  		return resp
    76  	}
    77  
    78  	addr := mustResourceInstanceAddr("test_object.a")
    79  	state := states.BuildState(func(s *states.SyncState) {
    80  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
    81  			AttrsJSON: []byte(`{"arg":"previous_run"}`),
    82  			Status:    states.ObjectTainted,
    83  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
    84  	})
    85  
    86  	ctx := testContext2(t, &ContextOpts{
    87  		Providers: map[addrs.Provider]providers.Factory{
    88  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
    89  		},
    90  	})
    91  
    92  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    93  	assertNoErrors(t, diags)
    94  
    95  	if !p.UpgradeResourceStateCalled {
    96  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
    97  	}
    98  	if !p.ReadResourceCalled {
    99  		t.Errorf("Provider's ReadResource wasn't called; should've been")
   100  	}
   101  
   102  	// The object should be absent from the plan's prior state, because that
   103  	// records the result of refreshing.
   104  	if got := plan.PriorState.ResourceInstance(addr); got != nil {
   105  		t.Errorf(
   106  			"instance %s is in the prior state after planning; should've been removed\n%s",
   107  			addr, spew.Sdump(got),
   108  		)
   109  	}
   110  
   111  	// However, the object should still be in the PrevRunState, because
   112  	// that reflects what we believed to exist before refreshing.
   113  	if got := plan.PrevRunState.ResourceInstance(addr); got == nil {
   114  		t.Errorf(
   115  			"instance %s is missing from the previous run state after planning; should've been preserved",
   116  			addr,
   117  		)
   118  	} else {
   119  		if !bytes.Contains(got.Current.AttrsJSON, []byte("upgraded")) {
   120  			t.Fatalf("previous run state has non-upgraded object\n%s", got.Current.AttrsJSON)
   121  		}
   122  	}
   123  
   124  	// This situation should result in a drifted resource change.
   125  	var drifted *plans.ResourceInstanceChangeSrc
   126  	for _, dr := range plan.DriftedResources {
   127  		if dr.Addr.Equal(addr) {
   128  			drifted = dr
   129  			break
   130  		}
   131  	}
   132  
   133  	if drifted == nil {
   134  		t.Errorf("instance %s is missing from the drifted resource changes", addr)
   135  	} else {
   136  		if got, want := drifted.Action, plans.Delete; got != want {
   137  			t.Errorf("unexpected instance %s drifted resource change action. got: %s, want: %s", addr, got, want)
   138  		}
   139  	}
   140  
   141  	// Because the configuration still mentions test_object.a, we should've
   142  	// planned to recreate it in order to fix the drift.
   143  	for _, c := range plan.Changes.Resources {
   144  		if c.Action != plans.Create {
   145  			t.Fatalf("expected Create action for missing %s, got %s", c.Addr, c.Action)
   146  		}
   147  	}
   148  }
   149  
   150  func TestContext2Plan_noChangeDataSourceSensitiveNestedSet(t *testing.T) {
   151  	m := testModuleInline(t, map[string]string{
   152  		"main.tf": `
   153  variable "bar" {
   154    sensitive = true
   155    default   = "baz"
   156  }
   157  
   158  data "test_data_source" "foo" {
   159    foo {
   160      bar = var.bar
   161    }
   162  }
   163  `,
   164  	})
   165  
   166  	p := new(MockProvider)
   167  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   168  		DataSources: map[string]*configschema.Block{
   169  			"test_data_source": {
   170  				Attributes: map[string]*configschema.Attribute{
   171  					"id": {
   172  						Type:     cty.String,
   173  						Computed: true,
   174  					},
   175  				},
   176  				BlockTypes: map[string]*configschema.NestedBlock{
   177  					"foo": {
   178  						Block: configschema.Block{
   179  							Attributes: map[string]*configschema.Attribute{
   180  								"bar": {Type: cty.String, Optional: true},
   181  							},
   182  						},
   183  						Nesting: configschema.NestingSet,
   184  					},
   185  				},
   186  			},
   187  		},
   188  	})
   189  
   190  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
   191  		State: cty.ObjectVal(map[string]cty.Value{
   192  			"id":  cty.StringVal("data_id"),
   193  			"foo": cty.SetVal([]cty.Value{cty.ObjectVal(map[string]cty.Value{"bar": cty.StringVal("baz")})}),
   194  		}),
   195  	}
   196  
   197  	state := states.NewState()
   198  	root := state.EnsureModule(addrs.RootModuleInstance)
   199  	root.SetResourceInstanceCurrent(
   200  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
   201  		&states.ResourceInstanceObjectSrc{
   202  			Status:    states.ObjectReady,
   203  			AttrsJSON: []byte(`{"id":"data_id", "foo":[{"bar":"baz"}]}`),
   204  			AttrSensitivePaths: []cty.PathValueMarks{
   205  				{
   206  					Path:  cty.GetAttrPath("foo"),
   207  					Marks: cty.NewValueMarks(marks.Sensitive),
   208  				},
   209  			},
   210  		},
   211  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   212  	)
   213  
   214  	ctx := testContext2(t, &ContextOpts{
   215  		Providers: map[addrs.Provider]providers.Factory{
   216  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   217  		},
   218  	})
   219  
   220  	plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   221  	assertNoErrors(t, diags)
   222  
   223  	for _, res := range plan.Changes.Resources {
   224  		if res.Action != plans.NoOp {
   225  			t.Fatalf("expected NoOp, got: %q %s", res.Addr, res.Action)
   226  		}
   227  	}
   228  }
   229  
   230  func TestContext2Plan_orphanDataInstance(t *testing.T) {
   231  	// ensure the planned replacement of the data source is evaluated properly
   232  	m := testModuleInline(t, map[string]string{
   233  		"main.tf": `
   234  data "test_object" "a" {
   235    for_each = { new = "ok" }
   236  }
   237  
   238  output "out" {
   239    value = [ for k, _ in data.test_object.a: k ]
   240  }
   241  `,
   242  	})
   243  
   244  	p := simpleMockProvider()
   245  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   246  		resp.State = req.Config
   247  		return resp
   248  	}
   249  
   250  	state := states.BuildState(func(s *states.SyncState) {
   251  		s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a["old"]`), &states.ResourceInstanceObjectSrc{
   252  			AttrsJSON: []byte(`{"test_string":"foo"}`),
   253  			Status:    states.ObjectReady,
   254  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
   255  	})
   256  
   257  	ctx := testContext2(t, &ContextOpts{
   258  		Providers: map[addrs.Provider]providers.Factory{
   259  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   260  		},
   261  	})
   262  
   263  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   264  	assertNoErrors(t, diags)
   265  
   266  	change, err := plan.Changes.Outputs[0].Decode()
   267  	if err != nil {
   268  		t.Fatal(err)
   269  	}
   270  
   271  	expected := cty.TupleVal([]cty.Value{cty.StringVal("new")})
   272  
   273  	if change.After.Equals(expected).False() {
   274  		t.Fatalf("expected %#v, got %#v\n", expected, change.After)
   275  	}
   276  }
   277  
   278  func TestContext2Plan_basicConfigurationAliases(t *testing.T) {
   279  	m := testModuleInline(t, map[string]string{
   280  		"main.tf": `
   281  provider "test" {
   282    alias = "z"
   283    test_string = "config"
   284  }
   285  
   286  module "mod" {
   287    source = "./mod"
   288    providers = {
   289      test.x = test.z
   290    }
   291  }
   292  `,
   293  
   294  		"mod/main.tf": `
   295  terraform {
   296    required_providers {
   297      test = {
   298        source = "registry.opentofu.org/hashicorp/test"
   299        configuration_aliases = [ test.x ]
   300  	}
   301    }
   302  }
   303  
   304  resource "test_object" "a" {
   305    provider = test.x
   306  }
   307  
   308  `,
   309  	})
   310  
   311  	p := simpleMockProvider()
   312  
   313  	// The resource within the module should be using the provider configured
   314  	// from the root module. We should never see an empty configuration.
   315  	p.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   316  		if req.Config.GetAttr("test_string").IsNull() {
   317  			resp.Diagnostics = resp.Diagnostics.Append(errors.New("missing test_string value"))
   318  		}
   319  		return resp
   320  	}
   321  
   322  	ctx := testContext2(t, &ContextOpts{
   323  		Providers: map[addrs.Provider]providers.Factory{
   324  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   325  		},
   326  	})
   327  
   328  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   329  	assertNoErrors(t, diags)
   330  }
   331  
   332  func TestContext2Plan_dataReferencesResourceInModules(t *testing.T) {
   333  	p := testProvider("test")
   334  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   335  		cfg := req.Config.AsValueMap()
   336  		cfg["id"] = cty.StringVal("d")
   337  		resp.State = cty.ObjectVal(cfg)
   338  		return resp
   339  	}
   340  
   341  	m := testModuleInline(t, map[string]string{
   342  		"main.tf": `
   343  locals {
   344    things = {
   345      old = "first"
   346      new = "second"
   347    }
   348  }
   349  
   350  module "mod" {
   351    source = "./mod"
   352    for_each = local.things
   353  }
   354  `,
   355  
   356  		"./mod/main.tf": `
   357  resource "test_resource" "a" {
   358  }
   359  
   360  data "test_data_source" "d" {
   361    depends_on = [test_resource.a]
   362  }
   363  
   364  resource "test_resource" "b" {
   365    value = data.test_data_source.d.id
   366  }
   367  `})
   368  
   369  	oldDataAddr := mustResourceInstanceAddr(`module.mod["old"].data.test_data_source.d`)
   370  
   371  	state := states.BuildState(func(s *states.SyncState) {
   372  		s.SetResourceInstanceCurrent(
   373  			mustResourceInstanceAddr(`module.mod["old"].test_resource.a`),
   374  			&states.ResourceInstanceObjectSrc{
   375  				AttrsJSON: []byte(`{"id":"a"}`),
   376  				Status:    states.ObjectReady,
   377  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   378  		)
   379  		s.SetResourceInstanceCurrent(
   380  			mustResourceInstanceAddr(`module.mod["old"].test_resource.b`),
   381  			&states.ResourceInstanceObjectSrc{
   382  				AttrsJSON: []byte(`{"id":"b","value":"d"}`),
   383  				Status:    states.ObjectReady,
   384  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   385  		)
   386  		s.SetResourceInstanceCurrent(
   387  			oldDataAddr,
   388  			&states.ResourceInstanceObjectSrc{
   389  				AttrsJSON: []byte(`{"id":"d"}`),
   390  				Status:    states.ObjectReady,
   391  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   392  		)
   393  	})
   394  
   395  	ctx := testContext2(t, &ContextOpts{
   396  		Providers: map[addrs.Provider]providers.Factory{
   397  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   398  		},
   399  	})
   400  
   401  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   402  	assertNoErrors(t, diags)
   403  
   404  	oldMod := oldDataAddr.Module
   405  
   406  	for _, c := range plan.Changes.Resources {
   407  		// there should be no changes from the old module instance
   408  		if c.Addr.Module.Equal(oldMod) && c.Action != plans.NoOp {
   409  			t.Errorf("unexpected change %s for %s\n", c.Action, c.Addr)
   410  		}
   411  	}
   412  }
   413  
   414  func TestContext2Plan_resourceChecksInExpandedModule(t *testing.T) {
   415  	// When a resource is in a nested module we have two levels of expansion
   416  	// to do: first expand the module the resource is declared in, and then
   417  	// expand the resource itself.
   418  	//
   419  	// In earlier versions of Terraform we did that expansion as two levels
   420  	// of DynamicExpand, which led to a bug where we didn't have any central
   421  	// location from which to register all of the instances of a checkable
   422  	// resource.
   423  	//
   424  	// We now handle the full expansion all in one graph node and one dynamic
   425  	// subgraph, which avoids the problem. This is a regression test for the
   426  	// earlier bug. If this test is panicking with "duplicate checkable objects
   427  	// report" then that suggests the bug is reintroduced and we're now back
   428  	// to reporting each module instance separately again, which is incorrect.
   429  
   430  	p := testProvider("test")
   431  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   432  		Provider: providers.Schema{
   433  			Block: &configschema.Block{},
   434  		},
   435  		ResourceTypes: map[string]providers.Schema{
   436  			"test": {
   437  				Block: &configschema.Block{},
   438  			},
   439  		},
   440  	}
   441  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   442  		resp.NewState = req.PriorState
   443  		return resp
   444  	}
   445  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   446  		resp.PlannedState = cty.EmptyObjectVal
   447  		return resp
   448  	}
   449  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   450  		resp.NewState = req.PlannedState
   451  		return resp
   452  	}
   453  
   454  	m := testModuleInline(t, map[string]string{
   455  		"main.tf": `
   456  			module "child" {
   457  				source = "./child"
   458  				count = 2 # must be at least 2 for this test to be valid
   459  			}
   460  		`,
   461  		"child/child.tf": `
   462  			locals {
   463  				a = "a"
   464  			}
   465  
   466  			resource "test" "test1" {
   467  				lifecycle {
   468  					postcondition {
   469  						# It doesn't matter what this checks as long as it
   470  						# passes, because if we don't handle expansion properly
   471  						# then we'll crash before we even get to evaluating this.
   472  						condition = local.a == local.a
   473  						error_message = "Postcondition failed."
   474  					}
   475  				}
   476  			}
   477  
   478  			resource "test" "test2" {
   479  				count = 2
   480  
   481  				lifecycle {
   482  					postcondition {
   483  						# It doesn't matter what this checks as long as it
   484  						# passes, because if we don't handle expansion properly
   485  						# then we'll crash before we even get to evaluating this.
   486  						condition = local.a == local.a
   487  						error_message = "Postcondition failed."
   488  					}
   489  				}
   490  			}
   491  		`,
   492  	})
   493  
   494  	ctx := testContext2(t, &ContextOpts{
   495  		Providers: map[addrs.Provider]providers.Factory{
   496  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   497  		},
   498  	})
   499  
   500  	priorState := states.NewState()
   501  	plan, diags := ctx.Plan(m, priorState, DefaultPlanOpts)
   502  	assertNoErrors(t, diags)
   503  
   504  	resourceInsts := []addrs.AbsResourceInstance{
   505  		mustResourceInstanceAddr("module.child[0].test.test1"),
   506  		mustResourceInstanceAddr("module.child[0].test.test2[0]"),
   507  		mustResourceInstanceAddr("module.child[0].test.test2[1]"),
   508  		mustResourceInstanceAddr("module.child[1].test.test1"),
   509  		mustResourceInstanceAddr("module.child[1].test.test2[0]"),
   510  		mustResourceInstanceAddr("module.child[1].test.test2[1]"),
   511  	}
   512  
   513  	for _, instAddr := range resourceInsts {
   514  		t.Run(fmt.Sprintf("results for %s", instAddr), func(t *testing.T) {
   515  			if rc := plan.Changes.ResourceInstance(instAddr); rc != nil {
   516  				if got, want := rc.Action, plans.Create; got != want {
   517  					t.Errorf("wrong action for %s\ngot:  %s\nwant: %s", instAddr, got, want)
   518  				}
   519  				if got, want := rc.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   520  					t.Errorf("wrong action reason for %s\ngot:  %s\nwant: %s", instAddr, got, want)
   521  				}
   522  			} else {
   523  				t.Errorf("no planned change for %s", instAddr)
   524  			}
   525  
   526  			if checkResult := plan.Checks.GetObjectResult(instAddr); checkResult != nil {
   527  				if got, want := checkResult.Status, checks.StatusPass; got != want {
   528  					t.Errorf("wrong check status for %s\ngot:  %s\nwant: %s", instAddr, got, want)
   529  				}
   530  			} else {
   531  				t.Errorf("no check result for %s", instAddr)
   532  			}
   533  		})
   534  	}
   535  }
   536  
   537  func TestContext2Plan_dataResourceChecksManagedResourceChange(t *testing.T) {
   538  	// This tests the situation where the remote system contains data that
   539  	// isn't valid per a data resource postcondition, but that the
   540  	// configuration is destined to make the remote system valid during apply
   541  	// and so we must defer reading the data resource and checking its
   542  	// conditions until the apply step.
   543  	//
   544  	// This is an exception to the rule tested in
   545  	// TestContext2Plan_dataReferencesResourceIndirectly which is relevant
   546  	// whenever there's at least one precondition or postcondition attached
   547  	// to a data resource.
   548  	//
   549  	// See TestContext2Plan_managedResourceChecksOtherManagedResourceChange for
   550  	// an incorrect situation where a data resource is used only indirectly
   551  	// to drive a precondition elsewhere, which therefore doesn't achieve this
   552  	// special exception.
   553  
   554  	p := testProvider("test")
   555  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   556  		Provider: providers.Schema{
   557  			Block: &configschema.Block{},
   558  		},
   559  		ResourceTypes: map[string]providers.Schema{
   560  			"test_resource": {
   561  				Block: &configschema.Block{
   562  					Attributes: map[string]*configschema.Attribute{
   563  						"id": {
   564  							Type:     cty.String,
   565  							Computed: true,
   566  						},
   567  						"valid": {
   568  							Type:     cty.Bool,
   569  							Required: true,
   570  						},
   571  					},
   572  				},
   573  			},
   574  		},
   575  		DataSources: map[string]providers.Schema{
   576  			"test_data_source": {
   577  				Block: &configschema.Block{
   578  					Attributes: map[string]*configschema.Attribute{
   579  						"id": {
   580  							Type:     cty.String,
   581  							Required: true,
   582  						},
   583  						"valid": {
   584  							Type:     cty.Bool,
   585  							Computed: true,
   586  						},
   587  					},
   588  				},
   589  			},
   590  		},
   591  	}
   592  	var mu sync.Mutex
   593  	validVal := cty.False
   594  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   595  		// NOTE: This assumes that the prior state declared below will have
   596  		// "valid" set to false already, and thus will match validVal above.
   597  		resp.NewState = req.PriorState
   598  		return resp
   599  	}
   600  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   601  		cfg := req.Config.AsValueMap()
   602  		mu.Lock()
   603  		cfg["valid"] = validVal
   604  		mu.Unlock()
   605  		resp.State = cty.ObjectVal(cfg)
   606  		return resp
   607  	}
   608  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   609  		cfg := req.Config.AsValueMap()
   610  		prior := req.PriorState.AsValueMap()
   611  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
   612  			"id":    prior["id"],
   613  			"valid": cfg["valid"],
   614  		})
   615  		return resp
   616  	}
   617  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   618  		planned := req.PlannedState.AsValueMap()
   619  
   620  		mu.Lock()
   621  		validVal = planned["valid"]
   622  		mu.Unlock()
   623  
   624  		resp.NewState = req.PlannedState
   625  		return resp
   626  	}
   627  
   628  	m := testModuleInline(t, map[string]string{
   629  		"main.tf": `
   630  
   631  resource "test_resource" "a" {
   632  	valid = true
   633  }
   634  
   635  locals {
   636  	# NOTE: We intentionally read through a local value here to make sure
   637  	# that this behavior still works even if there isn't a direct dependency
   638  	# between the data resource and the managed resource.
   639  	object_id = test_resource.a.id
   640  }
   641  
   642  data "test_data_source" "a" {
   643  	id = local.object_id
   644  
   645  	lifecycle {
   646  		postcondition {
   647  			condition     = self.valid
   648  			error_message = "Not valid!"
   649  		}
   650  	}
   651  }
   652  `})
   653  
   654  	managedAddr := mustResourceInstanceAddr(`test_resource.a`)
   655  	dataAddr := mustResourceInstanceAddr(`data.test_data_source.a`)
   656  
   657  	// This state is intended to represent the outcome of a previous apply that
   658  	// failed due to postcondition failure but had already updated the
   659  	// relevant object to be invalid.
   660  	//
   661  	// It could also potentially represent a similar situation where the
   662  	// previous apply succeeded but there has been a change outside of
   663  	// OpenTofu that made it invalid, although technically in that scenario
   664  	// the state data would become invalid only during the planning step. For
   665  	// our purposes here that's close enough because we don't have a real
   666  	// remote system in place anyway.
   667  	priorState := states.BuildState(func(s *states.SyncState) {
   668  		s.SetResourceInstanceCurrent(
   669  			managedAddr,
   670  			&states.ResourceInstanceObjectSrc{
   671  				// NOTE: "valid" is false here but is true in the configuration
   672  				// above, which is intended to represent that applying the
   673  				// configuration change would make this object become valid.
   674  				AttrsJSON: []byte(`{"id":"boop","valid":false}`),
   675  				Status:    states.ObjectReady,
   676  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   677  		)
   678  	})
   679  
   680  	ctx := testContext2(t, &ContextOpts{
   681  		Providers: map[addrs.Provider]providers.Factory{
   682  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   683  		},
   684  	})
   685  
   686  	plan, diags := ctx.Plan(m, priorState, DefaultPlanOpts)
   687  	assertNoErrors(t, diags)
   688  
   689  	if rc := plan.Changes.ResourceInstance(dataAddr); rc != nil {
   690  		if got, want := rc.Action, plans.Read; got != want {
   691  			t.Errorf("wrong action for %s\ngot:  %s\nwant: %s", dataAddr, got, want)
   692  		}
   693  		if got, want := rc.ActionReason, plans.ResourceInstanceReadBecauseDependencyPending; got != want {
   694  			t.Errorf("wrong action reason for %s\ngot:  %s\nwant: %s", dataAddr, got, want)
   695  		}
   696  	} else {
   697  		t.Fatalf("no planned change for %s", dataAddr)
   698  	}
   699  
   700  	if rc := plan.Changes.ResourceInstance(managedAddr); rc != nil {
   701  		if got, want := rc.Action, plans.Update; got != want {
   702  			t.Errorf("wrong action for %s\ngot:  %s\nwant: %s", managedAddr, got, want)
   703  		}
   704  		if got, want := rc.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
   705  			t.Errorf("wrong action reason for %s\ngot:  %s\nwant: %s", managedAddr, got, want)
   706  		}
   707  	} else {
   708  		t.Fatalf("no planned change for %s", managedAddr)
   709  	}
   710  
   711  	// This is primarily a plan-time test, since the special handling of
   712  	// data resources is a plan-time concern, but we'll still try applying the
   713  	// plan here just to make sure it's valid.
   714  	newState, diags := ctx.Apply(plan, m)
   715  	assertNoErrors(t, diags)
   716  
   717  	if rs := newState.ResourceInstance(dataAddr); rs != nil {
   718  		if !rs.HasCurrent() {
   719  			t.Errorf("no final state for %s", dataAddr)
   720  		}
   721  	} else {
   722  		t.Errorf("no final state for %s", dataAddr)
   723  	}
   724  
   725  	if rs := newState.ResourceInstance(managedAddr); rs != nil {
   726  		if !rs.HasCurrent() {
   727  			t.Errorf("no final state for %s", managedAddr)
   728  		}
   729  	} else {
   730  		t.Errorf("no final state for %s", managedAddr)
   731  	}
   732  
   733  	if got, want := validVal, cty.True; got != want {
   734  		t.Errorf("wrong final valid value\ngot:  %#v\nwant: %#v", got, want)
   735  	}
   736  
   737  }
   738  
   739  func TestContext2Plan_managedResourceChecksOtherManagedResourceChange(t *testing.T) {
   740  	// This tests the incorrect situation where a managed resource checks
   741  	// another managed resource indirectly via a data resource.
   742  	// This doesn't work because OpenTofu can't tell that the data resource
   743  	// outcome will be updated by a separate managed resource change and so
   744  	// we expect it to fail.
   745  	// This would ideally have worked except that we previously included a
   746  	// special case in the rules for data resources where they only consider
   747  	// direct dependencies when deciding whether to defer (except when the
   748  	// data resource itself has conditions) and so they can potentially
   749  	// read "too early" if the user creates the explicitly-not-recommended
   750  	// situation of a data resource and a managed resource in the same
   751  	// configuration both representing the same remote object.
   752  
   753  	p := testProvider("test")
   754  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   755  		Provider: providers.Schema{
   756  			Block: &configschema.Block{},
   757  		},
   758  		ResourceTypes: map[string]providers.Schema{
   759  			"test_resource": {
   760  				Block: &configschema.Block{
   761  					Attributes: map[string]*configschema.Attribute{
   762  						"id": {
   763  							Type:     cty.String,
   764  							Computed: true,
   765  						},
   766  						"valid": {
   767  							Type:     cty.Bool,
   768  							Required: true,
   769  						},
   770  					},
   771  				},
   772  			},
   773  		},
   774  		DataSources: map[string]providers.Schema{
   775  			"test_data_source": {
   776  				Block: &configschema.Block{
   777  					Attributes: map[string]*configschema.Attribute{
   778  						"id": {
   779  							Type:     cty.String,
   780  							Required: true,
   781  						},
   782  						"valid": {
   783  							Type:     cty.Bool,
   784  							Computed: true,
   785  						},
   786  					},
   787  				},
   788  			},
   789  		},
   790  	}
   791  	var mu sync.Mutex
   792  	validVal := cty.False
   793  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   794  		// NOTE: This assumes that the prior state declared below will have
   795  		// "valid" set to false already, and thus will match validVal above.
   796  		resp.NewState = req.PriorState
   797  		return resp
   798  	}
   799  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
   800  		cfg := req.Config.AsValueMap()
   801  		if cfg["id"].AsString() == "main" {
   802  			mu.Lock()
   803  			cfg["valid"] = validVal
   804  			mu.Unlock()
   805  		}
   806  		resp.State = cty.ObjectVal(cfg)
   807  		return resp
   808  	}
   809  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   810  		cfg := req.Config.AsValueMap()
   811  		prior := req.PriorState.AsValueMap()
   812  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
   813  			"id":    prior["id"],
   814  			"valid": cfg["valid"],
   815  		})
   816  		return resp
   817  	}
   818  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   819  		planned := req.PlannedState.AsValueMap()
   820  
   821  		if planned["id"].AsString() == "main" {
   822  			mu.Lock()
   823  			validVal = planned["valid"]
   824  			mu.Unlock()
   825  		}
   826  
   827  		resp.NewState = req.PlannedState
   828  		return resp
   829  	}
   830  
   831  	m := testModuleInline(t, map[string]string{
   832  		"main.tf": `
   833  
   834  resource "test_resource" "a" {
   835    valid = true
   836  }
   837  
   838  locals {
   839  	# NOTE: We intentionally read through a local value here because a
   840  	# direct reference from data.test_data_source.a to test_resource.a would
   841  	# cause OpenTofu to defer the data resource to the apply phase due to
   842  	# there being a pending change for the managed resource. We're explicitly
   843  	# testing the failure case where the data resource read happens too
   844  	# eagerly, which is what results from the reference being only indirect
   845  	# so OpenTofu can't "see" that the data resource result might be affected
   846  	# by changes to the managed resource.
   847  	object_id = test_resource.a.id
   848  }
   849  
   850  data "test_data_source" "a" {
   851  	id = local.object_id
   852  }
   853  
   854  resource "test_resource" "b" {
   855  	valid = true
   856  
   857  	lifecycle {
   858  		precondition {
   859  			condition     = data.test_data_source.a.valid
   860  			error_message = "Not valid!"
   861  		}
   862  	}
   863  }
   864  `})
   865  
   866  	managedAddrA := mustResourceInstanceAddr(`test_resource.a`)
   867  	managedAddrB := mustResourceInstanceAddr(`test_resource.b`)
   868  
   869  	// This state is intended to represent the outcome of a previous apply that
   870  	// failed due to postcondition failure but had already updated the
   871  	// relevant object to be invalid.
   872  	//
   873  	// It could also potentially represent a similar situation where the
   874  	// previous apply succeeded but there has been a change outside of
   875  	// OpenTofu that made it invalid, although technically in that scenario
   876  	// the state data would become invalid only during the planning step. For
   877  	// our purposes here that's close enough because we don't have a real
   878  	// remote system in place anyway.
   879  	priorState := states.BuildState(func(s *states.SyncState) {
   880  		s.SetResourceInstanceCurrent(
   881  			managedAddrA,
   882  			&states.ResourceInstanceObjectSrc{
   883  				// NOTE: "valid" is false here but is true in the configuration
   884  				// above, which is intended to represent that applying the
   885  				// configuration change would make this object become valid.
   886  				AttrsJSON: []byte(`{"id":"main","valid":false}`),
   887  				Status:    states.ObjectReady,
   888  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   889  		)
   890  		s.SetResourceInstanceCurrent(
   891  			managedAddrB,
   892  			&states.ResourceInstanceObjectSrc{
   893  				AttrsJSON: []byte(`{"id":"checker","valid":true}`),
   894  				Status:    states.ObjectReady,
   895  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   896  		)
   897  	})
   898  
   899  	ctx := testContext2(t, &ContextOpts{
   900  		Providers: map[addrs.Provider]providers.Factory{
   901  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   902  		},
   903  	})
   904  
   905  	_, diags := ctx.Plan(m, priorState, DefaultPlanOpts)
   906  	if !diags.HasErrors() {
   907  		t.Fatalf("unexpected successful plan; should've failed with non-passing precondition")
   908  	}
   909  
   910  	if got, want := diags.Err().Error(), "Resource precondition failed: Not valid!"; !strings.Contains(got, want) {
   911  		t.Errorf("Missing expected error message\ngot: %s\nwant substring: %s", got, want)
   912  	}
   913  }
   914  
   915  func TestContext2Plan_destroyWithRefresh(t *testing.T) {
   916  	m := testModuleInline(t, map[string]string{
   917  		"main.tf": `
   918  resource "test_object" "a" {
   919  }
   920  `,
   921  	})
   922  
   923  	p := simpleMockProvider()
   924  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
   925  		Provider: providers.Schema{Block: simpleTestSchema()},
   926  		ResourceTypes: map[string]providers.Schema{
   927  			"test_object": {
   928  				Block: &configschema.Block{
   929  					Attributes: map[string]*configschema.Attribute{
   930  						"arg": {Type: cty.String, Optional: true},
   931  					},
   932  				},
   933  			},
   934  		},
   935  	}
   936  
   937  	// This is called from the first instance of this provider, so we can't
   938  	// check p.ReadResourceCalled after plan.
   939  	readResourceCalled := false
   940  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
   941  		readResourceCalled = true
   942  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
   943  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
   944  				return cty.StringVal("current"), nil
   945  			}
   946  			return v, nil
   947  		})
   948  		if err != nil {
   949  			// shouldn't get here
   950  			t.Fatalf("ReadResourceFn transform failed")
   951  			return providers.ReadResourceResponse{}
   952  		}
   953  		return providers.ReadResourceResponse{
   954  			NewState: newVal,
   955  		}
   956  	}
   957  
   958  	upgradeResourceStateCalled := false
   959  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
   960  		upgradeResourceStateCalled = true
   961  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
   962  
   963  		// In the destroy-with-refresh codepath we end up calling
   964  		// UpgradeResourceState twice, because we do so once during refreshing
   965  		// (as part making a normal plan) and then again during the plan-destroy
   966  		// walk. The second call recieves the result of the earlier refresh,
   967  		// so we need to tolerate both "before" and "current" as possible
   968  		// inputs here.
   969  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
   970  			if !bytes.Contains(req.RawStateJSON, []byte("current")) {
   971  				t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object or the 'current' object\n%s", req.RawStateJSON)
   972  			}
   973  		}
   974  
   975  		// We'll put something different in "arg" as part of upgrading, just
   976  		// so that we can verify below that PrevRunState contains the upgraded
   977  		// (but NOT refreshed) version of the object.
   978  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
   979  			"arg": cty.StringVal("upgraded"),
   980  		})
   981  		return resp
   982  	}
   983  
   984  	addr := mustResourceInstanceAddr("test_object.a")
   985  	state := states.BuildState(func(s *states.SyncState) {
   986  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
   987  			AttrsJSON: []byte(`{"arg":"before"}`),
   988  			Status:    states.ObjectReady,
   989  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
   990  	})
   991  
   992  	ctx := testContext2(t, &ContextOpts{
   993  		Providers: map[addrs.Provider]providers.Factory{
   994  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   995  		},
   996  	})
   997  
   998  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   999  		Mode:        plans.DestroyMode,
  1000  		SkipRefresh: false, // the default
  1001  	})
  1002  	assertNoErrors(t, diags)
  1003  
  1004  	if !upgradeResourceStateCalled {
  1005  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1006  	}
  1007  	if !readResourceCalled {
  1008  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1009  	}
  1010  
  1011  	if plan.PriorState == nil {
  1012  		t.Fatal("missing plan state")
  1013  	}
  1014  
  1015  	for _, c := range plan.Changes.Resources {
  1016  		if c.Action != plans.Delete {
  1017  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
  1018  		}
  1019  	}
  1020  
  1021  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1022  		t.Errorf("%s has no previous run state at all after plan", addr)
  1023  	} else {
  1024  		if instState.Current == nil {
  1025  			t.Errorf("%s has no current object in the previous run state", addr)
  1026  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1027  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1028  		}
  1029  	}
  1030  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1031  		t.Errorf("%s has no prior state at all after plan", addr)
  1032  	} else {
  1033  		if instState.Current == nil {
  1034  			t.Errorf("%s has no current object in the prior state", addr)
  1035  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1036  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1037  		}
  1038  	}
  1039  }
  1040  
  1041  func TestContext2Plan_destroySkipRefresh(t *testing.T) {
  1042  	m := testModuleInline(t, map[string]string{
  1043  		"main.tf": `
  1044  resource "test_object" "a" {
  1045  }
  1046  `,
  1047  	})
  1048  
  1049  	p := simpleMockProvider()
  1050  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1051  		Provider: providers.Schema{Block: simpleTestSchema()},
  1052  		ResourceTypes: map[string]providers.Schema{
  1053  			"test_object": {
  1054  				Block: &configschema.Block{
  1055  					Attributes: map[string]*configschema.Attribute{
  1056  						"arg": {Type: cty.String, Optional: true},
  1057  					},
  1058  				},
  1059  			},
  1060  		},
  1061  	}
  1062  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  1063  		t.Helper()
  1064  		t.Errorf("unexpected call to ReadResource")
  1065  		resp.NewState = req.PriorState
  1066  		return resp
  1067  	}
  1068  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1069  		t.Logf("UpgradeResourceState %s", req.RawStateJSON)
  1070  		// We should've been given the prior state JSON as our input to upgrade.
  1071  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1072  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1073  		}
  1074  
  1075  		// We'll put something different in "arg" as part of upgrading, just
  1076  		// so that we can verify below that PrevRunState contains the upgraded
  1077  		// (but NOT refreshed) version of the object.
  1078  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1079  			"arg": cty.StringVal("upgraded"),
  1080  		})
  1081  		return resp
  1082  	}
  1083  
  1084  	addr := mustResourceInstanceAddr("test_object.a")
  1085  	state := states.BuildState(func(s *states.SyncState) {
  1086  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1087  			AttrsJSON: []byte(`{"arg":"before"}`),
  1088  			Status:    states.ObjectReady,
  1089  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1090  	})
  1091  
  1092  	ctx := testContext2(t, &ContextOpts{
  1093  		Providers: map[addrs.Provider]providers.Factory{
  1094  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1095  		},
  1096  	})
  1097  
  1098  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1099  		Mode:        plans.DestroyMode,
  1100  		SkipRefresh: true,
  1101  	})
  1102  	assertNoErrors(t, diags)
  1103  
  1104  	if !p.UpgradeResourceStateCalled {
  1105  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1106  	}
  1107  	if p.ReadResourceCalled {
  1108  		t.Errorf("Provider's ReadResource was called; shouldn't have been")
  1109  	}
  1110  
  1111  	if plan.PriorState == nil {
  1112  		t.Fatal("missing plan state")
  1113  	}
  1114  
  1115  	for _, c := range plan.Changes.Resources {
  1116  		if c.Action != plans.Delete {
  1117  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
  1118  		}
  1119  	}
  1120  
  1121  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1122  		t.Errorf("%s has no previous run state at all after plan", addr)
  1123  	} else {
  1124  		if instState.Current == nil {
  1125  			t.Errorf("%s has no current object in the previous run state", addr)
  1126  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1127  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1128  		}
  1129  	}
  1130  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1131  		t.Errorf("%s has no prior state at all after plan", addr)
  1132  	} else {
  1133  		if instState.Current == nil {
  1134  			t.Errorf("%s has no current object in the prior state", addr)
  1135  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1136  			// NOTE: The prior state should still have been _upgraded_, even
  1137  			// though we skipped running refresh after upgrading it.
  1138  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1139  		}
  1140  	}
  1141  }
  1142  
  1143  func TestContext2Plan_unmarkingSensitiveAttributeForOutput(t *testing.T) {
  1144  	m := testModuleInline(t, map[string]string{
  1145  		"main.tf": `
  1146  resource "test_resource" "foo" {
  1147  }
  1148  
  1149  output "result" {
  1150    value = nonsensitive(test_resource.foo.sensitive_attr)
  1151  }
  1152  `,
  1153  	})
  1154  
  1155  	p := new(MockProvider)
  1156  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1157  		ResourceTypes: map[string]*configschema.Block{
  1158  			"test_resource": {
  1159  				Attributes: map[string]*configschema.Attribute{
  1160  					"id": {
  1161  						Type:     cty.String,
  1162  						Computed: true,
  1163  					},
  1164  					"sensitive_attr": {
  1165  						Type:      cty.String,
  1166  						Computed:  true,
  1167  						Sensitive: true,
  1168  					},
  1169  				},
  1170  			},
  1171  		},
  1172  	})
  1173  
  1174  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1175  		return providers.PlanResourceChangeResponse{
  1176  			PlannedState: cty.UnknownVal(cty.Object(map[string]cty.Type{
  1177  				"id":             cty.String,
  1178  				"sensitive_attr": cty.String,
  1179  			})),
  1180  		}
  1181  	}
  1182  
  1183  	state := states.NewState()
  1184  
  1185  	ctx := testContext2(t, &ContextOpts{
  1186  		Providers: map[addrs.Provider]providers.Factory{
  1187  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1188  		},
  1189  	})
  1190  
  1191  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  1192  	assertNoErrors(t, diags)
  1193  
  1194  	for _, res := range plan.Changes.Resources {
  1195  		if res.Action != plans.Create {
  1196  			t.Fatalf("expected create, got: %q %s", res.Addr, res.Action)
  1197  		}
  1198  	}
  1199  }
  1200  
  1201  func TestContext2Plan_destroyNoProviderConfig(t *testing.T) {
  1202  	// providers do not need to be configured during a destroy plan
  1203  	p := simpleMockProvider()
  1204  	p.ValidateProviderConfigFn = func(req providers.ValidateProviderConfigRequest) (resp providers.ValidateProviderConfigResponse) {
  1205  		v := req.Config.GetAttr("test_string")
  1206  		if v.IsNull() || !v.IsKnown() || v.AsString() != "ok" {
  1207  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("invalid provider configuration: %#v", req.Config))
  1208  		}
  1209  		return resp
  1210  	}
  1211  
  1212  	m := testModuleInline(t, map[string]string{
  1213  		"main.tf": `
  1214  locals {
  1215    value = "ok"
  1216  }
  1217  
  1218  provider "test" {
  1219    test_string = local.value
  1220  }
  1221  `,
  1222  	})
  1223  
  1224  	addr := mustResourceInstanceAddr("test_object.a")
  1225  	state := states.BuildState(func(s *states.SyncState) {
  1226  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1227  			AttrsJSON: []byte(`{"test_string":"foo"}`),
  1228  			Status:    states.ObjectReady,
  1229  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1230  	})
  1231  
  1232  	ctx := testContext2(t, &ContextOpts{
  1233  		Providers: map[addrs.Provider]providers.Factory{
  1234  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1235  		},
  1236  	})
  1237  
  1238  	_, diags := ctx.Plan(m, state, &PlanOpts{
  1239  		Mode: plans.DestroyMode,
  1240  	})
  1241  	assertNoErrors(t, diags)
  1242  }
  1243  
  1244  func TestContext2Plan_movedResourceBasic(t *testing.T) {
  1245  	addrA := mustResourceInstanceAddr("test_object.a")
  1246  	addrB := mustResourceInstanceAddr("test_object.b")
  1247  	m := testModuleInline(t, map[string]string{
  1248  		"main.tf": `
  1249  			resource "test_object" "b" {
  1250  			}
  1251  
  1252  			moved {
  1253  				from = test_object.a
  1254  				to   = test_object.b
  1255  			}
  1256  		`,
  1257  	})
  1258  
  1259  	state := states.BuildState(func(s *states.SyncState) {
  1260  		// The prior state tracks test_object.a, which we should treat as
  1261  		// test_object.b because of the "moved" block in the config.
  1262  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1263  			AttrsJSON: []byte(`{}`),
  1264  			Status:    states.ObjectReady,
  1265  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1266  	})
  1267  
  1268  	p := simpleMockProvider()
  1269  	ctx := testContext2(t, &ContextOpts{
  1270  		Providers: map[addrs.Provider]providers.Factory{
  1271  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1272  		},
  1273  	})
  1274  
  1275  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1276  		Mode: plans.NormalMode,
  1277  		ForceReplace: []addrs.AbsResourceInstance{
  1278  			addrA,
  1279  		},
  1280  	})
  1281  	if diags.HasErrors() {
  1282  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1283  	}
  1284  
  1285  	t.Run(addrA.String(), func(t *testing.T) {
  1286  		instPlan := plan.Changes.ResourceInstance(addrA)
  1287  		if instPlan != nil {
  1288  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  1289  		}
  1290  	})
  1291  	t.Run(addrB.String(), func(t *testing.T) {
  1292  		instPlan := plan.Changes.ResourceInstance(addrB)
  1293  		if instPlan == nil {
  1294  			t.Fatalf("no plan for %s at all", addrB)
  1295  		}
  1296  
  1297  		if got, want := instPlan.Addr, addrB; !got.Equal(want) {
  1298  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1299  		}
  1300  		if got, want := instPlan.PrevRunAddr, addrA; !got.Equal(want) {
  1301  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1302  		}
  1303  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1304  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1305  		}
  1306  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1307  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1308  		}
  1309  	})
  1310  }
  1311  
  1312  func TestContext2Plan_movedResourceCollision(t *testing.T) {
  1313  	addrNoKey := mustResourceInstanceAddr("test_object.a")
  1314  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
  1315  	m := testModuleInline(t, map[string]string{
  1316  		"main.tf": `
  1317  			resource "test_object" "a" {
  1318  				# No "count" set, so test_object.a[0] will want
  1319  				# to implicitly move to test_object.a, but will get
  1320  				# blocked by the existing object at that address.
  1321  			}
  1322  		`,
  1323  	})
  1324  
  1325  	state := states.BuildState(func(s *states.SyncState) {
  1326  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  1327  			AttrsJSON: []byte(`{}`),
  1328  			Status:    states.ObjectReady,
  1329  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1330  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
  1331  			AttrsJSON: []byte(`{}`),
  1332  			Status:    states.ObjectReady,
  1333  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1334  	})
  1335  
  1336  	p := simpleMockProvider()
  1337  	ctx := testContext2(t, &ContextOpts{
  1338  		Providers: map[addrs.Provider]providers.Factory{
  1339  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1340  		},
  1341  	})
  1342  
  1343  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1344  		Mode: plans.NormalMode,
  1345  	})
  1346  	if diags.HasErrors() {
  1347  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1348  	}
  1349  
  1350  	// We should have a warning, though! We'll lightly abuse the "for RPC"
  1351  	// feature of diagnostics to get some more-readily-comparable diagnostic
  1352  	// values.
  1353  	gotDiags := diags.ForRPC()
  1354  	wantDiags := tfdiags.Diagnostics{
  1355  		tfdiags.Sourceless(
  1356  			tfdiags.Warning,
  1357  			"Unresolved resource instance address changes",
  1358  			`OpenTofu 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:
  1359    - test_object.a[0] could not move to test_object.a
  1360  
  1361  OpenTofu has planned to destroy these objects. If OpenTofu's proposed changes aren't appropriate, you must first resolve the conflicts using the "tofu state" subcommands and then create a new plan.`,
  1362  		),
  1363  	}.ForRPC()
  1364  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1365  		t.Errorf("wrong diagnostics\n%s", diff)
  1366  	}
  1367  
  1368  	t.Run(addrNoKey.String(), func(t *testing.T) {
  1369  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  1370  		if instPlan == nil {
  1371  			t.Fatalf("no plan for %s at all", addrNoKey)
  1372  		}
  1373  
  1374  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  1375  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1376  		}
  1377  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  1378  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1379  		}
  1380  		if got, want := instPlan.Action, plans.NoOp; got != want {
  1381  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1382  		}
  1383  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1384  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1385  		}
  1386  	})
  1387  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  1388  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  1389  		if instPlan == nil {
  1390  			t.Fatalf("no plan for %s at all", addrZeroKey)
  1391  		}
  1392  
  1393  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  1394  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1395  		}
  1396  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  1397  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1398  		}
  1399  		if got, want := instPlan.Action, plans.Delete; got != want {
  1400  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1401  		}
  1402  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseWrongRepetition; got != want {
  1403  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1404  		}
  1405  	})
  1406  }
  1407  
  1408  func TestContext2Plan_movedResourceCollisionDestroy(t *testing.T) {
  1409  	// This is like TestContext2Plan_movedResourceCollision but intended to
  1410  	// ensure we still produce the expected warning (and produce it only once)
  1411  	// when we're creating a destroy plan, rather than a normal plan.
  1412  	// (This case is interesting at the time of writing because we happen to
  1413  	// use a normal plan as a trick to refresh before creating a destroy plan.
  1414  	// This test will probably become uninteresting if a future change to
  1415  	// the destroy-time planning behavior handles refreshing in a different
  1416  	// way, which avoids this pre-processing step of running a normal plan
  1417  	// first.)
  1418  
  1419  	addrNoKey := mustResourceInstanceAddr("test_object.a")
  1420  	addrZeroKey := mustResourceInstanceAddr("test_object.a[0]")
  1421  	m := testModuleInline(t, map[string]string{
  1422  		"main.tf": `
  1423  			resource "test_object" "a" {
  1424  				# No "count" set, so test_object.a[0] will want
  1425  				# to implicitly move to test_object.a, but will get
  1426  				# blocked by the existing object at that address.
  1427  			}
  1428  		`,
  1429  	})
  1430  
  1431  	state := states.BuildState(func(s *states.SyncState) {
  1432  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  1433  			AttrsJSON: []byte(`{}`),
  1434  			Status:    states.ObjectReady,
  1435  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1436  		s.SetResourceInstanceCurrent(addrZeroKey, &states.ResourceInstanceObjectSrc{
  1437  			AttrsJSON: []byte(`{}`),
  1438  			Status:    states.ObjectReady,
  1439  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1440  	})
  1441  
  1442  	p := simpleMockProvider()
  1443  	ctx := testContext2(t, &ContextOpts{
  1444  		Providers: map[addrs.Provider]providers.Factory{
  1445  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1446  		},
  1447  	})
  1448  
  1449  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1450  		Mode: plans.DestroyMode,
  1451  	})
  1452  	if diags.HasErrors() {
  1453  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1454  	}
  1455  
  1456  	// We should have a warning, though! We'll lightly abuse the "for RPC"
  1457  	// feature of diagnostics to get some more-readily-comparable diagnostic
  1458  	// values.
  1459  	gotDiags := diags.ForRPC()
  1460  	wantDiags := tfdiags.Diagnostics{
  1461  		tfdiags.Sourceless(
  1462  			tfdiags.Warning,
  1463  			"Unresolved resource instance address changes",
  1464  			// NOTE: This message is _lightly_ confusing in the destroy case,
  1465  			// because it says "OpenTofu has planned to destroy these objects"
  1466  			// but this is a plan to destroy all objects, anyway. We expect the
  1467  			// conflict situation to be pretty rare though, and even rarer in
  1468  			// a "tofu destroy", so we'll just live with that for now
  1469  			// unless we see evidence that lots of folks are being confused by
  1470  			// it in practice.
  1471  			`OpenTofu 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:
  1472    - test_object.a[0] could not move to test_object.a
  1473  
  1474  OpenTofu has planned to destroy these objects. If OpenTofu's proposed changes aren't appropriate, you must first resolve the conflicts using the "tofu state" subcommands and then create a new plan.`,
  1475  		),
  1476  	}.ForRPC()
  1477  	if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1478  		// If we get here with a diff that makes it seem like the above warning
  1479  		// is being reported twice, the likely cause is not correctly handling
  1480  		// the warnings from the hidden normal plan we run as part of preparing
  1481  		// for a destroy plan, unless that strategy has changed in the meantime
  1482  		// since we originally wrote this test.
  1483  		t.Errorf("wrong diagnostics\n%s", diff)
  1484  	}
  1485  
  1486  	t.Run(addrNoKey.String(), func(t *testing.T) {
  1487  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  1488  		if instPlan == nil {
  1489  			t.Fatalf("no plan for %s at all", addrNoKey)
  1490  		}
  1491  
  1492  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  1493  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1494  		}
  1495  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  1496  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1497  		}
  1498  		if got, want := instPlan.Action, plans.Delete; got != want {
  1499  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1500  		}
  1501  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1502  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1503  		}
  1504  	})
  1505  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  1506  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  1507  		if instPlan == nil {
  1508  			t.Fatalf("no plan for %s at all", addrZeroKey)
  1509  		}
  1510  
  1511  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  1512  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  1513  		}
  1514  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  1515  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1516  		}
  1517  		if got, want := instPlan.Action, plans.Delete; got != want {
  1518  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1519  		}
  1520  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  1521  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  1522  		}
  1523  	})
  1524  }
  1525  
  1526  func TestContext2Plan_movedResourceUntargeted(t *testing.T) {
  1527  	addrA := mustResourceInstanceAddr("test_object.a")
  1528  	addrB := mustResourceInstanceAddr("test_object.b")
  1529  	m := testModuleInline(t, map[string]string{
  1530  		"main.tf": `
  1531  			resource "test_object" "b" {
  1532  			}
  1533  
  1534  			moved {
  1535  				from = test_object.a
  1536  				to   = test_object.b
  1537  			}
  1538  		`,
  1539  	})
  1540  
  1541  	state := states.BuildState(func(s *states.SyncState) {
  1542  		// The prior state tracks test_object.a, which we should treat as
  1543  		// test_object.b because of the "moved" block in the config.
  1544  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1545  			AttrsJSON: []byte(`{}`),
  1546  			Status:    states.ObjectReady,
  1547  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1548  	})
  1549  
  1550  	p := simpleMockProvider()
  1551  	ctx := testContext2(t, &ContextOpts{
  1552  		Providers: map[addrs.Provider]providers.Factory{
  1553  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1554  		},
  1555  	})
  1556  
  1557  	t.Run("without targeting instance A", func(t *testing.T) {
  1558  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1559  			Mode: plans.NormalMode,
  1560  			Targets: []addrs.Targetable{
  1561  				// NOTE: addrA isn't included here, but it's pending move to addrB
  1562  				// and so this plan request is invalid.
  1563  				addrB,
  1564  			},
  1565  		})
  1566  		diags.Sort()
  1567  
  1568  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1569  		// more easily comparable than the various different diagnostics types
  1570  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1571  		// comparison-friendly, by discarding all of the dynamic type information.
  1572  		gotDiags := diags.ForRPC()
  1573  		wantDiags := tfdiags.Diagnostics{
  1574  			tfdiags.Sourceless(
  1575  				tfdiags.Warning,
  1576  				"Resource targeting is in effect",
  1577  				`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.
  1578  
  1579  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
  1580  			),
  1581  			tfdiags.Sourceless(
  1582  				tfdiags.Error,
  1583  				"Moved resource instances excluded by targeting",
  1584  				`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -target=... options do not fully cover all of those resource instances.
  1585  
  1586  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1587    -target="test_object.a"
  1588  
  1589  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1590  			),
  1591  		}.ForRPC()
  1592  
  1593  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1594  			t.Errorf("wrong diagnostics\n%s", diff)
  1595  		}
  1596  	})
  1597  	t.Run("without targeting instance B", func(t *testing.T) {
  1598  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1599  			Mode: plans.NormalMode,
  1600  			Targets: []addrs.Targetable{
  1601  				addrA,
  1602  				// NOTE: addrB isn't included here, but it's pending move from
  1603  				// addrA and so this plan request is invalid.
  1604  			},
  1605  		})
  1606  		diags.Sort()
  1607  
  1608  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1609  		// more easily comparable than the various different diagnostics types
  1610  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1611  		// comparison-friendly, by discarding all of the dynamic type information.
  1612  		gotDiags := diags.ForRPC()
  1613  		wantDiags := tfdiags.Diagnostics{
  1614  			tfdiags.Sourceless(
  1615  				tfdiags.Warning,
  1616  				"Resource targeting is in effect",
  1617  				`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.
  1618  
  1619  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
  1620  			),
  1621  			tfdiags.Sourceless(
  1622  				tfdiags.Error,
  1623  				"Moved resource instances excluded by targeting",
  1624  				`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -target=... options do not fully cover all of those resource instances.
  1625  
  1626  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1627    -target="test_object.b"
  1628  
  1629  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1630  			),
  1631  		}.ForRPC()
  1632  
  1633  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1634  			t.Errorf("wrong diagnostics\n%s", diff)
  1635  		}
  1636  	})
  1637  	t.Run("without targeting either instance", func(t *testing.T) {
  1638  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1639  			Mode: plans.NormalMode,
  1640  			Targets: []addrs.Targetable{
  1641  				mustResourceInstanceAddr("test_object.unrelated"),
  1642  				// NOTE: neither addrA nor addrB are included here, but there's
  1643  				// a pending move between them and so this is invalid.
  1644  			},
  1645  		})
  1646  		diags.Sort()
  1647  
  1648  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1649  		// more easily comparable than the various different diagnostics types
  1650  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1651  		// comparison-friendly, by discarding all of the dynamic type information.
  1652  		gotDiags := diags.ForRPC()
  1653  		wantDiags := tfdiags.Diagnostics{
  1654  			tfdiags.Sourceless(
  1655  				tfdiags.Warning,
  1656  				"Resource targeting is in effect",
  1657  				`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.
  1658  
  1659  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
  1660  			),
  1661  			tfdiags.Sourceless(
  1662  				tfdiags.Error,
  1663  				"Moved resource instances excluded by targeting",
  1664  				`Resource instances in your current state have moved to new addresses in the latest configuration. OpenTofu must include those resource instances while planning in order to ensure a correct result, but your -target=... options do not fully cover all of those resource instances.
  1665  
  1666  To create a valid plan, either remove your -target=... options altogether or add the following additional target options:
  1667    -target="test_object.a"
  1668    -target="test_object.b"
  1669  
  1670  Note that adding these options may include further additional resource instances in your plan, in order to respect object dependencies.`,
  1671  			),
  1672  		}.ForRPC()
  1673  
  1674  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1675  			t.Errorf("wrong diagnostics\n%s", diff)
  1676  		}
  1677  	})
  1678  	t.Run("with both addresses in the target set", func(t *testing.T) {
  1679  		// The error messages in the other subtests above suggest adding
  1680  		// addresses to the set of targets. This additional test makes sure that
  1681  		// following that advice actually leads to a valid result.
  1682  
  1683  		_, diags := ctx.Plan(m, state, &PlanOpts{
  1684  			Mode: plans.NormalMode,
  1685  			Targets: []addrs.Targetable{
  1686  				// This time we're including both addresses in the target,
  1687  				// to get the same effect an end-user would get if following
  1688  				// the advice in our error message in the other subtests.
  1689  				addrA,
  1690  				addrB,
  1691  			},
  1692  		})
  1693  		diags.Sort()
  1694  
  1695  		// We're semi-abusing "ForRPC" here just to get diagnostics that are
  1696  		// more easily comparable than the various different diagnostics types
  1697  		// tfdiags uses internally. The RPC-friendly diagnostics are also
  1698  		// comparison-friendly, by discarding all of the dynamic type information.
  1699  		gotDiags := diags.ForRPC()
  1700  		wantDiags := tfdiags.Diagnostics{
  1701  			// Still get the warning about the -target option...
  1702  			tfdiags.Sourceless(
  1703  				tfdiags.Warning,
  1704  				"Resource targeting is in effect",
  1705  				`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.
  1706  
  1707  The -target option is not for routine use, and is provided only for exceptional situations such as recovering from errors or mistakes, or when OpenTofu specifically suggests to use it as part of an error message.`,
  1708  			),
  1709  			// ...but now we have no error about test_object.a
  1710  		}.ForRPC()
  1711  
  1712  		if diff := cmp.Diff(wantDiags, gotDiags); diff != "" {
  1713  			t.Errorf("wrong diagnostics\n%s", diff)
  1714  		}
  1715  	})
  1716  }
  1717  
  1718  func TestContext2Plan_untargetedResourceSchemaChange(t *testing.T) {
  1719  	// an untargeted resource which requires a schema migration should not
  1720  	// block planning due external changes in the plan.
  1721  	addrA := mustResourceInstanceAddr("test_object.a")
  1722  	addrB := mustResourceInstanceAddr("test_object.b")
  1723  	m := testModuleInline(t, map[string]string{
  1724  		"main.tf": `
  1725  resource "test_object" "a" {
  1726  }
  1727  resource "test_object" "b" {
  1728  }`,
  1729  	})
  1730  
  1731  	state := states.BuildState(func(s *states.SyncState) {
  1732  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1733  			AttrsJSON: []byte(`{}`),
  1734  			Status:    states.ObjectReady,
  1735  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1736  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
  1737  			// old_list is no longer in the schema
  1738  			AttrsJSON: []byte(`{"old_list":["used to be","a list here"]}`),
  1739  			Status:    states.ObjectReady,
  1740  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1741  	})
  1742  
  1743  	p := simpleMockProvider()
  1744  
  1745  	// external changes trigger a "drift report", but because test_object.b was
  1746  	// not targeted, the state was not fixed to match the schema and cannot be
  1747  	// deocded for the report.
  1748  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  1749  		obj := req.PriorState.AsValueMap()
  1750  		// test_number changed externally
  1751  		obj["test_number"] = cty.NumberIntVal(1)
  1752  		resp.NewState = cty.ObjectVal(obj)
  1753  		return resp
  1754  	}
  1755  
  1756  	ctx := testContext2(t, &ContextOpts{
  1757  		Providers: map[addrs.Provider]providers.Factory{
  1758  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1759  		},
  1760  	})
  1761  
  1762  	_, diags := ctx.Plan(m, state, &PlanOpts{
  1763  		Mode: plans.NormalMode,
  1764  		Targets: []addrs.Targetable{
  1765  			addrA,
  1766  		},
  1767  	})
  1768  	//
  1769  	assertNoErrors(t, diags)
  1770  }
  1771  
  1772  func TestContext2Plan_movedResourceRefreshOnly(t *testing.T) {
  1773  	addrA := mustResourceInstanceAddr("test_object.a")
  1774  	addrB := mustResourceInstanceAddr("test_object.b")
  1775  	m := testModuleInline(t, map[string]string{
  1776  		"main.tf": `
  1777  			resource "test_object" "b" {
  1778  			}
  1779  
  1780  			moved {
  1781  				from = test_object.a
  1782  				to   = test_object.b
  1783  			}
  1784  		`,
  1785  	})
  1786  
  1787  	state := states.BuildState(func(s *states.SyncState) {
  1788  		// The prior state tracks test_object.a, which we should treat as
  1789  		// test_object.b because of the "moved" block in the config.
  1790  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  1791  			AttrsJSON: []byte(`{}`),
  1792  			Status:    states.ObjectReady,
  1793  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1794  	})
  1795  
  1796  	p := simpleMockProvider()
  1797  	ctx := testContext2(t, &ContextOpts{
  1798  		Providers: map[addrs.Provider]providers.Factory{
  1799  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1800  		},
  1801  	})
  1802  
  1803  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1804  		Mode: plans.RefreshOnlyMode,
  1805  	})
  1806  	if diags.HasErrors() {
  1807  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1808  	}
  1809  
  1810  	t.Run(addrA.String(), func(t *testing.T) {
  1811  		instPlan := plan.Changes.ResourceInstance(addrA)
  1812  		if instPlan != nil {
  1813  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  1814  		}
  1815  	})
  1816  	t.Run(addrB.String(), func(t *testing.T) {
  1817  		instPlan := plan.Changes.ResourceInstance(addrB)
  1818  		if instPlan != nil {
  1819  			t.Fatalf("unexpected plan for %s", addrB)
  1820  		}
  1821  	})
  1822  	t.Run("drift", func(t *testing.T) {
  1823  		var drifted *plans.ResourceInstanceChangeSrc
  1824  		for _, dr := range plan.DriftedResources {
  1825  			if dr.Addr.Equal(addrB) {
  1826  				drifted = dr
  1827  				break
  1828  			}
  1829  		}
  1830  
  1831  		if drifted == nil {
  1832  			t.Fatalf("instance %s is missing from the drifted resource changes", addrB)
  1833  		}
  1834  
  1835  		if got, want := drifted.PrevRunAddr, addrA; !got.Equal(want) {
  1836  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  1837  		}
  1838  		if got, want := drifted.Action, plans.NoOp; got != want {
  1839  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  1840  		}
  1841  	})
  1842  }
  1843  
  1844  func TestContext2Plan_refreshOnlyMode(t *testing.T) {
  1845  	addr := mustResourceInstanceAddr("test_object.a")
  1846  
  1847  	// The configuration, the prior state, and the refresh result intentionally
  1848  	// have different values for "test_string" so we can observe that the
  1849  	// refresh took effect but the configuration change wasn't considered.
  1850  	m := testModuleInline(t, map[string]string{
  1851  		"main.tf": `
  1852  			resource "test_object" "a" {
  1853  				arg = "after"
  1854  			}
  1855  
  1856  			output "out" {
  1857  				value = test_object.a.arg
  1858  			}
  1859  		`,
  1860  	})
  1861  	state := states.BuildState(func(s *states.SyncState) {
  1862  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  1863  			AttrsJSON: []byte(`{"arg":"before"}`),
  1864  			Status:    states.ObjectReady,
  1865  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  1866  	})
  1867  
  1868  	p := simpleMockProvider()
  1869  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  1870  		Provider: providers.Schema{Block: simpleTestSchema()},
  1871  		ResourceTypes: map[string]providers.Schema{
  1872  			"test_object": {
  1873  				Block: &configschema.Block{
  1874  					Attributes: map[string]*configschema.Attribute{
  1875  						"arg": {Type: cty.String, Optional: true},
  1876  					},
  1877  				},
  1878  			},
  1879  		},
  1880  	}
  1881  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  1882  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  1883  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  1884  				return cty.StringVal("current"), nil
  1885  			}
  1886  			return v, nil
  1887  		})
  1888  		if err != nil {
  1889  			// shouldn't get here
  1890  			t.Fatalf("ReadResourceFn transform failed")
  1891  			return providers.ReadResourceResponse{}
  1892  		}
  1893  		return providers.ReadResourceResponse{
  1894  			NewState: newVal,
  1895  		}
  1896  	}
  1897  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  1898  		// We should've been given the prior state JSON as our input to upgrade.
  1899  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  1900  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  1901  		}
  1902  
  1903  		// We'll put something different in "arg" as part of upgrading, just
  1904  		// so that we can verify below that PrevRunState contains the upgraded
  1905  		// (but NOT refreshed) version of the object.
  1906  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  1907  			"arg": cty.StringVal("upgraded"),
  1908  		})
  1909  		return resp
  1910  	}
  1911  
  1912  	ctx := testContext2(t, &ContextOpts{
  1913  		Providers: map[addrs.Provider]providers.Factory{
  1914  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1915  		},
  1916  	})
  1917  
  1918  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1919  		Mode: plans.RefreshOnlyMode,
  1920  	})
  1921  	if diags.HasErrors() {
  1922  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  1923  	}
  1924  
  1925  	if !p.UpgradeResourceStateCalled {
  1926  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  1927  	}
  1928  	if !p.ReadResourceCalled {
  1929  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  1930  	}
  1931  
  1932  	if got, want := len(plan.Changes.Resources), 0; got != want {
  1933  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  1934  	}
  1935  
  1936  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  1937  		t.Errorf("%s has no prior state at all after plan", addr)
  1938  	} else {
  1939  		if instState.Current == nil {
  1940  			t.Errorf("%s has no current object after plan", addr)
  1941  		} else if got, want := instState.Current.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  1942  			// Should've saved the result of refreshing
  1943  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1944  		}
  1945  	}
  1946  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  1947  		t.Errorf("%s has no previous run state at all after plan", addr)
  1948  	} else {
  1949  		if instState.Current == nil {
  1950  			t.Errorf("%s has no current object in the previous run state", addr)
  1951  		} else if got, want := instState.Current.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  1952  			// Should've saved the result of upgrading
  1953  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  1954  		}
  1955  	}
  1956  
  1957  	// The output value should also have updated. If not, it's likely that we
  1958  	// skipped updating the working state to match the refreshed state when we
  1959  	// were evaluating the resource.
  1960  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  1961  		t.Errorf("no change planned for output value 'out'")
  1962  	} else {
  1963  		outChange, err := outChangeSrc.Decode()
  1964  		if err != nil {
  1965  			t.Fatalf("failed to decode output value 'out': %s", err)
  1966  		}
  1967  		got := outChange.After
  1968  		want := cty.StringVal("current")
  1969  		if !want.RawEquals(got) {
  1970  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  1971  		}
  1972  	}
  1973  }
  1974  
  1975  func TestContext2Plan_refreshOnlyMode_deposed(t *testing.T) {
  1976  	addr := mustResourceInstanceAddr("test_object.a")
  1977  	deposedKey := states.DeposedKey("byebye")
  1978  
  1979  	// The configuration, the prior state, and the refresh result intentionally
  1980  	// have different values for "test_string" so we can observe that the
  1981  	// refresh took effect but the configuration change wasn't considered.
  1982  	m := testModuleInline(t, map[string]string{
  1983  		"main.tf": `
  1984  			resource "test_object" "a" {
  1985  				arg = "after"
  1986  			}
  1987  
  1988  			output "out" {
  1989  				value = test_object.a.arg
  1990  			}
  1991  		`,
  1992  	})
  1993  	state := states.BuildState(func(s *states.SyncState) {
  1994  		// Note that we're intentionally recording a _deposed_ object here,
  1995  		// and not including a current object, so a normal (non-refresh)
  1996  		// plan would normally plan to create a new object _and_ destroy
  1997  		// the deposed one, but refresh-only mode should prevent that.
  1998  		s.SetResourceInstanceDeposed(addr, deposedKey, &states.ResourceInstanceObjectSrc{
  1999  			AttrsJSON: []byte(`{"arg":"before"}`),
  2000  			Status:    states.ObjectReady,
  2001  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2002  	})
  2003  
  2004  	p := simpleMockProvider()
  2005  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  2006  		Provider: providers.Schema{Block: simpleTestSchema()},
  2007  		ResourceTypes: map[string]providers.Schema{
  2008  			"test_object": {
  2009  				Block: &configschema.Block{
  2010  					Attributes: map[string]*configschema.Attribute{
  2011  						"arg": {Type: cty.String, Optional: true},
  2012  					},
  2013  				},
  2014  			},
  2015  		},
  2016  	}
  2017  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  2018  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2019  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  2020  				return cty.StringVal("current"), nil
  2021  			}
  2022  			return v, nil
  2023  		})
  2024  		if err != nil {
  2025  			// shouldn't get here
  2026  			t.Fatalf("ReadResourceFn transform failed")
  2027  			return providers.ReadResourceResponse{}
  2028  		}
  2029  		return providers.ReadResourceResponse{
  2030  			NewState: newVal,
  2031  		}
  2032  	}
  2033  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  2034  		// We should've been given the prior state JSON as our input to upgrade.
  2035  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  2036  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  2037  		}
  2038  
  2039  		// We'll put something different in "arg" as part of upgrading, just
  2040  		// so that we can verify below that PrevRunState contains the upgraded
  2041  		// (but NOT refreshed) version of the object.
  2042  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  2043  			"arg": cty.StringVal("upgraded"),
  2044  		})
  2045  		return resp
  2046  	}
  2047  
  2048  	ctx := testContext2(t, &ContextOpts{
  2049  		Providers: map[addrs.Provider]providers.Factory{
  2050  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2051  		},
  2052  	})
  2053  
  2054  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2055  		Mode: plans.RefreshOnlyMode,
  2056  	})
  2057  	if diags.HasErrors() {
  2058  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2059  	}
  2060  
  2061  	if !p.UpgradeResourceStateCalled {
  2062  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  2063  	}
  2064  	if !p.ReadResourceCalled {
  2065  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  2066  	}
  2067  
  2068  	if got, want := len(plan.Changes.Resources), 0; got != want {
  2069  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  2070  	}
  2071  
  2072  	if instState := plan.PriorState.ResourceInstance(addr); instState == nil {
  2073  		t.Errorf("%s has no prior state at all after plan", addr)
  2074  	} else {
  2075  		if obj := instState.Deposed[deposedKey]; obj == nil {
  2076  			t.Errorf("%s has no deposed object after plan", addr)
  2077  		} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  2078  			// Should've saved the result of refreshing
  2079  			t.Errorf("%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  2080  		}
  2081  	}
  2082  	if instState := plan.PrevRunState.ResourceInstance(addr); instState == nil {
  2083  		t.Errorf("%s has no previous run state at all after plan", addr)
  2084  	} else {
  2085  		if obj := instState.Deposed[deposedKey]; obj == nil {
  2086  			t.Errorf("%s has no deposed object in the previous run state", addr)
  2087  		} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  2088  			// Should've saved the result of upgrading
  2089  			t.Errorf("%s has wrong previous run state after plan\ngot:\n%s\n\nwant substring: %s", addr, got, want)
  2090  		}
  2091  	}
  2092  
  2093  	// The output value should also have updated. If not, it's likely that we
  2094  	// skipped updating the working state to match the refreshed state when we
  2095  	// were evaluating the resource.
  2096  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  2097  		t.Errorf("no change planned for output value 'out'")
  2098  	} else {
  2099  		outChange, err := outChangeSrc.Decode()
  2100  		if err != nil {
  2101  			t.Fatalf("failed to decode output value 'out': %s", err)
  2102  		}
  2103  		got := outChange.After
  2104  		want := cty.UnknownVal(cty.String)
  2105  		if !want.RawEquals(got) {
  2106  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  2107  		}
  2108  	}
  2109  
  2110  	// Deposed objects should not be represented in drift.
  2111  	if len(plan.DriftedResources) > 0 {
  2112  		t.Errorf("unexpected drifted resources (%d)", len(plan.DriftedResources))
  2113  	}
  2114  }
  2115  
  2116  func TestContext2Plan_refreshOnlyMode_orphan(t *testing.T) {
  2117  	addr := mustAbsResourceAddr("test_object.a")
  2118  
  2119  	// The configuration, the prior state, and the refresh result intentionally
  2120  	// have different values for "test_string" so we can observe that the
  2121  	// refresh took effect but the configuration change wasn't considered.
  2122  	m := testModuleInline(t, map[string]string{
  2123  		"main.tf": `
  2124  			resource "test_object" "a" {
  2125  				arg = "after"
  2126  				count = 1
  2127  			}
  2128  
  2129  			output "out" {
  2130  				value = test_object.a.*.arg
  2131  			}
  2132  		`,
  2133  	})
  2134  	state := states.BuildState(func(s *states.SyncState) {
  2135  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(0)), &states.ResourceInstanceObjectSrc{
  2136  			AttrsJSON: []byte(`{"arg":"before"}`),
  2137  			Status:    states.ObjectReady,
  2138  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2139  		s.SetResourceInstanceCurrent(addr.Instance(addrs.IntKey(1)), &states.ResourceInstanceObjectSrc{
  2140  			AttrsJSON: []byte(`{"arg":"before"}`),
  2141  			Status:    states.ObjectReady,
  2142  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2143  	})
  2144  
  2145  	p := simpleMockProvider()
  2146  	p.GetProviderSchemaResponse = &providers.GetProviderSchemaResponse{
  2147  		Provider: providers.Schema{Block: simpleTestSchema()},
  2148  		ResourceTypes: map[string]providers.Schema{
  2149  			"test_object": {
  2150  				Block: &configschema.Block{
  2151  					Attributes: map[string]*configschema.Attribute{
  2152  						"arg": {Type: cty.String, Optional: true},
  2153  					},
  2154  				},
  2155  			},
  2156  		},
  2157  	}
  2158  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  2159  		newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2160  			if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "arg"}) {
  2161  				return cty.StringVal("current"), nil
  2162  			}
  2163  			return v, nil
  2164  		})
  2165  		if err != nil {
  2166  			// shouldn't get here
  2167  			t.Fatalf("ReadResourceFn transform failed")
  2168  			return providers.ReadResourceResponse{}
  2169  		}
  2170  		return providers.ReadResourceResponse{
  2171  			NewState: newVal,
  2172  		}
  2173  	}
  2174  	p.UpgradeResourceStateFn = func(req providers.UpgradeResourceStateRequest) (resp providers.UpgradeResourceStateResponse) {
  2175  		// We should've been given the prior state JSON as our input to upgrade.
  2176  		if !bytes.Contains(req.RawStateJSON, []byte("before")) {
  2177  			t.Fatalf("UpgradeResourceState request doesn't contain the 'before' object\n%s", req.RawStateJSON)
  2178  		}
  2179  
  2180  		// We'll put something different in "arg" as part of upgrading, just
  2181  		// so that we can verify below that PrevRunState contains the upgraded
  2182  		// (but NOT refreshed) version of the object.
  2183  		resp.UpgradedState = cty.ObjectVal(map[string]cty.Value{
  2184  			"arg": cty.StringVal("upgraded"),
  2185  		})
  2186  		return resp
  2187  	}
  2188  
  2189  	ctx := testContext2(t, &ContextOpts{
  2190  		Providers: map[addrs.Provider]providers.Factory{
  2191  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2192  		},
  2193  	})
  2194  
  2195  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2196  		Mode: plans.RefreshOnlyMode,
  2197  	})
  2198  	if diags.HasErrors() {
  2199  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2200  	}
  2201  
  2202  	if !p.UpgradeResourceStateCalled {
  2203  		t.Errorf("Provider's UpgradeResourceState wasn't called; should've been")
  2204  	}
  2205  	if !p.ReadResourceCalled {
  2206  		t.Errorf("Provider's ReadResource wasn't called; should've been")
  2207  	}
  2208  
  2209  	if got, want := len(plan.Changes.Resources), 0; got != want {
  2210  		t.Errorf("plan contains resource changes; want none\n%s", spew.Sdump(plan.Changes.Resources))
  2211  	}
  2212  
  2213  	if rState := plan.PriorState.Resource(addr); rState == nil {
  2214  		t.Errorf("%s has no prior state at all after plan", addr)
  2215  	} else {
  2216  		for i := 0; i < 2; i++ {
  2217  			instKey := addrs.IntKey(i)
  2218  			if obj := rState.Instance(instKey).Current; obj == nil {
  2219  				t.Errorf("%s%s has no object after plan", addr, instKey)
  2220  			} else if got, want := obj.AttrsJSON, `"current"`; !bytes.Contains(got, []byte(want)) {
  2221  				// Should've saved the result of refreshing
  2222  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  2223  			}
  2224  		}
  2225  	}
  2226  	if rState := plan.PrevRunState.Resource(addr); rState == nil {
  2227  		t.Errorf("%s has no prior state at all after plan", addr)
  2228  	} else {
  2229  		for i := 0; i < 2; i++ {
  2230  			instKey := addrs.IntKey(i)
  2231  			if obj := rState.Instance(instKey).Current; obj == nil {
  2232  				t.Errorf("%s%s has no object after plan", addr, instKey)
  2233  			} else if got, want := obj.AttrsJSON, `"upgraded"`; !bytes.Contains(got, []byte(want)) {
  2234  				// Should've saved the result of upgrading
  2235  				t.Errorf("%s%s has wrong prior state after plan\ngot:\n%s\n\nwant substring: %s", addr, instKey, got, want)
  2236  			}
  2237  		}
  2238  	}
  2239  
  2240  	// The output value should also have updated. If not, it's likely that we
  2241  	// skipped updating the working state to match the refreshed state when we
  2242  	// were evaluating the resource.
  2243  	if outChangeSrc := plan.Changes.OutputValue(addrs.RootModuleInstance.OutputValue("out")); outChangeSrc == nil {
  2244  		t.Errorf("no change planned for output value 'out'")
  2245  	} else {
  2246  		outChange, err := outChangeSrc.Decode()
  2247  		if err != nil {
  2248  			t.Fatalf("failed to decode output value 'out': %s", err)
  2249  		}
  2250  		got := outChange.After
  2251  		want := cty.TupleVal([]cty.Value{cty.StringVal("current"), cty.StringVal("current")})
  2252  		if !want.RawEquals(got) {
  2253  			t.Errorf("wrong value for output value 'out'\ngot:  %#v\nwant: %#v", got, want)
  2254  		}
  2255  	}
  2256  }
  2257  
  2258  func TestContext2Plan_invalidSensitiveModuleOutput(t *testing.T) {
  2259  	m := testModuleInline(t, map[string]string{
  2260  		"child/main.tf": `
  2261  output "out" {
  2262    value = sensitive("xyz")
  2263  }`,
  2264  		"main.tf": `
  2265  module "child" {
  2266    source = "./child"
  2267  }
  2268  
  2269  output "root" {
  2270    value = module.child.out
  2271  }`,
  2272  	})
  2273  
  2274  	ctx := testContext2(t, &ContextOpts{})
  2275  
  2276  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  2277  	if !diags.HasErrors() {
  2278  		t.Fatal("succeeded; want errors")
  2279  	}
  2280  	if got, want := diags.Err().Error(), "Output refers to sensitive values"; !strings.Contains(got, want) {
  2281  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  2282  	}
  2283  }
  2284  
  2285  func TestContext2Plan_planDataSourceSensitiveNested(t *testing.T) {
  2286  	m := testModuleInline(t, map[string]string{
  2287  		"main.tf": `
  2288  resource "test_instance" "bar" {
  2289  }
  2290  
  2291  data "test_data_source" "foo" {
  2292    foo {
  2293      bar = test_instance.bar.sensitive
  2294    }
  2295  }
  2296  `,
  2297  	})
  2298  
  2299  	p := new(MockProvider)
  2300  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2301  		resp.PlannedState = cty.ObjectVal(map[string]cty.Value{
  2302  			"sensitive": cty.UnknownVal(cty.String),
  2303  		})
  2304  		return resp
  2305  	}
  2306  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2307  		ResourceTypes: map[string]*configschema.Block{
  2308  			"test_instance": {
  2309  				Attributes: map[string]*configschema.Attribute{
  2310  					"sensitive": {
  2311  						Type:      cty.String,
  2312  						Computed:  true,
  2313  						Sensitive: true,
  2314  					},
  2315  				},
  2316  			},
  2317  		},
  2318  		DataSources: map[string]*configschema.Block{
  2319  			"test_data_source": {
  2320  				Attributes: map[string]*configschema.Attribute{
  2321  					"id": {
  2322  						Type:     cty.String,
  2323  						Computed: true,
  2324  					},
  2325  				},
  2326  				BlockTypes: map[string]*configschema.NestedBlock{
  2327  					"foo": {
  2328  						Block: configschema.Block{
  2329  							Attributes: map[string]*configschema.Attribute{
  2330  								"bar": {Type: cty.String, Optional: true},
  2331  							},
  2332  						},
  2333  						Nesting: configschema.NestingSet,
  2334  					},
  2335  				},
  2336  			},
  2337  		},
  2338  	})
  2339  
  2340  	state := states.NewState()
  2341  	root := state.EnsureModule(addrs.RootModuleInstance)
  2342  	root.SetResourceInstanceCurrent(
  2343  		mustResourceInstanceAddr("data.test_data_source.foo").Resource,
  2344  		&states.ResourceInstanceObjectSrc{
  2345  			Status:    states.ObjectReady,
  2346  			AttrsJSON: []byte(`{"string":"data_id", "foo":[{"bar":"old"}]}`),
  2347  			AttrSensitivePaths: []cty.PathValueMarks{
  2348  				{
  2349  					Path:  cty.GetAttrPath("foo"),
  2350  					Marks: cty.NewValueMarks(marks.Sensitive),
  2351  				},
  2352  			},
  2353  		},
  2354  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2355  	)
  2356  	root.SetResourceInstanceCurrent(
  2357  		mustResourceInstanceAddr("test_instance.bar").Resource,
  2358  		&states.ResourceInstanceObjectSrc{
  2359  			Status:    states.ObjectReady,
  2360  			AttrsJSON: []byte(`{"sensitive":"old"}`),
  2361  			AttrSensitivePaths: []cty.PathValueMarks{
  2362  				{
  2363  					Path:  cty.GetAttrPath("sensitive"),
  2364  					Marks: cty.NewValueMarks(marks.Sensitive),
  2365  				},
  2366  			},
  2367  		},
  2368  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2369  	)
  2370  
  2371  	ctx := testContext2(t, &ContextOpts{
  2372  		Providers: map[addrs.Provider]providers.Factory{
  2373  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2374  		},
  2375  	})
  2376  
  2377  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2378  	assertNoErrors(t, diags)
  2379  
  2380  	for _, res := range plan.Changes.Resources {
  2381  		switch res.Addr.String() {
  2382  		case "test_instance.bar":
  2383  			if res.Action != plans.Update {
  2384  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2385  			}
  2386  		case "data.test_data_source.foo":
  2387  			if res.Action != plans.Read {
  2388  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2389  			}
  2390  		default:
  2391  			t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2392  		}
  2393  	}
  2394  }
  2395  
  2396  func TestContext2Plan_forceReplace(t *testing.T) {
  2397  	addrA := mustResourceInstanceAddr("test_object.a")
  2398  	addrB := mustResourceInstanceAddr("test_object.b")
  2399  	m := testModuleInline(t, map[string]string{
  2400  		"main.tf": `
  2401  			resource "test_object" "a" {
  2402  			}
  2403  			resource "test_object" "b" {
  2404  			}
  2405  		`,
  2406  	})
  2407  
  2408  	state := states.BuildState(func(s *states.SyncState) {
  2409  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  2410  			AttrsJSON: []byte(`{}`),
  2411  			Status:    states.ObjectReady,
  2412  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2413  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
  2414  			AttrsJSON: []byte(`{}`),
  2415  			Status:    states.ObjectReady,
  2416  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2417  	})
  2418  
  2419  	p := simpleMockProvider()
  2420  	ctx := testContext2(t, &ContextOpts{
  2421  		Providers: map[addrs.Provider]providers.Factory{
  2422  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2423  		},
  2424  	})
  2425  
  2426  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2427  		Mode: plans.NormalMode,
  2428  		ForceReplace: []addrs.AbsResourceInstance{
  2429  			addrA,
  2430  		},
  2431  	})
  2432  	if diags.HasErrors() {
  2433  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2434  	}
  2435  
  2436  	t.Run(addrA.String(), func(t *testing.T) {
  2437  		instPlan := plan.Changes.ResourceInstance(addrA)
  2438  		if instPlan == nil {
  2439  			t.Fatalf("no plan for %s at all", addrA)
  2440  		}
  2441  
  2442  		if got, want := instPlan.Action, plans.DeleteThenCreate; got != want {
  2443  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2444  		}
  2445  		if got, want := instPlan.ActionReason, plans.ResourceInstanceReplaceByRequest; got != want {
  2446  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2447  		}
  2448  	})
  2449  	t.Run(addrB.String(), func(t *testing.T) {
  2450  		instPlan := plan.Changes.ResourceInstance(addrB)
  2451  		if instPlan == nil {
  2452  			t.Fatalf("no plan for %s at all", addrB)
  2453  		}
  2454  
  2455  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2456  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2457  		}
  2458  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2459  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2460  		}
  2461  	})
  2462  }
  2463  
  2464  func TestContext2Plan_forceReplaceIncompleteAddr(t *testing.T) {
  2465  	addr0 := mustResourceInstanceAddr("test_object.a[0]")
  2466  	addr1 := mustResourceInstanceAddr("test_object.a[1]")
  2467  	addrBare := mustResourceInstanceAddr("test_object.a")
  2468  	m := testModuleInline(t, map[string]string{
  2469  		"main.tf": `
  2470  			resource "test_object" "a" {
  2471  				count = 2
  2472  			}
  2473  		`,
  2474  	})
  2475  
  2476  	state := states.BuildState(func(s *states.SyncState) {
  2477  		s.SetResourceInstanceCurrent(addr0, &states.ResourceInstanceObjectSrc{
  2478  			AttrsJSON: []byte(`{}`),
  2479  			Status:    states.ObjectReady,
  2480  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2481  		s.SetResourceInstanceCurrent(addr1, &states.ResourceInstanceObjectSrc{
  2482  			AttrsJSON: []byte(`{}`),
  2483  			Status:    states.ObjectReady,
  2484  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2485  	})
  2486  
  2487  	p := simpleMockProvider()
  2488  	ctx := testContext2(t, &ContextOpts{
  2489  		Providers: map[addrs.Provider]providers.Factory{
  2490  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2491  		},
  2492  	})
  2493  
  2494  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2495  		Mode: plans.NormalMode,
  2496  		ForceReplace: []addrs.AbsResourceInstance{
  2497  			addrBare,
  2498  		},
  2499  	})
  2500  	if diags.HasErrors() {
  2501  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2502  	}
  2503  	diagsErr := diags.ErrWithWarnings()
  2504  	if diagsErr == nil {
  2505  		t.Fatalf("no warnings were returned")
  2506  	}
  2507  	if got, want := diagsErr.Error(), "Incompletely-matched force-replace resource instance"; !strings.Contains(got, want) {
  2508  		t.Errorf("missing expected warning\ngot:\n%s\n\nwant substring: %s", got, want)
  2509  	}
  2510  
  2511  	t.Run(addr0.String(), func(t *testing.T) {
  2512  		instPlan := plan.Changes.ResourceInstance(addr0)
  2513  		if instPlan == nil {
  2514  			t.Fatalf("no plan for %s at all", addr0)
  2515  		}
  2516  
  2517  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2518  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2519  		}
  2520  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2521  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2522  		}
  2523  	})
  2524  	t.Run(addr1.String(), func(t *testing.T) {
  2525  		instPlan := plan.Changes.ResourceInstance(addr1)
  2526  		if instPlan == nil {
  2527  			t.Fatalf("no plan for %s at all", addr1)
  2528  		}
  2529  
  2530  		if got, want := instPlan.Action, plans.NoOp; got != want {
  2531  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2532  		}
  2533  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2534  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2535  		}
  2536  	})
  2537  }
  2538  
  2539  // Verify that adding a module instance does force existing module data sources
  2540  // to be deferred
  2541  func TestContext2Plan_noChangeDataSourceAddingModuleInstance(t *testing.T) {
  2542  	m := testModuleInline(t, map[string]string{
  2543  		"main.tf": `
  2544  locals {
  2545    data = {
  2546      a = "a"
  2547      b = "b"
  2548    }
  2549  }
  2550  
  2551  module "one" {
  2552    source   = "./mod"
  2553    for_each = local.data
  2554    input = each.value
  2555  }
  2556  
  2557  module "two" {
  2558    source   = "./mod"
  2559    for_each = module.one
  2560    input = each.value.output
  2561  }
  2562  `,
  2563  		"mod/main.tf": `
  2564  variable "input" {
  2565  }
  2566  
  2567  resource "test_resource" "x" {
  2568    value = var.input
  2569  }
  2570  
  2571  data "test_data_source" "d" {
  2572    foo = test_resource.x.id
  2573  }
  2574  
  2575  output "output" {
  2576    value = test_resource.x.id
  2577  }
  2578  `,
  2579  	})
  2580  
  2581  	p := testProvider("test")
  2582  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  2583  		State: cty.ObjectVal(map[string]cty.Value{
  2584  			"id":  cty.StringVal("data"),
  2585  			"foo": cty.StringVal("foo"),
  2586  		}),
  2587  	}
  2588  	state := states.NewState()
  2589  	modOne := addrs.RootModuleInstance.Child("one", addrs.StringKey("a"))
  2590  	modTwo := addrs.RootModuleInstance.Child("two", addrs.StringKey("a"))
  2591  	one := state.EnsureModule(modOne)
  2592  	two := state.EnsureModule(modTwo)
  2593  	one.SetResourceInstanceCurrent(
  2594  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2595  		&states.ResourceInstanceObjectSrc{
  2596  			Status:    states.ObjectReady,
  2597  			AttrsJSON: []byte(`{"id":"foo","value":"a"}`),
  2598  		},
  2599  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2600  	)
  2601  	one.SetResourceInstanceCurrent(
  2602  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2603  		&states.ResourceInstanceObjectSrc{
  2604  			Status:    states.ObjectReady,
  2605  			AttrsJSON: []byte(`{"id":"data"}`),
  2606  		},
  2607  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2608  	)
  2609  	two.SetResourceInstanceCurrent(
  2610  		mustResourceInstanceAddr(`test_resource.x`).Resource,
  2611  		&states.ResourceInstanceObjectSrc{
  2612  			Status:    states.ObjectReady,
  2613  			AttrsJSON: []byte(`{"id":"foo","value":"foo"}`),
  2614  		},
  2615  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2616  	)
  2617  	two.SetResourceInstanceCurrent(
  2618  		mustResourceInstanceAddr(`data.test_data_source.d`).Resource,
  2619  		&states.ResourceInstanceObjectSrc{
  2620  			Status:    states.ObjectReady,
  2621  			AttrsJSON: []byte(`{"id":"data"}`),
  2622  		},
  2623  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  2624  	)
  2625  
  2626  	ctx := testContext2(t, &ContextOpts{
  2627  		Providers: map[addrs.Provider]providers.Factory{
  2628  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2629  		},
  2630  	})
  2631  
  2632  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2633  	assertNoErrors(t, diags)
  2634  
  2635  	for _, res := range plan.Changes.Resources {
  2636  		// both existing data sources should be read during plan
  2637  		if res.Addr.Module[0].InstanceKey == addrs.StringKey("b") {
  2638  			continue
  2639  		}
  2640  
  2641  		if res.Addr.Resource.Resource.Mode == addrs.DataResourceMode && res.Action != plans.NoOp {
  2642  			t.Errorf("unexpected %s plan for %s", res.Action, res.Addr)
  2643  		}
  2644  	}
  2645  }
  2646  
  2647  func TestContext2Plan_moduleExpandOrphansResourceInstance(t *testing.T) {
  2648  	// This test deals with the situation where a user has changed the
  2649  	// repetition/expansion mode for a module call while there are already
  2650  	// resource instances from the previous declaration in the state.
  2651  	//
  2652  	// This is conceptually just the same as removing the resources
  2653  	// from the module configuration only for that instance, but the
  2654  	// implementation of it ends up a little different because it's
  2655  	// an entry in the resource address's _module path_ that we'll find
  2656  	// missing, rather than the resource's own instance key, and so
  2657  	// our analyses need to handle that situation by indicating that all
  2658  	// of the resources under the missing module instance have zero
  2659  	// instances, regardless of which resource in that module we might
  2660  	// be asking about, and do so without tripping over any missing
  2661  	// registrations in the instance expander that might lead to panics
  2662  	// if we aren't careful.
  2663  	//
  2664  	// (For some history here, see https://github.com/hashicorp/terraform/issues/30110 )
  2665  
  2666  	addrNoKey := mustResourceInstanceAddr("module.child.test_object.a[0]")
  2667  	addrZeroKey := mustResourceInstanceAddr("module.child[0].test_object.a[0]")
  2668  	m := testModuleInline(t, map[string]string{
  2669  		"main.tf": `
  2670  			module "child" {
  2671  				source = "./child"
  2672  				count = 1
  2673  			}
  2674  		`,
  2675  		"child/main.tf": `
  2676  			resource "test_object" "a" {
  2677  				count = 1
  2678  			}
  2679  		`,
  2680  	})
  2681  
  2682  	state := states.BuildState(func(s *states.SyncState) {
  2683  		// Notice that addrNoKey is the address which lacks any instance key
  2684  		// for module.child, and so that module instance doesn't match the
  2685  		// call declared above with count = 1, and therefore the resource
  2686  		// inside is "orphaned" even though the resource block actually
  2687  		// still exists there.
  2688  		s.SetResourceInstanceCurrent(addrNoKey, &states.ResourceInstanceObjectSrc{
  2689  			AttrsJSON: []byte(`{}`),
  2690  			Status:    states.ObjectReady,
  2691  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2692  	})
  2693  
  2694  	p := simpleMockProvider()
  2695  	ctx := testContext2(t, &ContextOpts{
  2696  		Providers: map[addrs.Provider]providers.Factory{
  2697  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2698  		},
  2699  	})
  2700  
  2701  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  2702  		Mode: plans.NormalMode,
  2703  	})
  2704  	if diags.HasErrors() {
  2705  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  2706  	}
  2707  
  2708  	t.Run(addrNoKey.String(), func(t *testing.T) {
  2709  		instPlan := plan.Changes.ResourceInstance(addrNoKey)
  2710  		if instPlan == nil {
  2711  			t.Fatalf("no plan for %s at all", addrNoKey)
  2712  		}
  2713  
  2714  		if got, want := instPlan.Addr, addrNoKey; !got.Equal(want) {
  2715  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  2716  		}
  2717  		if got, want := instPlan.PrevRunAddr, addrNoKey; !got.Equal(want) {
  2718  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  2719  		}
  2720  		if got, want := instPlan.Action, plans.Delete; got != want {
  2721  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2722  		}
  2723  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoModule; got != want {
  2724  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2725  		}
  2726  	})
  2727  
  2728  	t.Run(addrZeroKey.String(), func(t *testing.T) {
  2729  		instPlan := plan.Changes.ResourceInstance(addrZeroKey)
  2730  		if instPlan == nil {
  2731  			t.Fatalf("no plan for %s at all", addrZeroKey)
  2732  		}
  2733  
  2734  		if got, want := instPlan.Addr, addrZeroKey; !got.Equal(want) {
  2735  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  2736  		}
  2737  		if got, want := instPlan.PrevRunAddr, addrZeroKey; !got.Equal(want) {
  2738  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  2739  		}
  2740  		if got, want := instPlan.Action, plans.Create; got != want {
  2741  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  2742  		}
  2743  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  2744  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  2745  		}
  2746  	})
  2747  }
  2748  
  2749  func TestContext2Plan_resourcePreconditionPostcondition(t *testing.T) {
  2750  	m := testModuleInline(t, map[string]string{
  2751  		"main.tf": `
  2752  variable "boop" {
  2753    type = string
  2754  }
  2755  
  2756  resource "test_resource" "a" {
  2757    value = var.boop
  2758    lifecycle {
  2759      precondition {
  2760        condition     = var.boop == "boop"
  2761        error_message = "Wrong boop."
  2762      }
  2763      postcondition {
  2764        condition     = self.output != ""
  2765        error_message = "Output must not be blank."
  2766      }
  2767    }
  2768  }
  2769  
  2770  `,
  2771  	})
  2772  
  2773  	p := testProvider("test")
  2774  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2775  		ResourceTypes: map[string]*configschema.Block{
  2776  			"test_resource": {
  2777  				Attributes: map[string]*configschema.Attribute{
  2778  					"value": {
  2779  						Type:     cty.String,
  2780  						Required: true,
  2781  					},
  2782  					"output": {
  2783  						Type:     cty.String,
  2784  						Computed: true,
  2785  					},
  2786  				},
  2787  			},
  2788  		},
  2789  	})
  2790  
  2791  	t.Run("conditions pass", func(t *testing.T) {
  2792  		ctx := testContext2(t, &ContextOpts{
  2793  			Providers: map[addrs.Provider]providers.Factory{
  2794  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2795  			},
  2796  		})
  2797  
  2798  		p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2799  			m := req.ProposedNewState.AsValueMap()
  2800  			m["output"] = cty.StringVal("bar")
  2801  
  2802  			resp.PlannedState = cty.ObjectVal(m)
  2803  			resp.LegacyTypeSystem = true
  2804  			return resp
  2805  		}
  2806  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2807  			Mode: plans.NormalMode,
  2808  			SetVariables: InputValues{
  2809  				"boop": &InputValue{
  2810  					Value:      cty.StringVal("boop"),
  2811  					SourceType: ValueFromCLIArg,
  2812  				},
  2813  			},
  2814  		})
  2815  		assertNoErrors(t, diags)
  2816  		for _, res := range plan.Changes.Resources {
  2817  			switch res.Addr.String() {
  2818  			case "test_resource.a":
  2819  				if res.Action != plans.Create {
  2820  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2821  				}
  2822  			default:
  2823  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  2824  			}
  2825  		}
  2826  	})
  2827  
  2828  	t.Run("precondition fail", func(t *testing.T) {
  2829  		ctx := testContext2(t, &ContextOpts{
  2830  			Providers: map[addrs.Provider]providers.Factory{
  2831  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2832  			},
  2833  		})
  2834  
  2835  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2836  			Mode: plans.NormalMode,
  2837  			SetVariables: InputValues{
  2838  				"boop": &InputValue{
  2839  					Value:      cty.StringVal("nope"),
  2840  					SourceType: ValueFromCLIArg,
  2841  				},
  2842  			},
  2843  		})
  2844  		if !diags.HasErrors() {
  2845  			t.Fatal("succeeded; want errors")
  2846  		}
  2847  		if got, want := diags.Err().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2848  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  2849  		}
  2850  		if p.PlanResourceChangeCalled {
  2851  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  2852  		}
  2853  	})
  2854  
  2855  	t.Run("precondition fail refresh-only", func(t *testing.T) {
  2856  		ctx := testContext2(t, &ContextOpts{
  2857  			Providers: map[addrs.Provider]providers.Factory{
  2858  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2859  			},
  2860  		})
  2861  
  2862  		state := states.BuildState(func(s *states.SyncState) {
  2863  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2864  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2865  				Status:    states.ObjectReady,
  2866  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2867  		})
  2868  		_, diags := ctx.Plan(m, state, &PlanOpts{
  2869  			Mode: plans.RefreshOnlyMode,
  2870  			SetVariables: InputValues{
  2871  				"boop": &InputValue{
  2872  					Value:      cty.StringVal("nope"),
  2873  					SourceType: ValueFromCLIArg,
  2874  				},
  2875  			},
  2876  		})
  2877  		assertNoErrors(t, diags)
  2878  		if len(diags) == 0 {
  2879  			t.Fatalf("no diags, but should have warnings")
  2880  		}
  2881  		if got, want := diags.ErrWithWarnings().Error(), "Resource precondition failed: Wrong boop."; got != want {
  2882  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  2883  		}
  2884  		if !p.ReadResourceCalled {
  2885  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  2886  		}
  2887  	})
  2888  
  2889  	t.Run("postcondition fail", func(t *testing.T) {
  2890  		ctx := testContext2(t, &ContextOpts{
  2891  			Providers: map[addrs.Provider]providers.Factory{
  2892  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2893  			},
  2894  		})
  2895  
  2896  		p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  2897  			m := req.ProposedNewState.AsValueMap()
  2898  			m["output"] = cty.StringVal("")
  2899  
  2900  			resp.PlannedState = cty.ObjectVal(m)
  2901  			resp.LegacyTypeSystem = true
  2902  			return resp
  2903  		}
  2904  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2905  			Mode: plans.NormalMode,
  2906  			SetVariables: InputValues{
  2907  				"boop": &InputValue{
  2908  					Value:      cty.StringVal("boop"),
  2909  					SourceType: ValueFromCLIArg,
  2910  				},
  2911  			},
  2912  		})
  2913  		if !diags.HasErrors() {
  2914  			t.Fatal("succeeded; want errors")
  2915  		}
  2916  		if got, want := diags.Err().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
  2917  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  2918  		}
  2919  		if !p.PlanResourceChangeCalled {
  2920  			t.Errorf("Provider's PlanResourceChange wasn't called; should've been")
  2921  		}
  2922  	})
  2923  
  2924  	t.Run("postcondition fail refresh-only", func(t *testing.T) {
  2925  		ctx := testContext2(t, &ContextOpts{
  2926  			Providers: map[addrs.Provider]providers.Factory{
  2927  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2928  			},
  2929  		})
  2930  
  2931  		state := states.BuildState(func(s *states.SyncState) {
  2932  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2933  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2934  				Status:    states.ObjectReady,
  2935  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2936  		})
  2937  		p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  2938  			newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2939  				if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "output"}) {
  2940  					return cty.StringVal(""), nil
  2941  				}
  2942  				return v, nil
  2943  			})
  2944  			if err != nil {
  2945  				// shouldn't get here
  2946  				t.Fatalf("ReadResourceFn transform failed")
  2947  				return providers.ReadResourceResponse{}
  2948  			}
  2949  			return providers.ReadResourceResponse{
  2950  				NewState: newVal,
  2951  			}
  2952  		}
  2953  		_, diags := ctx.Plan(m, state, &PlanOpts{
  2954  			Mode: plans.RefreshOnlyMode,
  2955  			SetVariables: InputValues{
  2956  				"boop": &InputValue{
  2957  					Value:      cty.StringVal("boop"),
  2958  					SourceType: ValueFromCLIArg,
  2959  				},
  2960  			},
  2961  		})
  2962  		assertNoErrors(t, diags)
  2963  		if len(diags) == 0 {
  2964  			t.Fatalf("no diags, but should have warnings")
  2965  		}
  2966  		if got, want := diags.ErrWithWarnings().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
  2967  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  2968  		}
  2969  		if !p.ReadResourceCalled {
  2970  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  2971  		}
  2972  		if p.PlanResourceChangeCalled {
  2973  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  2974  		}
  2975  	})
  2976  
  2977  	t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) {
  2978  		ctx := testContext2(t, &ContextOpts{
  2979  			Providers: map[addrs.Provider]providers.Factory{
  2980  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2981  			},
  2982  		})
  2983  
  2984  		state := states.BuildState(func(s *states.SyncState) {
  2985  			s.SetResourceInstanceCurrent(mustResourceInstanceAddr("test_resource.a"), &states.ResourceInstanceObjectSrc{
  2986  				AttrsJSON: []byte(`{"value":"boop","output":"blorp"}`),
  2987  				Status:    states.ObjectReady,
  2988  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  2989  		})
  2990  		p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  2991  			newVal, err := cty.Transform(req.PriorState, func(path cty.Path, v cty.Value) (cty.Value, error) {
  2992  				if len(path) == 1 && path[0] == (cty.GetAttrStep{Name: "output"}) {
  2993  					return cty.StringVal(""), nil
  2994  				}
  2995  				return v, nil
  2996  			})
  2997  			if err != nil {
  2998  				// shouldn't get here
  2999  				t.Fatalf("ReadResourceFn transform failed")
  3000  				return providers.ReadResourceResponse{}
  3001  			}
  3002  			return providers.ReadResourceResponse{
  3003  				NewState: newVal,
  3004  			}
  3005  		}
  3006  		_, diags := ctx.Plan(m, state, &PlanOpts{
  3007  			Mode: plans.RefreshOnlyMode,
  3008  			SetVariables: InputValues{
  3009  				"boop": &InputValue{
  3010  					Value:      cty.StringVal("nope"),
  3011  					SourceType: ValueFromCLIArg,
  3012  				},
  3013  			},
  3014  		})
  3015  		assertNoErrors(t, diags)
  3016  		if got, want := len(diags), 2; got != want {
  3017  			t.Errorf("wrong number of warnings, got %d, want %d", got, want)
  3018  		}
  3019  		warnings := diags.ErrWithWarnings().Error()
  3020  		wantWarnings := []string{
  3021  			"Resource precondition failed: Wrong boop.",
  3022  			"Resource postcondition failed: Output must not be blank.",
  3023  		}
  3024  		for _, want := range wantWarnings {
  3025  			if !strings.Contains(warnings, want) {
  3026  				t.Errorf("missing warning:\ngot:  %s\nwant to contain: %q", warnings, want)
  3027  			}
  3028  		}
  3029  		if !p.ReadResourceCalled {
  3030  			t.Errorf("Provider's ReadResource wasn't called; should've been")
  3031  		}
  3032  		if p.PlanResourceChangeCalled {
  3033  			t.Errorf("Provider's PlanResourceChange was called; should'nt've been")
  3034  		}
  3035  	})
  3036  }
  3037  
  3038  func TestContext2Plan_dataSourcePreconditionPostcondition(t *testing.T) {
  3039  	m := testModuleInline(t, map[string]string{
  3040  		"main.tf": `
  3041  variable "boop" {
  3042    type = string
  3043  }
  3044  
  3045  data "test_data_source" "a" {
  3046    foo = var.boop
  3047    lifecycle {
  3048      precondition {
  3049        condition     = var.boop == "boop"
  3050        error_message = "Wrong boop."
  3051      }
  3052      postcondition {
  3053        condition     = length(self.results) > 0
  3054        error_message = "Results cannot be empty."
  3055      }
  3056    }
  3057  }
  3058  
  3059  resource "test_resource" "a" {
  3060    value    = data.test_data_source.a.results[0]
  3061  }
  3062  `,
  3063  	})
  3064  
  3065  	p := testProvider("test")
  3066  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  3067  		ResourceTypes: map[string]*configschema.Block{
  3068  			"test_resource": {
  3069  				Attributes: map[string]*configschema.Attribute{
  3070  					"value": {
  3071  						Type:     cty.String,
  3072  						Required: true,
  3073  					},
  3074  				},
  3075  			},
  3076  		},
  3077  		DataSources: map[string]*configschema.Block{
  3078  			"test_data_source": {
  3079  				Attributes: map[string]*configschema.Attribute{
  3080  					"foo": {
  3081  						Type:     cty.String,
  3082  						Required: true,
  3083  					},
  3084  					"results": {
  3085  						Type:     cty.List(cty.String),
  3086  						Computed: true,
  3087  					},
  3088  				},
  3089  			},
  3090  		},
  3091  	})
  3092  
  3093  	t.Run("conditions pass", func(t *testing.T) {
  3094  		ctx := testContext2(t, &ContextOpts{
  3095  			Providers: map[addrs.Provider]providers.Factory{
  3096  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3097  			},
  3098  		})
  3099  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3100  			State: cty.ObjectVal(map[string]cty.Value{
  3101  				"foo":     cty.StringVal("boop"),
  3102  				"results": cty.ListVal([]cty.Value{cty.StringVal("boop")}),
  3103  			}),
  3104  		}
  3105  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3106  			Mode: plans.NormalMode,
  3107  			SetVariables: InputValues{
  3108  				"boop": &InputValue{
  3109  					Value:      cty.StringVal("boop"),
  3110  					SourceType: ValueFromCLIArg,
  3111  				},
  3112  			},
  3113  		})
  3114  		assertNoErrors(t, diags)
  3115  		for _, res := range plan.Changes.Resources {
  3116  			switch res.Addr.String() {
  3117  			case "test_resource.a":
  3118  				if res.Action != plans.Create {
  3119  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3120  				}
  3121  			case "data.test_data_source.a":
  3122  				if res.Action != plans.Read {
  3123  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3124  				}
  3125  			default:
  3126  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3127  			}
  3128  		}
  3129  
  3130  		addr := mustResourceInstanceAddr("data.test_data_source.a")
  3131  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3132  			t.Errorf("no check result for %s", addr)
  3133  		} else {
  3134  			wantResult := &states.CheckResultObject{
  3135  				Status: checks.StatusPass,
  3136  			}
  3137  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3138  				t.Errorf("wrong check result for %s\n%s", addr, diff)
  3139  			}
  3140  		}
  3141  	})
  3142  
  3143  	t.Run("precondition fail", func(t *testing.T) {
  3144  		ctx := testContext2(t, &ContextOpts{
  3145  			Providers: map[addrs.Provider]providers.Factory{
  3146  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3147  			},
  3148  		})
  3149  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3150  			Mode: plans.NormalMode,
  3151  			SetVariables: InputValues{
  3152  				"boop": &InputValue{
  3153  					Value:      cty.StringVal("nope"),
  3154  					SourceType: ValueFromCLIArg,
  3155  				},
  3156  			},
  3157  		})
  3158  		if !diags.HasErrors() {
  3159  			t.Fatal("succeeded; want errors")
  3160  		}
  3161  		if got, want := diags.Err().Error(), "Resource precondition failed: Wrong boop."; got != want {
  3162  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3163  		}
  3164  		if p.ReadDataSourceCalled {
  3165  			t.Errorf("Provider's ReadResource was called; should'nt've been")
  3166  		}
  3167  	})
  3168  
  3169  	t.Run("precondition fail refresh-only", func(t *testing.T) {
  3170  		ctx := testContext2(t, &ContextOpts{
  3171  			Providers: map[addrs.Provider]providers.Factory{
  3172  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3173  			},
  3174  		})
  3175  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3176  			Mode: plans.RefreshOnlyMode,
  3177  			SetVariables: InputValues{
  3178  				"boop": &InputValue{
  3179  					Value:      cty.StringVal("nope"),
  3180  					SourceType: ValueFromCLIArg,
  3181  				},
  3182  			},
  3183  		})
  3184  		assertNoErrors(t, diags)
  3185  		if len(diags) == 0 {
  3186  			t.Fatalf("no diags, but should have warnings")
  3187  		}
  3188  		if got, want := diags.ErrWithWarnings().Error(), "Resource precondition failed: Wrong boop."; got != want {
  3189  			t.Fatalf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  3190  		}
  3191  		for _, res := range plan.Changes.Resources {
  3192  			switch res.Addr.String() {
  3193  			case "test_resource.a":
  3194  				if res.Action != plans.Create {
  3195  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3196  				}
  3197  			case "data.test_data_source.a":
  3198  				if res.Action != plans.Read {
  3199  					t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3200  				}
  3201  			default:
  3202  				t.Fatalf("unexpected %s change for %s", res.Action, res.Addr)
  3203  			}
  3204  		}
  3205  	})
  3206  
  3207  	t.Run("postcondition fail", func(t *testing.T) {
  3208  		ctx := testContext2(t, &ContextOpts{
  3209  			Providers: map[addrs.Provider]providers.Factory{
  3210  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3211  			},
  3212  		})
  3213  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3214  			State: cty.ObjectVal(map[string]cty.Value{
  3215  				"foo":     cty.StringVal("boop"),
  3216  				"results": cty.ListValEmpty(cty.String),
  3217  			}),
  3218  		}
  3219  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3220  			Mode: plans.NormalMode,
  3221  			SetVariables: InputValues{
  3222  				"boop": &InputValue{
  3223  					Value:      cty.StringVal("boop"),
  3224  					SourceType: ValueFromCLIArg,
  3225  				},
  3226  			},
  3227  		})
  3228  		if !diags.HasErrors() {
  3229  			t.Fatal("succeeded; want errors")
  3230  		}
  3231  		if got, want := diags.Err().Error(), "Resource postcondition failed: Results cannot be empty."; got != want {
  3232  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3233  		}
  3234  		if !p.ReadDataSourceCalled {
  3235  			t.Errorf("Provider's ReadDataSource wasn't called; should've been")
  3236  		}
  3237  	})
  3238  
  3239  	t.Run("postcondition fail refresh-only", func(t *testing.T) {
  3240  		ctx := testContext2(t, &ContextOpts{
  3241  			Providers: map[addrs.Provider]providers.Factory{
  3242  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3243  			},
  3244  		})
  3245  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3246  			State: cty.ObjectVal(map[string]cty.Value{
  3247  				"foo":     cty.StringVal("boop"),
  3248  				"results": cty.ListValEmpty(cty.String),
  3249  			}),
  3250  		}
  3251  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3252  			Mode: plans.RefreshOnlyMode,
  3253  			SetVariables: InputValues{
  3254  				"boop": &InputValue{
  3255  					Value:      cty.StringVal("boop"),
  3256  					SourceType: ValueFromCLIArg,
  3257  				},
  3258  			},
  3259  		})
  3260  		assertNoErrors(t, diags)
  3261  		if got, want := diags.ErrWithWarnings().Error(), "Resource postcondition failed: Results cannot be empty."; got != want {
  3262  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3263  		}
  3264  		addr := mustResourceInstanceAddr("data.test_data_source.a")
  3265  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3266  			t.Errorf("no check result for %s", addr)
  3267  		} else {
  3268  			wantResult := &states.CheckResultObject{
  3269  				Status: checks.StatusFail,
  3270  				FailureMessages: []string{
  3271  					"Results cannot be empty.",
  3272  				},
  3273  			}
  3274  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3275  				t.Errorf("wrong check result\n%s", diff)
  3276  			}
  3277  		}
  3278  	})
  3279  
  3280  	t.Run("precondition and postcondition fail refresh-only", func(t *testing.T) {
  3281  		ctx := testContext2(t, &ContextOpts{
  3282  			Providers: map[addrs.Provider]providers.Factory{
  3283  				addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3284  			},
  3285  		})
  3286  		p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  3287  			State: cty.ObjectVal(map[string]cty.Value{
  3288  				"foo":     cty.StringVal("nope"),
  3289  				"results": cty.ListValEmpty(cty.String),
  3290  			}),
  3291  		}
  3292  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3293  			Mode: plans.RefreshOnlyMode,
  3294  			SetVariables: InputValues{
  3295  				"boop": &InputValue{
  3296  					Value:      cty.StringVal("nope"),
  3297  					SourceType: ValueFromCLIArg,
  3298  				},
  3299  			},
  3300  		})
  3301  		assertNoErrors(t, diags)
  3302  		if got, want := len(diags), 2; got != want {
  3303  			t.Errorf("wrong number of warnings, got %d, want %d", got, want)
  3304  		}
  3305  		warnings := diags.ErrWithWarnings().Error()
  3306  		wantWarnings := []string{
  3307  			"Resource precondition failed: Wrong boop.",
  3308  			"Resource postcondition failed: Results cannot be empty.",
  3309  		}
  3310  		for _, want := range wantWarnings {
  3311  			if !strings.Contains(warnings, want) {
  3312  				t.Errorf("missing warning:\ngot:  %s\nwant to contain: %q", warnings, want)
  3313  			}
  3314  		}
  3315  	})
  3316  }
  3317  
  3318  func TestContext2Plan_outputPrecondition(t *testing.T) {
  3319  	m := testModuleInline(t, map[string]string{
  3320  		"main.tf": `
  3321  variable "boop" {
  3322    type = string
  3323  }
  3324  
  3325  output "a" {
  3326    value = var.boop
  3327    precondition {
  3328      condition     = var.boop == "boop"
  3329      error_message = "Wrong boop."
  3330    }
  3331  }
  3332  `,
  3333  	})
  3334  
  3335  	p := testProvider("test")
  3336  
  3337  	ctx := testContext2(t, &ContextOpts{
  3338  		Providers: map[addrs.Provider]providers.Factory{
  3339  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3340  		},
  3341  	})
  3342  
  3343  	t.Run("condition pass", func(t *testing.T) {
  3344  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3345  			Mode: plans.NormalMode,
  3346  			SetVariables: InputValues{
  3347  				"boop": &InputValue{
  3348  					Value:      cty.StringVal("boop"),
  3349  					SourceType: ValueFromCLIArg,
  3350  				},
  3351  			},
  3352  		})
  3353  		assertNoErrors(t, diags)
  3354  		addr := addrs.RootModuleInstance.OutputValue("a")
  3355  		outputPlan := plan.Changes.OutputValue(addr)
  3356  		if outputPlan == nil {
  3357  			t.Fatalf("no plan for %s at all", addr)
  3358  		}
  3359  		if got, want := outputPlan.Addr, addr; !got.Equal(want) {
  3360  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  3361  		}
  3362  		if got, want := outputPlan.Action, plans.Create; got != want {
  3363  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  3364  		}
  3365  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3366  			t.Errorf("no check result for %s", addr)
  3367  		} else {
  3368  			wantResult := &states.CheckResultObject{
  3369  				Status: checks.StatusPass,
  3370  			}
  3371  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3372  				t.Errorf("wrong check result\n%s", diff)
  3373  			}
  3374  		}
  3375  	})
  3376  
  3377  	t.Run("condition fail", func(t *testing.T) {
  3378  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3379  			Mode: plans.NormalMode,
  3380  			SetVariables: InputValues{
  3381  				"boop": &InputValue{
  3382  					Value:      cty.StringVal("nope"),
  3383  					SourceType: ValueFromCLIArg,
  3384  				},
  3385  			},
  3386  		})
  3387  		if !diags.HasErrors() {
  3388  			t.Fatal("succeeded; want errors")
  3389  		}
  3390  		if got, want := diags.Err().Error(), "Module output value precondition failed: Wrong boop."; got != want {
  3391  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
  3392  		}
  3393  	})
  3394  
  3395  	t.Run("condition fail refresh-only", func(t *testing.T) {
  3396  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3397  			Mode: plans.RefreshOnlyMode,
  3398  			SetVariables: InputValues{
  3399  				"boop": &InputValue{
  3400  					Value:      cty.StringVal("nope"),
  3401  					SourceType: ValueFromCLIArg,
  3402  				},
  3403  			},
  3404  		})
  3405  		assertNoErrors(t, diags)
  3406  		if len(diags) == 0 {
  3407  			t.Fatalf("no diags, but should have warnings")
  3408  		}
  3409  		if got, want := diags.ErrWithWarnings().Error(), "Module output value precondition failed: Wrong boop."; got != want {
  3410  			t.Errorf("wrong warning:\ngot:  %s\nwant: %q", got, want)
  3411  		}
  3412  		addr := addrs.RootModuleInstance.OutputValue("a")
  3413  		outputPlan := plan.Changes.OutputValue(addr)
  3414  		if outputPlan == nil {
  3415  			t.Fatalf("no plan for %s at all", addr)
  3416  		}
  3417  		if got, want := outputPlan.Addr, addr; !got.Equal(want) {
  3418  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  3419  		}
  3420  		if got, want := outputPlan.Action, plans.Create; got != want {
  3421  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  3422  		}
  3423  		if gotResult := plan.Checks.GetObjectResult(addr); gotResult == nil {
  3424  			t.Errorf("no condition result for %s", addr)
  3425  		} else {
  3426  			wantResult := &states.CheckResultObject{
  3427  				Status:          checks.StatusFail,
  3428  				FailureMessages: []string{"Wrong boop."},
  3429  			}
  3430  			if diff := cmp.Diff(wantResult, gotResult, valueComparer); diff != "" {
  3431  				t.Errorf("wrong condition result\n%s", diff)
  3432  			}
  3433  		}
  3434  	})
  3435  }
  3436  
  3437  func TestContext2Plan_preconditionErrors(t *testing.T) {
  3438  	testCases := []struct {
  3439  		condition   string
  3440  		wantSummary string
  3441  		wantDetail  string
  3442  	}{
  3443  		{
  3444  			"data.test_data_source",
  3445  			"Invalid reference",
  3446  			`The "data" object must be followed by two attribute names`,
  3447  		},
  3448  		{
  3449  			"self.value",
  3450  			`Invalid "self" reference`,
  3451  			"only in resource provisioner, connection, and postcondition blocks",
  3452  		},
  3453  		{
  3454  			"data.foo.bar",
  3455  			"Reference to undeclared resource",
  3456  			`A data resource "foo" "bar" has not been declared in the root module`,
  3457  		},
  3458  		{
  3459  			"test_resource.b.value",
  3460  			"Invalid condition result",
  3461  			"Condition expression must return either true or false",
  3462  		},
  3463  		{
  3464  			"test_resource.c.value",
  3465  			"Invalid condition result",
  3466  			"Invalid condition result value: a bool is required",
  3467  		},
  3468  	}
  3469  
  3470  	p := testProvider("test")
  3471  	ctx := testContext2(t, &ContextOpts{
  3472  		Providers: map[addrs.Provider]providers.Factory{
  3473  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3474  		},
  3475  	})
  3476  
  3477  	for _, tc := range testCases {
  3478  		t.Run(tc.condition, func(t *testing.T) {
  3479  			main := fmt.Sprintf(`
  3480  			resource "test_resource" "a" {
  3481  				value = var.boop
  3482  				lifecycle {
  3483  					precondition {
  3484  						condition     = %s
  3485  						error_message = "Not relevant."
  3486  					}
  3487  				}
  3488  			}
  3489  
  3490  			resource "test_resource" "b" {
  3491  				value = null
  3492  			}
  3493  
  3494  			resource "test_resource" "c" {
  3495  				value = "bar"
  3496  			}
  3497  			`, tc.condition)
  3498  			m := testModuleInline(t, map[string]string{"main.tf": main})
  3499  
  3500  			plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  3501  			if !diags.HasErrors() {
  3502  				t.Fatal("succeeded; want errors")
  3503  			}
  3504  
  3505  			if !plan.Errored {
  3506  				t.Fatal("plan failed to record error")
  3507  			}
  3508  
  3509  			diag := diags[0]
  3510  			if got, want := diag.Description().Summary, tc.wantSummary; got != want {
  3511  				t.Errorf("unexpected summary\n got: %s\nwant: %s", got, want)
  3512  			}
  3513  			if got, want := diag.Description().Detail, tc.wantDetail; !strings.Contains(got, want) {
  3514  				t.Errorf("unexpected summary\ngot: %s\nwant to contain %q", got, want)
  3515  			}
  3516  
  3517  			for _, kv := range plan.Checks.ConfigResults.Elements() {
  3518  				// All these are configuration or evaluation errors
  3519  				if kv.Value.Status != checks.StatusError {
  3520  					t.Errorf("incorrect status, got %s", kv.Value.Status)
  3521  				}
  3522  			}
  3523  		})
  3524  	}
  3525  }
  3526  
  3527  func TestContext2Plan_preconditionSensitiveValues(t *testing.T) {
  3528  	p := testProvider("test")
  3529  	ctx := testContext2(t, &ContextOpts{
  3530  		Providers: map[addrs.Provider]providers.Factory{
  3531  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3532  		},
  3533  	})
  3534  
  3535  	m := testModuleInline(t, map[string]string{
  3536  		"main.tf": `
  3537  variable "boop" {
  3538    sensitive = true
  3539    type      = string
  3540  }
  3541  
  3542  output "a" {
  3543    sensitive = true
  3544    value     = var.boop
  3545  
  3546    precondition {
  3547      condition     = length(var.boop) <= 4
  3548      error_message = "Boop is too long, ${length(var.boop)} > 4"
  3549    }
  3550  }
  3551  `,
  3552  	})
  3553  
  3554  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  3555  		Mode: plans.NormalMode,
  3556  		SetVariables: InputValues{
  3557  			"boop": &InputValue{
  3558  				Value:      cty.StringVal("bleep"),
  3559  				SourceType: ValueFromCLIArg,
  3560  			},
  3561  		},
  3562  	})
  3563  	if !diags.HasErrors() {
  3564  		t.Fatal("succeeded; want errors")
  3565  	}
  3566  	if got, want := len(diags), 2; got != want {
  3567  		t.Errorf("wrong number of diags, got %d, want %d", got, want)
  3568  	}
  3569  	for _, diag := range diags {
  3570  		desc := diag.Description()
  3571  		if desc.Summary == "Module output value precondition failed" {
  3572  			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) {
  3573  				t.Errorf("unexpected detail\ngot: %s\nwant to contain %q", got, want)
  3574  			}
  3575  		} else if desc.Summary == "Error message refers to sensitive values" {
  3576  			if got, want := desc.Detail, "The error expression used to explain this condition refers to sensitive values, so OpenTofu will not display the resulting message."; !strings.Contains(got, want) {
  3577  				t.Errorf("unexpected detail\ngot: %s\nwant to contain %q", got, want)
  3578  			}
  3579  		} else {
  3580  			t.Errorf("unexpected summary\ngot: %s", desc.Summary)
  3581  		}
  3582  	}
  3583  }
  3584  
  3585  func TestContext2Plan_triggeredBy(t *testing.T) {
  3586  	m := testModuleInline(t, map[string]string{
  3587  		"main.tf": `
  3588  resource "test_object" "a" {
  3589    count = 1
  3590    test_string = "new"
  3591  }
  3592  resource "test_object" "b" {
  3593    count = 1
  3594    test_string = test_object.a[count.index].test_string
  3595    lifecycle {
  3596      # the change to test_string in the other resource should trigger replacement
  3597      replace_triggered_by = [ test_object.a[count.index].test_string ]
  3598    }
  3599  }
  3600  `,
  3601  	})
  3602  
  3603  	p := simpleMockProvider()
  3604  
  3605  	ctx := testContext2(t, &ContextOpts{
  3606  		Providers: map[addrs.Provider]providers.Factory{
  3607  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3608  		},
  3609  	})
  3610  
  3611  	state := states.BuildState(func(s *states.SyncState) {
  3612  		s.SetResourceInstanceCurrent(
  3613  			mustResourceInstanceAddr("test_object.a[0]"),
  3614  			&states.ResourceInstanceObjectSrc{
  3615  				AttrsJSON: []byte(`{"test_string":"old"}`),
  3616  				Status:    states.ObjectReady,
  3617  			},
  3618  			mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3619  		)
  3620  		s.SetResourceInstanceCurrent(
  3621  			mustResourceInstanceAddr("test_object.b[0]"),
  3622  			&states.ResourceInstanceObjectSrc{
  3623  				AttrsJSON: []byte(`{}`),
  3624  				Status:    states.ObjectReady,
  3625  			},
  3626  			mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3627  		)
  3628  	})
  3629  
  3630  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  3631  		Mode: plans.NormalMode,
  3632  	})
  3633  	if diags.HasErrors() {
  3634  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  3635  	}
  3636  	for _, c := range plan.Changes.Resources {
  3637  		switch c.Addr.String() {
  3638  		case "test_object.a[0]":
  3639  			if c.Action != plans.Update {
  3640  				t.Fatalf("unexpected %s change for %s\n", c.Action, c.Addr)
  3641  			}
  3642  		case "test_object.b[0]":
  3643  			if c.Action != plans.DeleteThenCreate {
  3644  				t.Fatalf("unexpected %s change for %s\n", c.Action, c.Addr)
  3645  			}
  3646  			if c.ActionReason != plans.ResourceInstanceReplaceByTriggers {
  3647  				t.Fatalf("incorrect reason for change: %s\n", c.ActionReason)
  3648  			}
  3649  		default:
  3650  			t.Fatal("unexpected change", c.Addr, c.Action)
  3651  		}
  3652  	}
  3653  }
  3654  
  3655  func TestContext2Plan_dataSchemaChange(t *testing.T) {
  3656  	// We can't decode the prior state when a data source upgrades the schema
  3657  	// in an incompatible way. Since prior state for data sources is purely
  3658  	// informational, decoding should be skipped altogether.
  3659  	m := testModuleInline(t, map[string]string{
  3660  		"main.tf": `
  3661  data "test_object" "a" {
  3662    obj {
  3663      # args changes from a list to a map
  3664      args = {
  3665        val = "string"
  3666  	}
  3667    }
  3668  }
  3669  `,
  3670  	})
  3671  
  3672  	p := new(MockProvider)
  3673  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  3674  		DataSources: map[string]*configschema.Block{
  3675  			"test_object": {
  3676  				Attributes: map[string]*configschema.Attribute{
  3677  					"id": {
  3678  						Type:     cty.String,
  3679  						Computed: true,
  3680  					},
  3681  				},
  3682  				BlockTypes: map[string]*configschema.NestedBlock{
  3683  					"obj": {
  3684  						Block: configschema.Block{
  3685  							Attributes: map[string]*configschema.Attribute{
  3686  								"args": {Type: cty.Map(cty.String), Optional: true},
  3687  							},
  3688  						},
  3689  						Nesting: configschema.NestingSet,
  3690  					},
  3691  				},
  3692  			},
  3693  		},
  3694  	})
  3695  
  3696  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
  3697  		resp.State = req.Config
  3698  		return resp
  3699  	}
  3700  
  3701  	state := states.BuildState(func(s *states.SyncState) {
  3702  		s.SetResourceInstanceCurrent(mustResourceInstanceAddr(`data.test_object.a`), &states.ResourceInstanceObjectSrc{
  3703  			AttrsJSON: []byte(`{"id":"old","obj":[{"args":["string"]}]}`),
  3704  			Status:    states.ObjectReady,
  3705  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  3706  	})
  3707  
  3708  	ctx := testContext2(t, &ContextOpts{
  3709  		Providers: map[addrs.Provider]providers.Factory{
  3710  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3711  		},
  3712  	})
  3713  
  3714  	_, diags := ctx.Plan(m, state, DefaultPlanOpts)
  3715  	assertNoErrors(t, diags)
  3716  }
  3717  
  3718  func TestContext2Plan_applyGraphError(t *testing.T) {
  3719  	m := testModuleInline(t, map[string]string{
  3720  		"main.tf": `
  3721  resource "test_object" "a" {
  3722  }
  3723  resource "test_object" "b" {
  3724  	depends_on = [test_object.a]
  3725  }
  3726  `,
  3727  	})
  3728  
  3729  	p := simpleMockProvider()
  3730  
  3731  	// Here we introduce a cycle via state which only shows up in the apply
  3732  	// graph where the actual destroy instances are connected in the graph.
  3733  	// This could happen for example when a user has an existing state with
  3734  	// stored dependencies, and changes the config in such a way that
  3735  	// contradicts the stored dependencies.
  3736  	state := states.NewState()
  3737  	root := state.EnsureModule(addrs.RootModuleInstance)
  3738  	root.SetResourceInstanceCurrent(
  3739  		mustResourceInstanceAddr("test_object.a").Resource,
  3740  		&states.ResourceInstanceObjectSrc{
  3741  			Status:       states.ObjectTainted,
  3742  			AttrsJSON:    []byte(`{"test_string":"a"}`),
  3743  			Dependencies: []addrs.ConfigResource{mustResourceInstanceAddr("test_object.b").ContainingResource().Config()},
  3744  		},
  3745  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3746  	)
  3747  	root.SetResourceInstanceCurrent(
  3748  		mustResourceInstanceAddr("test_object.b").Resource,
  3749  		&states.ResourceInstanceObjectSrc{
  3750  			Status:    states.ObjectTainted,
  3751  			AttrsJSON: []byte(`{"test_string":"b"}`),
  3752  		},
  3753  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3754  	)
  3755  
  3756  	ctx := testContext2(t, &ContextOpts{
  3757  		Providers: map[addrs.Provider]providers.Factory{
  3758  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3759  		},
  3760  	})
  3761  
  3762  	_, diags := ctx.Plan(m, state, &PlanOpts{
  3763  		Mode: plans.NormalMode,
  3764  	})
  3765  	if !diags.HasErrors() {
  3766  		t.Fatal("cycle error not detected")
  3767  	}
  3768  
  3769  	msg := diags.ErrWithWarnings().Error()
  3770  	if !strings.Contains(msg, "Cycle") {
  3771  		t.Fatalf("no cycle error found:\n got: %s\n", msg)
  3772  	}
  3773  }
  3774  
  3775  // plan a destroy with no state where configuration could fail to evaluate
  3776  // expansion indexes.
  3777  func TestContext2Plan_emptyDestroy(t *testing.T) {
  3778  	m := testModuleInline(t, map[string]string{
  3779  		"main.tf": `
  3780  locals {
  3781    enable = true
  3782    value  = local.enable ? module.example[0].out : null
  3783  }
  3784  
  3785  module "example" {
  3786    count  = local.enable ? 1 : 0
  3787    source = "./example"
  3788  }
  3789  `,
  3790  		"example/main.tf": `
  3791  resource "test_resource" "x" {
  3792  }
  3793  
  3794  output "out" {
  3795    value = test_resource.x
  3796  }
  3797  `,
  3798  	})
  3799  
  3800  	p := testProvider("test")
  3801  	state := states.NewState()
  3802  
  3803  	ctx := testContext2(t, &ContextOpts{
  3804  		Providers: map[addrs.Provider]providers.Factory{
  3805  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3806  		},
  3807  	})
  3808  
  3809  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  3810  		Mode: plans.DestroyMode,
  3811  	})
  3812  
  3813  	assertNoErrors(t, diags)
  3814  
  3815  	// ensure that the given states are valid and can be serialized
  3816  	if plan.PrevRunState == nil {
  3817  		t.Fatal("nil plan.PrevRunState")
  3818  	}
  3819  	if plan.PriorState == nil {
  3820  		t.Fatal("nil plan.PriorState")
  3821  	}
  3822  }
  3823  
  3824  // A deposed instances which no longer exists during ReadResource creates NoOp
  3825  // change, which should not effect the plan.
  3826  func TestContext2Plan_deposedNoLongerExists(t *testing.T) {
  3827  	m := testModuleInline(t, map[string]string{
  3828  		"main.tf": `
  3829  resource "test_object" "b" {
  3830    count = 1
  3831    test_string = "updated"
  3832    lifecycle {
  3833      create_before_destroy = true
  3834    }
  3835  }
  3836  `,
  3837  	})
  3838  
  3839  	p := simpleMockProvider()
  3840  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  3841  		s := req.PriorState.GetAttr("test_string").AsString()
  3842  		if s == "current" {
  3843  			resp.NewState = req.PriorState
  3844  			return resp
  3845  		}
  3846  		// pretend the non-current instance has been deleted already
  3847  		resp.NewState = cty.NullVal(req.PriorState.Type())
  3848  		return resp
  3849  	}
  3850  
  3851  	// Here we introduce a cycle via state which only shows up in the apply
  3852  	// graph where the actual destroy instances are connected in the graph.
  3853  	// This could happen for example when a user has an existing state with
  3854  	// stored dependencies, and changes the config in such a way that
  3855  	// contradicts the stored dependencies.
  3856  	state := states.NewState()
  3857  	root := state.EnsureModule(addrs.RootModuleInstance)
  3858  	root.SetResourceInstanceDeposed(
  3859  		mustResourceInstanceAddr("test_object.a[0]").Resource,
  3860  		states.DeposedKey("deposed"),
  3861  		&states.ResourceInstanceObjectSrc{
  3862  			Status:       states.ObjectTainted,
  3863  			AttrsJSON:    []byte(`{"test_string":"old"}`),
  3864  			Dependencies: []addrs.ConfigResource{},
  3865  		},
  3866  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3867  	)
  3868  	root.SetResourceInstanceCurrent(
  3869  		mustResourceInstanceAddr("test_object.a[0]").Resource,
  3870  		&states.ResourceInstanceObjectSrc{
  3871  			Status:       states.ObjectTainted,
  3872  			AttrsJSON:    []byte(`{"test_string":"current"}`),
  3873  			Dependencies: []addrs.ConfigResource{},
  3874  		},
  3875  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  3876  	)
  3877  
  3878  	ctx := testContext2(t, &ContextOpts{
  3879  		Providers: map[addrs.Provider]providers.Factory{
  3880  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  3881  		},
  3882  	})
  3883  
  3884  	_, diags := ctx.Plan(m, state, &PlanOpts{
  3885  		Mode: plans.NormalMode,
  3886  	})
  3887  	assertNoErrors(t, diags)
  3888  }
  3889  
  3890  // make sure there are no cycles with changes around a provider configured via
  3891  // managed resources.
  3892  func TestContext2Plan_destroyWithResourceConfiguredProvider(t *testing.T) {
  3893  	m := testModuleInline(t, map[string]string{
  3894  		"main.tf": `
  3895  resource "test_object" "a" {
  3896    in = "a"
  3897  }
  3898  
  3899  provider "test" {
  3900    alias = "other"
  3901    in = test_object.a.out
  3902  }
  3903  
  3904  resource "test_object" "b" {
  3905    provider = test.other
  3906    in = "a"
  3907  }
  3908  `})
  3909  
  3910  	testProvider := &MockProvider{
  3911  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  3912  			Provider: providers.Schema{
  3913  				Block: &configschema.Block{
  3914  					Attributes: map[string]*configschema.Attribute{
  3915  						"in": {
  3916  							Type:     cty.String,
  3917  							Optional: true,
  3918  						},
  3919  					},
  3920  				},
  3921  			},
  3922  			ResourceTypes: map[string]providers.Schema{
  3923  				"test_object": providers.Schema{
  3924  					Block: &configschema.Block{
  3925  						Attributes: map[string]*configschema.Attribute{
  3926  							"in": {
  3927  								Type:     cty.String,
  3928  								Optional: true,
  3929  							},
  3930  							"out": {
  3931  								Type:     cty.Number,
  3932  								Computed: true,
  3933  							},
  3934  						},
  3935  					},
  3936  				},
  3937  			},
  3938  		},
  3939  	}
  3940  
  3941  	ctx := testContext2(t, &ContextOpts{
  3942  		Providers: map[addrs.Provider]providers.Factory{
  3943  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider),
  3944  		},
  3945  	})
  3946  
  3947  	// plan+apply to create the initial state
  3948  	opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
  3949  	plan, diags := ctx.Plan(m, states.NewState(), opts)
  3950  	assertNoErrors(t, diags)
  3951  	state, diags := ctx.Apply(plan, m)
  3952  	assertNoErrors(t, diags)
  3953  
  3954  	// Resource changes which have dependencies across providers which
  3955  	// themselves depend on resources can result in cycles.
  3956  	// Because other_object transitively depends on the module resources
  3957  	// through its provider, we trigger changes on both sides of this boundary
  3958  	// to ensure we can create a valid plan.
  3959  	//
  3960  	// Try to replace both instances
  3961  	addrA := mustResourceInstanceAddr("test_object.a")
  3962  	addrB := mustResourceInstanceAddr(`test_object.b`)
  3963  	opts.ForceReplace = []addrs.AbsResourceInstance{addrA, addrB}
  3964  
  3965  	_, diags = ctx.Plan(m, state, opts)
  3966  	assertNoErrors(t, diags)
  3967  }
  3968  
  3969  func TestContext2Plan_destroyPartialState(t *testing.T) {
  3970  	m := testModuleInline(t, map[string]string{
  3971  		"main.tf": `
  3972  resource "test_object" "a" {
  3973  }
  3974  
  3975  output "out" {
  3976    value = module.mod.out
  3977  }
  3978  
  3979  module "mod" {
  3980    source = "./mod"
  3981  }
  3982  `,
  3983  
  3984  		"./mod/main.tf": `
  3985  resource "test_object" "a" {
  3986    count = 2
  3987  
  3988    lifecycle {
  3989      precondition {
  3990  	  # test_object_b has already been destroyed, so referencing the first
  3991        # instance must not fail during a destroy plan.
  3992        condition = test_object.b[0].test_string == "invalid"
  3993        error_message = "should not block destroy"
  3994      }
  3995      precondition {
  3996        # this failing condition should bot block a destroy plan
  3997        condition = !local.continue
  3998        error_message = "should not block destroy"
  3999      }
  4000    }
  4001  }
  4002  
  4003  resource "test_object" "b" {
  4004    count = 2
  4005  }
  4006  
  4007  locals {
  4008    continue = true
  4009  }
  4010  
  4011  output "out" {
  4012    # the reference to test_object.b[0] may not be valid during a destroy plan,
  4013    # but should not fail.
  4014    value = local.continue ? test_object.a[1].test_string != "invalid"  && test_object.b[0].test_string != "invalid" : false
  4015  
  4016    precondition {
  4017      # test_object_b has already been destroyed, so referencing the first
  4018      # instance must not fail during a destroy plan.
  4019      condition = test_object.b[0].test_string == "invalid"
  4020      error_message = "should not block destroy"
  4021    }
  4022    precondition {
  4023      # this failing condition should bot block a destroy plan
  4024      condition = test_object.a[0].test_string == "invalid"
  4025      error_message = "should not block destroy"
  4026    }
  4027  }
  4028  `})
  4029  
  4030  	p := simpleMockProvider()
  4031  
  4032  	// This state could be the result of a failed destroy, leaving only 2
  4033  	// remaining instances. We want to be able to continue the destroy to
  4034  	// remove everything without blocking on invalid references or failing
  4035  	// conditions.
  4036  	state := states.NewState()
  4037  	mod := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey))
  4038  	mod.SetResourceInstanceCurrent(
  4039  		mustResourceInstanceAddr("test_object.a[0]").Resource,
  4040  		&states.ResourceInstanceObjectSrc{
  4041  			Status:       states.ObjectTainted,
  4042  			AttrsJSON:    []byte(`{"test_string":"current"}`),
  4043  			Dependencies: []addrs.ConfigResource{},
  4044  		},
  4045  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  4046  	)
  4047  	mod.SetResourceInstanceCurrent(
  4048  		mustResourceInstanceAddr("test_object.a[1]").Resource,
  4049  		&states.ResourceInstanceObjectSrc{
  4050  			Status:       states.ObjectTainted,
  4051  			AttrsJSON:    []byte(`{"test_string":"current"}`),
  4052  			Dependencies: []addrs.ConfigResource{},
  4053  		},
  4054  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  4055  	)
  4056  
  4057  	ctx := testContext2(t, &ContextOpts{
  4058  		Providers: map[addrs.Provider]providers.Factory{
  4059  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4060  		},
  4061  	})
  4062  
  4063  	_, diags := ctx.Plan(m, state, &PlanOpts{
  4064  		Mode: plans.DestroyMode,
  4065  	})
  4066  	assertNoErrors(t, diags)
  4067  }
  4068  
  4069  func TestContext2Plan_destroyPartialStateLocalRef(t *testing.T) {
  4070  	m := testModuleInline(t, map[string]string{
  4071  		"main.tf": `
  4072  module "already_destroyed" {
  4073    count = 1
  4074    source = "./mod"
  4075  }
  4076  
  4077  locals {
  4078    eval_error = module.already_destroyed[0].out
  4079  }
  4080  
  4081  output "already_destroyed" {
  4082    value = local.eval_error
  4083  }
  4084  
  4085  `,
  4086  
  4087  		"./mod/main.tf": `
  4088  resource "test_object" "a" {
  4089  }
  4090  
  4091  output "out" {
  4092    value = test_object.a.test_string
  4093  }
  4094  `})
  4095  
  4096  	p := simpleMockProvider()
  4097  
  4098  	state := states.NewState()
  4099  	ctx := testContext2(t, &ContextOpts{
  4100  		Providers: map[addrs.Provider]providers.Factory{
  4101  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4102  		},
  4103  	})
  4104  
  4105  	_, diags := ctx.Plan(m, state, &PlanOpts{
  4106  		Mode: plans.DestroyMode,
  4107  	})
  4108  	assertNoErrors(t, diags)
  4109  }
  4110  
  4111  // Make sure the data sources in the prior state are serializeable even if
  4112  // there were an error in the plan.
  4113  func TestContext2Plan_dataSourceReadPlanError(t *testing.T) {
  4114  	m, snap := testModuleWithSnapshot(t, "data-source-read-with-plan-error")
  4115  	awsProvider := testProvider("aws")
  4116  	testProvider := testProvider("test")
  4117  
  4118  	testProvider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  4119  		resp.PlannedState = req.ProposedNewState
  4120  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("oops"))
  4121  		return resp
  4122  	}
  4123  
  4124  	state := states.NewState()
  4125  
  4126  	ctx := testContext2(t, &ContextOpts{
  4127  		Providers: map[addrs.Provider]providers.Factory{
  4128  			addrs.NewDefaultProvider("aws"):  testProviderFuncFixed(awsProvider),
  4129  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider),
  4130  		},
  4131  	})
  4132  
  4133  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  4134  	if !diags.HasErrors() {
  4135  		t.Fatalf("expected plan error")
  4136  	}
  4137  
  4138  	// make sure we can serialize the plan even if there were an error
  4139  	_, _, _, err := contextOptsForPlanViaFile(t, snap, plan)
  4140  	if err != nil {
  4141  		t.Fatalf("failed to round-trip through planfile: %s", err)
  4142  	}
  4143  }
  4144  
  4145  func TestContext2Plan_ignoredMarkedValue(t *testing.T) {
  4146  	m := testModuleInline(t, map[string]string{
  4147  		"main.tf": `
  4148  resource "test_object" "a" {
  4149    map = {
  4150      prior = "value"
  4151      new   = sensitive("ignored")
  4152    }
  4153  }
  4154  `})
  4155  
  4156  	testProvider := &MockProvider{
  4157  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  4158  			ResourceTypes: map[string]providers.Schema{
  4159  				"test_object": providers.Schema{
  4160  					Block: &configschema.Block{
  4161  						Attributes: map[string]*configschema.Attribute{
  4162  							"map": {
  4163  								Type:     cty.Map(cty.String),
  4164  								Optional: true,
  4165  							},
  4166  						},
  4167  					},
  4168  				},
  4169  			},
  4170  		},
  4171  	}
  4172  
  4173  	testProvider.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  4174  		// We're going to ignore any changes here and return the prior state.
  4175  		resp.PlannedState = req.PriorState
  4176  		return resp
  4177  	}
  4178  
  4179  	state := states.NewState()
  4180  	root := state.RootModule()
  4181  	root.SetResourceInstanceCurrent(
  4182  		mustResourceInstanceAddr("test_object.a").Resource,
  4183  		&states.ResourceInstanceObjectSrc{
  4184  			Status:       states.ObjectReady,
  4185  			AttrsJSON:    []byte(`{"map":{"prior":"value"}}`),
  4186  			Dependencies: []addrs.ConfigResource{},
  4187  		},
  4188  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  4189  	)
  4190  	ctx := testContext2(t, &ContextOpts{
  4191  		Providers: map[addrs.Provider]providers.Factory{
  4192  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(testProvider),
  4193  		},
  4194  	})
  4195  
  4196  	// plan+apply to create the initial state
  4197  	opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
  4198  	plan, diags := ctx.Plan(m, state, opts)
  4199  	assertNoErrors(t, diags)
  4200  
  4201  	for _, c := range plan.Changes.Resources {
  4202  		if c.Action != plans.NoOp {
  4203  			t.Errorf("unexpected %s change for %s", c.Action, c.Addr)
  4204  		}
  4205  	}
  4206  }
  4207  
  4208  func TestContext2Plan_importResourceBasic(t *testing.T) {
  4209  	addr := mustResourceInstanceAddr("test_object.a")
  4210  	m := testModuleInline(t, map[string]string{
  4211  		"main.tf": `
  4212  resource "test_object" "a" {
  4213    test_string = "foo"
  4214  }
  4215  
  4216  import {
  4217    to   = test_object.a
  4218    id   = "123"
  4219  }
  4220  `,
  4221  	})
  4222  
  4223  	p := simpleMockProvider()
  4224  	hook := new(MockHook)
  4225  	ctx := testContext2(t, &ContextOpts{
  4226  		Hooks: []Hook{hook},
  4227  		Providers: map[addrs.Provider]providers.Factory{
  4228  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4229  		},
  4230  	})
  4231  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  4232  		NewState: cty.ObjectVal(map[string]cty.Value{
  4233  			"test_string": cty.StringVal("foo"),
  4234  		}),
  4235  	}
  4236  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4237  		ImportedResources: []providers.ImportedResource{
  4238  			{
  4239  				TypeName: "test_object",
  4240  				State: cty.ObjectVal(map[string]cty.Value{
  4241  					"test_string": cty.StringVal("foo"),
  4242  				}),
  4243  			},
  4244  		},
  4245  	}
  4246  
  4247  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  4248  	if diags.HasErrors() {
  4249  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  4250  	}
  4251  
  4252  	t.Run(addr.String(), func(t *testing.T) {
  4253  		instPlan := plan.Changes.ResourceInstance(addr)
  4254  		if instPlan == nil {
  4255  			t.Fatalf("no plan for %s at all", addr)
  4256  		}
  4257  
  4258  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  4259  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  4260  		}
  4261  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  4262  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  4263  		}
  4264  		if got, want := instPlan.Action, plans.NoOp; got != want {
  4265  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  4266  		}
  4267  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  4268  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  4269  		}
  4270  		if instPlan.Importing.ID != "123" {
  4271  			t.Errorf("expected import change from \"123\", got non-import change")
  4272  		}
  4273  
  4274  		if !hook.PrePlanImportCalled {
  4275  			t.Fatalf("PostPlanImport hook not called")
  4276  		}
  4277  		if addr, wantAddr := hook.PrePlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  4278  			t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  4279  		}
  4280  
  4281  		if !hook.PostPlanImportCalled {
  4282  			t.Fatalf("PostPlanImport hook not called")
  4283  		}
  4284  		if addr, wantAddr := hook.PostPlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  4285  			t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  4286  		}
  4287  	})
  4288  }
  4289  
  4290  func TestContext2Plan_importToDynamicAddress(t *testing.T) {
  4291  	type TestConfiguration struct {
  4292  		Description         string
  4293  		ResolvedAddress     string
  4294  		inlineConfiguration map[string]string
  4295  	}
  4296  	configurations := []TestConfiguration{
  4297  		{
  4298  			Description:     "To address includes a variable as index",
  4299  			ResolvedAddress: "test_object.a[0]",
  4300  			inlineConfiguration: map[string]string{
  4301  				"main.tf": `
  4302  variable "index" {
  4303    default = 0
  4304  }
  4305  
  4306  resource "test_object" "a" {
  4307    count = 1
  4308    test_string = "foo"
  4309  }
  4310  
  4311  import {
  4312    to   = test_object.a[var.index]
  4313    id   = "%d"
  4314  }
  4315  `,
  4316  			},
  4317  		},
  4318  		{
  4319  			Description:     "To address includes a local as index",
  4320  			ResolvedAddress: "test_object.a[0]",
  4321  			inlineConfiguration: map[string]string{
  4322  				"main.tf": `
  4323  locals {
  4324    index = 0
  4325  }
  4326  
  4327  resource "test_object" "a" {
  4328    count = 1
  4329    test_string = "foo"
  4330  }
  4331  
  4332  import {
  4333    to   = test_object.a[local.index]
  4334    id   = "%d"
  4335  }
  4336  `,
  4337  			},
  4338  		},
  4339  		{
  4340  			Description:     "To address includes a conditional expression as index",
  4341  			ResolvedAddress: "test_object.a[\"zero\"]",
  4342  			inlineConfiguration: map[string]string{
  4343  				"main.tf": `
  4344  resource "test_object" "a" {
  4345    for_each = toset(["zero"])
  4346    test_string = "foo"
  4347  }
  4348  
  4349  import {
  4350    to   = test_object.a[ true ? "zero" : "one"]
  4351    id   = "%d"
  4352  }
  4353  `,
  4354  			},
  4355  		},
  4356  		{
  4357  			Description:     "To address includes a conditional expression with vars and locals as index",
  4358  			ResolvedAddress: "test_object.a[\"one\"]",
  4359  			inlineConfiguration: map[string]string{
  4360  				"main.tf": `
  4361  variable "one" {
  4362    default = 1
  4363  }
  4364  
  4365  locals {
  4366    zero = "zero"
  4367    one = "one"
  4368  }
  4369  
  4370  resource "test_object" "a" {
  4371    for_each = toset(["one"])
  4372    test_string = "foo"
  4373  }
  4374  
  4375  import {
  4376    to   = test_object.a[var.one == 1 ? local.one : local.zero]
  4377    id   = "%d"
  4378  }
  4379  `,
  4380  			},
  4381  		},
  4382  		{
  4383  			Description:     "To address includes a resource reference as index",
  4384  			ResolvedAddress: "test_object.a[\"boop\"]",
  4385  			inlineConfiguration: map[string]string{
  4386  				"main.tf": `
  4387  resource "test_object" "reference" {
  4388    test_string = "boop"
  4389  }
  4390  
  4391  resource "test_object" "a" {
  4392    for_each = toset(["boop"])
  4393    test_string = "foo"
  4394  }
  4395  
  4396  import {
  4397    to   = test_object.a[test_object.reference.test_string]
  4398    id   = "%d"
  4399  }
  4400  `,
  4401  			},
  4402  		},
  4403  		{
  4404  			Description:     "To address includes a data reference as index",
  4405  			ResolvedAddress: "test_object.a[\"bip\"]",
  4406  			inlineConfiguration: map[string]string{
  4407  				"main.tf": `
  4408  data "test_object" "reference" {
  4409  }
  4410  
  4411  resource "test_object" "a" {
  4412    for_each = toset(["bip"])
  4413    test_string = "foo"
  4414  }
  4415  
  4416  import {
  4417    to   = test_object.a[data.test_object.reference.test_string]
  4418    id   = "%d"
  4419  }
  4420  `,
  4421  			},
  4422  		},
  4423  	}
  4424  
  4425  	const importId = 123
  4426  
  4427  	for _, configuration := range configurations {
  4428  		t.Run(configuration.Description, func(t *testing.T) {
  4429  
  4430  			// Format the configuration with the import ID
  4431  			formattedConfiguration := make(map[string]string)
  4432  			for configFileName, configFileContent := range configuration.inlineConfiguration {
  4433  				formattedConfigFileContent := fmt.Sprintf(configFileContent, importId)
  4434  				formattedConfiguration[configFileName] = formattedConfigFileContent
  4435  			}
  4436  
  4437  			addr := mustResourceInstanceAddr(configuration.ResolvedAddress)
  4438  			m := testModuleInline(t, formattedConfiguration)
  4439  
  4440  			p := &MockProvider{
  4441  				GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  4442  					Provider: providers.Schema{Block: simpleTestSchema()},
  4443  					ResourceTypes: map[string]providers.Schema{
  4444  						"test_object": providers.Schema{Block: simpleTestSchema()},
  4445  					},
  4446  					DataSources: map[string]providers.Schema{
  4447  						"test_object": providers.Schema{
  4448  							Block: &configschema.Block{
  4449  								Attributes: map[string]*configschema.Attribute{
  4450  									"test_string": {
  4451  										Type:     cty.String,
  4452  										Optional: true,
  4453  									},
  4454  								},
  4455  							},
  4456  						},
  4457  					},
  4458  				},
  4459  			}
  4460  
  4461  			hook := new(MockHook)
  4462  			ctx := testContext2(t, &ContextOpts{
  4463  				Hooks: []Hook{hook},
  4464  				Providers: map[addrs.Provider]providers.Factory{
  4465  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4466  				},
  4467  			})
  4468  			p.ReadResourceResponse = &providers.ReadResourceResponse{
  4469  				NewState: cty.ObjectVal(map[string]cty.Value{
  4470  					"test_string": cty.StringVal("foo"),
  4471  				}),
  4472  			}
  4473  
  4474  			p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  4475  				State: cty.ObjectVal(map[string]cty.Value{
  4476  					"test_string": cty.StringVal("bip"),
  4477  				}),
  4478  			}
  4479  
  4480  			p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4481  				ImportedResources: []providers.ImportedResource{
  4482  					{
  4483  						TypeName: "test_object",
  4484  						State: cty.ObjectVal(map[string]cty.Value{
  4485  							"test_string": cty.StringVal("foo"),
  4486  						}),
  4487  					},
  4488  				},
  4489  			}
  4490  
  4491  			plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
  4492  			if diags.HasErrors() {
  4493  				t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  4494  			}
  4495  
  4496  			t.Run(addr.String(), func(t *testing.T) {
  4497  				instPlan := plan.Changes.ResourceInstance(addr)
  4498  				if instPlan == nil {
  4499  					t.Fatalf("no plan for %s at all", addr)
  4500  				}
  4501  
  4502  				if got, want := instPlan.Addr, addr; !got.Equal(want) {
  4503  					t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  4504  				}
  4505  				if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  4506  					t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  4507  				}
  4508  				if got, want := instPlan.Action, plans.NoOp; got != want {
  4509  					t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  4510  				}
  4511  				if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  4512  					t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  4513  				}
  4514  				if instPlan.Importing.ID != strconv.Itoa(importId) {
  4515  					t.Errorf("expected import change from \"%d\", got non-import change", importId)
  4516  				}
  4517  
  4518  				if !hook.PrePlanImportCalled {
  4519  					t.Fatalf("PostPlanImport hook not called")
  4520  				}
  4521  				if addr, wantAddr := hook.PrePlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  4522  					t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  4523  				}
  4524  
  4525  				if !hook.PostPlanImportCalled {
  4526  					t.Fatalf("PostPlanImport hook not called")
  4527  				}
  4528  				if addr, wantAddr := hook.PostPlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  4529  					t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  4530  				}
  4531  			})
  4532  		})
  4533  	}
  4534  }
  4535  
  4536  func TestContext2Plan_importForEach(t *testing.T) {
  4537  	type ImportResult struct {
  4538  		ResolvedAddress string
  4539  		ResolvedId      string
  4540  	}
  4541  	type TestConfiguration struct {
  4542  		Description         string
  4543  		ImportResults       []ImportResult
  4544  		inlineConfiguration map[string]string
  4545  	}
  4546  	configurations := []TestConfiguration{
  4547  		{
  4548  			Description:   "valid map",
  4549  			ImportResults: []ImportResult{{ResolvedAddress: `test_object.a["key1"]`, ResolvedId: "val1"}, {ResolvedAddress: `test_object.a["key2"]`, ResolvedId: "val2"}, {ResolvedAddress: `test_object.a["key3"]`, ResolvedId: "val3"}},
  4550  			inlineConfiguration: map[string]string{
  4551  				"main.tf": `
  4552  locals {
  4553    map = {
  4554      "key1" = "val1"
  4555      "key2" = "val2"
  4556      "key3" = "val3"
  4557    }
  4558  }
  4559  
  4560  resource "test_object" "a" {
  4561    for_each = local.map
  4562  }
  4563  
  4564  import {
  4565    for_each = local.map
  4566    to = test_object.a[each.key]
  4567    id = each.value
  4568  }
  4569  `,
  4570  			},
  4571  		},
  4572  		{
  4573  			Description:   "valid set",
  4574  			ImportResults: []ImportResult{{ResolvedAddress: `test_object.a["val0"]`, ResolvedId: "val0"}, {ResolvedAddress: `test_object.a["val1"]`, ResolvedId: "val1"}, {ResolvedAddress: `test_object.a["val2"]`, ResolvedId: "val2"}},
  4575  			inlineConfiguration: map[string]string{
  4576  				"main.tf": `
  4577  variable "set" {
  4578  	type = set(string)
  4579  	default = ["val0", "val1", "val2"]
  4580  }
  4581  
  4582  resource "test_object" "a" {
  4583    for_each = var.set
  4584  }
  4585  
  4586  import {
  4587    for_each = var.set
  4588    to = test_object.a[each.key]
  4589    id = each.value
  4590  }
  4591  `,
  4592  			},
  4593  		},
  4594  		{
  4595  			Description:   "valid tuple",
  4596  			ImportResults: []ImportResult{{ResolvedAddress: `module.mod[0].test_object.a["resKey1"]`, ResolvedId: "val1"}, {ResolvedAddress: `module.mod[0].test_object.a["resKey2"]`, ResolvedId: "val2"}, {ResolvedAddress: `module.mod[1].test_object.a["resKey1"]`, ResolvedId: "val3"}, {ResolvedAddress: `module.mod[1].test_object.a["resKey2"]`, ResolvedId: "val4"}},
  4597  			inlineConfiguration: map[string]string{
  4598  				"mod/main.tf": `
  4599  variable "set" {
  4600  	type = set(string)
  4601  	default = ["resKey1", "resKey2"]
  4602  }
  4603  
  4604  resource "test_object" "a" {
  4605    for_each = var.set
  4606  }
  4607  `,
  4608  				"main.tf": `
  4609  locals {
  4610    tuple = [
  4611      {
  4612        moduleKey = 0
  4613        resourceKey = "resKey1"
  4614        id = "val1"
  4615      },
  4616      {
  4617        moduleKey = 0
  4618        resourceKey = "resKey2"
  4619        id = "val2"
  4620      },
  4621      {
  4622        moduleKey = 1
  4623        resourceKey = "resKey1"
  4624        id = "val3"
  4625      },
  4626      {
  4627        moduleKey = 1
  4628        resourceKey = "resKey2"
  4629        id = "val4"
  4630      },
  4631    ]
  4632  }
  4633  
  4634  module "mod" {
  4635    count = 2
  4636    source = "./mod"
  4637  }
  4638  
  4639  import {
  4640    for_each = local.tuple
  4641    id = each.value.id
  4642    to = module.mod[each.value.moduleKey].test_object.a[each.value.resourceKey]
  4643  }
  4644  `,
  4645  			},
  4646  		},
  4647  	}
  4648  
  4649  	for _, configuration := range configurations {
  4650  		t.Run(configuration.Description, func(t *testing.T) {
  4651  			m := testModuleInline(t, configuration.inlineConfiguration)
  4652  			p := simpleMockProvider()
  4653  
  4654  			hook := new(MockHook)
  4655  			ctx := testContext2(t, &ContextOpts{
  4656  				Hooks: []Hook{hook},
  4657  				Providers: map[addrs.Provider]providers.Factory{
  4658  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4659  				},
  4660  			})
  4661  			p.ReadResourceResponse = &providers.ReadResourceResponse{
  4662  				NewState: cty.ObjectVal(map[string]cty.Value{}),
  4663  			}
  4664  
  4665  			p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4666  				ImportedResources: []providers.ImportedResource{
  4667  					{
  4668  						TypeName: "test_object",
  4669  						State:    cty.ObjectVal(map[string]cty.Value{}),
  4670  					},
  4671  				},
  4672  			}
  4673  
  4674  			plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
  4675  			if diags.HasErrors() {
  4676  				t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  4677  			}
  4678  
  4679  			if len(plan.Changes.Resources) != len(configuration.ImportResults) {
  4680  				t.Fatalf("excpected %d resource chnages in the plan, got %d instead", len(configuration.ImportResults), len(plan.Changes.Resources))
  4681  			}
  4682  
  4683  			for _, importResult := range configuration.ImportResults {
  4684  				addr := mustResourceInstanceAddr(importResult.ResolvedAddress)
  4685  
  4686  				t.Run(addr.String(), func(t *testing.T) {
  4687  					instPlan := plan.Changes.ResourceInstance(addr)
  4688  					if instPlan == nil {
  4689  						t.Fatalf("no plan for %s at all", addr)
  4690  					}
  4691  
  4692  					if got, want := instPlan.Addr, addr; !got.Equal(want) {
  4693  						t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  4694  					}
  4695  					if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  4696  						t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  4697  					}
  4698  					if got, want := instPlan.Action, plans.NoOp; got != want {
  4699  						t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  4700  					}
  4701  					if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  4702  						t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  4703  					}
  4704  					if instPlan.Importing.ID != importResult.ResolvedId {
  4705  						t.Errorf("expected import change from \"%s\", got non-import change", importResult.ResolvedId)
  4706  					}
  4707  
  4708  					if !hook.PrePlanImportCalled {
  4709  						t.Fatalf("PostPlanImport hook not called")
  4710  					}
  4711  
  4712  					if !hook.PostPlanImportCalled {
  4713  						t.Fatalf("PostPlanImport hook not called")
  4714  					}
  4715  				})
  4716  			}
  4717  		})
  4718  	}
  4719  }
  4720  
  4721  func TestContext2Plan_importToInvalidDynamicAddress(t *testing.T) {
  4722  	type TestConfiguration struct {
  4723  		Description         string
  4724  		expectedError       string
  4725  		inlineConfiguration map[string]string
  4726  	}
  4727  	configurations := []TestConfiguration{
  4728  		{
  4729  			Description:   "To address index value is null",
  4730  			expectedError: "Import block 'to' address contains an invalid key: Import block contained a resource address using an index which is null. Please ensure the expression for the index is not null",
  4731  			inlineConfiguration: map[string]string{
  4732  				"main.tf": `
  4733  variable "index" {
  4734    default = null
  4735  }
  4736  
  4737  resource "test_object" "a" {
  4738    count = 1
  4739    test_string = "foo"
  4740  }
  4741  
  4742  import {
  4743    to   = test_object.a[var.index]
  4744    id   = "123"
  4745  }
  4746  `,
  4747  			},
  4748  		},
  4749  		{
  4750  			Description:   "To address index is not a number or a string",
  4751  			expectedError: "Import block 'to' address contains an invalid key: Import block contained a resource address using an index which is not valid for a resource instance (not a string or a number). Please ensure the expression for the index is correct, and returns either a string or a number",
  4752  			inlineConfiguration: map[string]string{
  4753  				"main.tf": `
  4754  locals {
  4755    index = toset(["foo"])
  4756  }
  4757  
  4758  resource "test_object" "a" {
  4759    for_each = toset(["foo"])
  4760    test_string = "foo"
  4761  }
  4762  
  4763  import {
  4764    to   = test_object.a[local.index]
  4765    id   = "123"
  4766  }
  4767  `,
  4768  			},
  4769  		},
  4770  		{
  4771  			Description:   "To address index value is sensitive",
  4772  			expectedError: "Import block 'to' address contains an invalid key: Import block contained a resource address using an index which is sensitive. Please ensure indexes used in the resource address of an import target are not sensitive",
  4773  			inlineConfiguration: map[string]string{
  4774  				"main.tf": `
  4775  locals {
  4776    index = sensitive("foo")
  4777  }
  4778  
  4779  resource "test_object" "a" {
  4780    for_each = toset(["foo"])
  4781    test_string = "foo"
  4782  }
  4783  
  4784  import {
  4785    to   = test_object.a[local.index]
  4786    id   = "123"
  4787  }
  4788  `,
  4789  			},
  4790  		},
  4791  		{
  4792  			Description:   "To address index value will only be known after apply",
  4793  			expectedError: "Import block contained a resource address using an index that will only be known after apply. Please ensure to use expressions that are known at plan time for the index of an import target address",
  4794  			inlineConfiguration: map[string]string{
  4795  				"main.tf": `
  4796  resource "test_object" "reference" {
  4797  }
  4798  
  4799  resource "test_object" "a" {
  4800    count = 1
  4801    test_string = "foo"
  4802  }
  4803  
  4804  import {
  4805    to   = test_object.a[test_object.reference.id]
  4806    id   = "123"
  4807  }
  4808  `,
  4809  			},
  4810  		},
  4811  	}
  4812  
  4813  	for _, configuration := range configurations {
  4814  		t.Run(configuration.Description, func(t *testing.T) {
  4815  			m := testModuleInline(t, configuration.inlineConfiguration)
  4816  
  4817  			providerSchema := &configschema.Block{
  4818  				Attributes: map[string]*configschema.Attribute{
  4819  					"test_string": {
  4820  						Type:     cty.String,
  4821  						Optional: true,
  4822  					},
  4823  					"id": {
  4824  						Type:     cty.String,
  4825  						Computed: true,
  4826  					},
  4827  				},
  4828  			}
  4829  
  4830  			p := &MockProvider{
  4831  				GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  4832  					Provider: providers.Schema{Block: providerSchema},
  4833  					ResourceTypes: map[string]providers.Schema{
  4834  						"test_object": providers.Schema{Block: providerSchema},
  4835  					},
  4836  				},
  4837  			}
  4838  
  4839  			hook := new(MockHook)
  4840  			ctx := testContext2(t, &ContextOpts{
  4841  				Hooks: []Hook{hook},
  4842  				Providers: map[addrs.Provider]providers.Factory{
  4843  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4844  				},
  4845  			})
  4846  
  4847  			p.ReadResourceResponse = &providers.ReadResourceResponse{
  4848  				NewState: cty.ObjectVal(map[string]cty.Value{
  4849  					"test_string": cty.StringVal("foo"),
  4850  				}),
  4851  			}
  4852  
  4853  			p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  4854  				testStringVal := req.ProposedNewState.GetAttr("test_string")
  4855  				return providers.PlanResourceChangeResponse{
  4856  					PlannedState: cty.ObjectVal(map[string]cty.Value{
  4857  						"test_string": testStringVal,
  4858  						"id":          cty.UnknownVal(cty.String),
  4859  					}),
  4860  				}
  4861  			}
  4862  
  4863  			p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4864  				ImportedResources: []providers.ImportedResource{
  4865  					{
  4866  						TypeName: "test_object",
  4867  						State: cty.ObjectVal(map[string]cty.Value{
  4868  							"test_string": cty.StringVal("foo"),
  4869  						}),
  4870  					},
  4871  				},
  4872  			}
  4873  
  4874  			_, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
  4875  
  4876  			if !diags.HasErrors() {
  4877  				t.Fatal("succeeded; want errors")
  4878  			}
  4879  			if got, want := diags.Err().Error(), configuration.expectedError; !strings.Contains(got, want) {
  4880  				t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  4881  			}
  4882  		})
  4883  	}
  4884  }
  4885  
  4886  func TestContext2Plan_importResourceAlreadyInState(t *testing.T) {
  4887  	addr := mustResourceInstanceAddr("test_object.a")
  4888  	m := testModuleInline(t, map[string]string{
  4889  		"main.tf": `
  4890  resource "test_object" "a" {
  4891    test_string = "foo"
  4892  }
  4893  
  4894  import {
  4895    to   = test_object.a
  4896    id   = "123"
  4897  }
  4898  `,
  4899  	})
  4900  
  4901  	p := simpleMockProvider()
  4902  	ctx := testContext2(t, &ContextOpts{
  4903  		Providers: map[addrs.Provider]providers.Factory{
  4904  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4905  		},
  4906  	})
  4907  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  4908  		NewState: cty.ObjectVal(map[string]cty.Value{
  4909  			"test_string": cty.StringVal("foo"),
  4910  		}),
  4911  	}
  4912  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4913  		ImportedResources: []providers.ImportedResource{
  4914  			{
  4915  				TypeName: "test_object",
  4916  				State: cty.ObjectVal(map[string]cty.Value{
  4917  					"test_string": cty.StringVal("foo"),
  4918  				}),
  4919  			},
  4920  		},
  4921  	}
  4922  
  4923  	state := states.NewState()
  4924  	root := state.EnsureModule(addrs.RootModuleInstance)
  4925  	root.SetResourceInstanceCurrent(
  4926  		mustResourceInstanceAddr("test_object.a").Resource,
  4927  		&states.ResourceInstanceObjectSrc{
  4928  			Status:    states.ObjectReady,
  4929  			AttrsJSON: []byte(`{"test_string":"foo"}`),
  4930  		},
  4931  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  4932  	)
  4933  
  4934  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  4935  	if diags.HasErrors() {
  4936  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  4937  	}
  4938  
  4939  	t.Run(addr.String(), func(t *testing.T) {
  4940  		instPlan := plan.Changes.ResourceInstance(addr)
  4941  		if instPlan == nil {
  4942  			t.Fatalf("no plan for %s at all", addr)
  4943  		}
  4944  
  4945  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  4946  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  4947  		}
  4948  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  4949  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  4950  		}
  4951  		if got, want := instPlan.Action, plans.NoOp; got != want {
  4952  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  4953  		}
  4954  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  4955  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  4956  		}
  4957  		if instPlan.Importing != nil {
  4958  			t.Errorf("expected non-import change, got import change %+v", instPlan.Importing)
  4959  		}
  4960  	})
  4961  }
  4962  
  4963  func TestContext2Plan_importResourceUpdate(t *testing.T) {
  4964  	addr := mustResourceInstanceAddr("test_object.a")
  4965  	m := testModuleInline(t, map[string]string{
  4966  		"main.tf": `
  4967  resource "test_object" "a" {
  4968    test_string = "bar"
  4969  }
  4970  
  4971  import {
  4972    to   = test_object.a
  4973    id   = "123"
  4974  }
  4975  `,
  4976  	})
  4977  
  4978  	p := simpleMockProvider()
  4979  	ctx := testContext2(t, &ContextOpts{
  4980  		Providers: map[addrs.Provider]providers.Factory{
  4981  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  4982  		},
  4983  	})
  4984  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  4985  		NewState: cty.ObjectVal(map[string]cty.Value{
  4986  			"test_string": cty.StringVal("foo"),
  4987  		}),
  4988  	}
  4989  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  4990  		ImportedResources: []providers.ImportedResource{
  4991  			{
  4992  				TypeName: "test_object",
  4993  				State: cty.ObjectVal(map[string]cty.Value{
  4994  					"test_string": cty.StringVal("foo"),
  4995  				}),
  4996  			},
  4997  		},
  4998  	}
  4999  
  5000  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  5001  	if diags.HasErrors() {
  5002  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5003  	}
  5004  
  5005  	t.Run(addr.String(), func(t *testing.T) {
  5006  		instPlan := plan.Changes.ResourceInstance(addr)
  5007  		if instPlan == nil {
  5008  			t.Fatalf("no plan for %s at all", addr)
  5009  		}
  5010  
  5011  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  5012  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  5013  		}
  5014  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  5015  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  5016  		}
  5017  		if got, want := instPlan.Action, plans.Update; got != want {
  5018  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  5019  		}
  5020  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  5021  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  5022  		}
  5023  		if instPlan.Importing.ID != "123" {
  5024  			t.Errorf("expected import change from \"123\", got non-import change")
  5025  		}
  5026  	})
  5027  }
  5028  
  5029  func TestContext2Plan_importResourceReplace(t *testing.T) {
  5030  	addr := mustResourceInstanceAddr("test_object.a")
  5031  	m := testModuleInline(t, map[string]string{
  5032  		"main.tf": `
  5033  resource "test_object" "a" {
  5034    test_string = "bar"
  5035  }
  5036  
  5037  import {
  5038    to   = test_object.a
  5039    id   = "123"
  5040  }
  5041  `,
  5042  	})
  5043  
  5044  	p := simpleMockProvider()
  5045  	ctx := testContext2(t, &ContextOpts{
  5046  		Providers: map[addrs.Provider]providers.Factory{
  5047  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5048  		},
  5049  	})
  5050  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  5051  		NewState: cty.ObjectVal(map[string]cty.Value{
  5052  			"test_string": cty.StringVal("foo"),
  5053  		}),
  5054  	}
  5055  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5056  		ImportedResources: []providers.ImportedResource{
  5057  			{
  5058  				TypeName: "test_object",
  5059  				State: cty.ObjectVal(map[string]cty.Value{
  5060  					"test_string": cty.StringVal("foo"),
  5061  				}),
  5062  			},
  5063  		},
  5064  	}
  5065  
  5066  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5067  		Mode: plans.NormalMode,
  5068  		ForceReplace: []addrs.AbsResourceInstance{
  5069  			addr,
  5070  		},
  5071  	})
  5072  	if diags.HasErrors() {
  5073  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5074  	}
  5075  
  5076  	t.Run(addr.String(), func(t *testing.T) {
  5077  		instPlan := plan.Changes.ResourceInstance(addr)
  5078  		if instPlan == nil {
  5079  			t.Fatalf("no plan for %s at all", addr)
  5080  		}
  5081  
  5082  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  5083  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  5084  		}
  5085  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  5086  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  5087  		}
  5088  		if got, want := instPlan.Action, plans.DeleteThenCreate; got != want {
  5089  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  5090  		}
  5091  		if instPlan.Importing.ID != "123" {
  5092  			t.Errorf("expected import change from \"123\", got non-import change")
  5093  		}
  5094  	})
  5095  }
  5096  
  5097  func TestContext2Plan_importRefreshOnce(t *testing.T) {
  5098  	addr := mustResourceInstanceAddr("test_object.a")
  5099  	m := testModuleInline(t, map[string]string{
  5100  		"main.tf": `
  5101  resource "test_object" "a" {
  5102    test_string = "bar"
  5103  }
  5104  
  5105  import {
  5106    to   = test_object.a
  5107    id   = "123"
  5108  }
  5109  `,
  5110  	})
  5111  
  5112  	p := simpleMockProvider()
  5113  	ctx := testContext2(t, &ContextOpts{
  5114  		Providers: map[addrs.Provider]providers.Factory{
  5115  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5116  		},
  5117  	})
  5118  
  5119  	readCalled := 0
  5120  	p.ReadResourceFn = func(req providers.ReadResourceRequest) providers.ReadResourceResponse {
  5121  		readCalled++
  5122  		state, _ := simpleTestSchema().CoerceValue(cty.ObjectVal(map[string]cty.Value{
  5123  			"test_string": cty.StringVal("foo"),
  5124  		}))
  5125  
  5126  		return providers.ReadResourceResponse{
  5127  			NewState: state,
  5128  		}
  5129  	}
  5130  
  5131  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5132  		ImportedResources: []providers.ImportedResource{
  5133  			{
  5134  				TypeName: "test_object",
  5135  				State: cty.ObjectVal(map[string]cty.Value{
  5136  					"test_string": cty.StringVal("foo"),
  5137  				}),
  5138  			},
  5139  		},
  5140  	}
  5141  
  5142  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5143  		Mode: plans.NormalMode,
  5144  		ForceReplace: []addrs.AbsResourceInstance{
  5145  			addr,
  5146  		},
  5147  	})
  5148  	if diags.HasErrors() {
  5149  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5150  	}
  5151  
  5152  	if readCalled > 1 {
  5153  		t.Error("ReadResource called multiple times for import")
  5154  	}
  5155  }
  5156  
  5157  func TestContext2Plan_importIdVariable(t *testing.T) {
  5158  	p := testProvider("aws")
  5159  	m := testModule(t, "import-id-variable")
  5160  	ctx := testContext2(t, &ContextOpts{
  5161  		Providers: map[addrs.Provider]providers.Factory{
  5162  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  5163  		},
  5164  	})
  5165  
  5166  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5167  		ImportedResources: []providers.ImportedResource{
  5168  			{
  5169  				TypeName: "aws_instance",
  5170  				State: cty.ObjectVal(map[string]cty.Value{
  5171  					"id": cty.StringVal("foo"),
  5172  				}),
  5173  			},
  5174  		},
  5175  	}
  5176  
  5177  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5178  		SetVariables: InputValues{
  5179  			"the_id": &InputValue{
  5180  				// let var take its default value
  5181  				Value: cty.NilVal,
  5182  			},
  5183  		},
  5184  	})
  5185  	if diags.HasErrors() {
  5186  		t.Fatalf("unexpected errors: %s", diags.Err())
  5187  	}
  5188  }
  5189  
  5190  func TestContext2Plan_importIdReference(t *testing.T) {
  5191  	p := testProvider("aws")
  5192  	m := testModule(t, "import-id-reference")
  5193  	ctx := testContext2(t, &ContextOpts{
  5194  		Providers: map[addrs.Provider]providers.Factory{
  5195  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  5196  		},
  5197  	})
  5198  
  5199  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5200  		ImportedResources: []providers.ImportedResource{
  5201  			{
  5202  				TypeName: "aws_instance",
  5203  				State: cty.ObjectVal(map[string]cty.Value{
  5204  					"id": cty.StringVal("foo"),
  5205  				}),
  5206  			},
  5207  		},
  5208  	}
  5209  
  5210  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5211  		SetVariables: InputValues{
  5212  			"the_id": &InputValue{
  5213  				// let var take its default value
  5214  				Value: cty.NilVal,
  5215  			},
  5216  		},
  5217  	})
  5218  	if diags.HasErrors() {
  5219  		t.Fatalf("unexpected errors: %s", diags.Err())
  5220  	}
  5221  }
  5222  
  5223  func TestContext2Plan_importIdFunc(t *testing.T) {
  5224  	p := testProvider("aws")
  5225  	m := testModule(t, "import-id-func")
  5226  	ctx := testContext2(t, &ContextOpts{
  5227  		Providers: map[addrs.Provider]providers.Factory{
  5228  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  5229  		},
  5230  	})
  5231  
  5232  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5233  		ImportedResources: []providers.ImportedResource{
  5234  			{
  5235  				TypeName: "aws_instance",
  5236  				State: cty.ObjectVal(map[string]cty.Value{
  5237  					"id": cty.StringVal("foo"),
  5238  				}),
  5239  			},
  5240  		},
  5241  	}
  5242  
  5243  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  5244  	if diags.HasErrors() {
  5245  		t.Fatalf("unexpected errors: %s", diags.Err())
  5246  	}
  5247  }
  5248  
  5249  func TestContext2Plan_importIdDataSource(t *testing.T) {
  5250  	p := testProvider("aws")
  5251  	m := testModule(t, "import-id-data-source")
  5252  
  5253  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  5254  		ResourceTypes: map[string]*configschema.Block{
  5255  			"aws_subnet": {
  5256  				Attributes: map[string]*configschema.Attribute{
  5257  					"id": {
  5258  						Type:     cty.String,
  5259  						Computed: true,
  5260  					},
  5261  				},
  5262  			},
  5263  		},
  5264  		DataSources: map[string]*configschema.Block{
  5265  			"aws_subnet": {
  5266  				Attributes: map[string]*configschema.Attribute{
  5267  					"vpc_id": {
  5268  						Type:     cty.String,
  5269  						Required: true,
  5270  					},
  5271  					"cidr_block": {
  5272  						Type:     cty.String,
  5273  						Computed: true,
  5274  					},
  5275  					"id": {
  5276  						Type:     cty.String,
  5277  						Computed: true,
  5278  					},
  5279  				},
  5280  			},
  5281  		},
  5282  	})
  5283  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  5284  		State: cty.ObjectVal(map[string]cty.Value{
  5285  			"vpc_id":     cty.StringVal("abc"),
  5286  			"cidr_block": cty.StringVal("10.0.1.0/24"),
  5287  			"id":         cty.StringVal("123"),
  5288  		}),
  5289  	}
  5290  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5291  		ImportedResources: []providers.ImportedResource{
  5292  			{
  5293  				TypeName: "aws_subnet",
  5294  				State: cty.ObjectVal(map[string]cty.Value{
  5295  					"id": cty.StringVal("foo"),
  5296  				}),
  5297  			},
  5298  		},
  5299  	}
  5300  	ctx := testContext2(t, &ContextOpts{
  5301  		Providers: map[addrs.Provider]providers.Factory{
  5302  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  5303  		},
  5304  	})
  5305  
  5306  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  5307  	if diags.HasErrors() {
  5308  		t.Fatalf("unexpected errors: %s", diags.Err())
  5309  	}
  5310  }
  5311  
  5312  func TestContext2Plan_importIdModule(t *testing.T) {
  5313  	p := testProvider("aws")
  5314  	m := testModule(t, "import-id-module")
  5315  
  5316  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  5317  		ResourceTypes: map[string]*configschema.Block{
  5318  			"aws_lb": {
  5319  				Attributes: map[string]*configschema.Attribute{
  5320  					"id": {
  5321  						Type:     cty.String,
  5322  						Computed: true,
  5323  					},
  5324  				},
  5325  			},
  5326  		},
  5327  	})
  5328  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5329  		ImportedResources: []providers.ImportedResource{
  5330  			{
  5331  				TypeName: "aws_lb",
  5332  				State: cty.ObjectVal(map[string]cty.Value{
  5333  					"id": cty.StringVal("foo"),
  5334  				}),
  5335  			},
  5336  		},
  5337  	}
  5338  	ctx := testContext2(t, &ContextOpts{
  5339  		Providers: map[addrs.Provider]providers.Factory{
  5340  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  5341  		},
  5342  	})
  5343  
  5344  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  5345  	if diags.HasErrors() {
  5346  		t.Fatalf("unexpected errors: %s", diags.Err())
  5347  	}
  5348  }
  5349  
  5350  func TestContext2Plan_importIdInvalidNull(t *testing.T) {
  5351  	p := testProvider("test")
  5352  	m := testModule(t, "import-id-invalid-null")
  5353  	ctx := testContext2(t, &ContextOpts{
  5354  		Providers: map[addrs.Provider]providers.Factory{
  5355  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5356  		},
  5357  	})
  5358  
  5359  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5360  		SetVariables: InputValues{
  5361  			"the_id": &InputValue{
  5362  				Value: cty.NullVal(cty.String),
  5363  			},
  5364  		},
  5365  	})
  5366  	if !diags.HasErrors() {
  5367  		t.Fatal("succeeded; want errors")
  5368  	}
  5369  	if got, want := diags.Err().Error(), "The import ID cannot be null"; !strings.Contains(got, want) {
  5370  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  5371  	}
  5372  }
  5373  
  5374  func TestContext2Plan_importIdInvalidUnknown(t *testing.T) {
  5375  	p := testProvider("test")
  5376  	m := testModule(t, "import-id-invalid-unknown")
  5377  	ctx := testContext2(t, &ContextOpts{
  5378  		Providers: map[addrs.Provider]providers.Factory{
  5379  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5380  		},
  5381  	})
  5382  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  5383  		ResourceTypes: map[string]*configschema.Block{
  5384  			"test_resource": {
  5385  				Attributes: map[string]*configschema.Attribute{
  5386  					"id": {
  5387  						Type:     cty.String,
  5388  						Computed: true,
  5389  					},
  5390  				},
  5391  			},
  5392  		},
  5393  	})
  5394  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  5395  		return providers.PlanResourceChangeResponse{
  5396  			PlannedState: cty.UnknownVal(cty.Object(map[string]cty.Type{
  5397  				"id": cty.String,
  5398  			})),
  5399  		}
  5400  	}
  5401  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5402  		ImportedResources: []providers.ImportedResource{
  5403  			{
  5404  				TypeName: "test_resource",
  5405  				State: cty.ObjectVal(map[string]cty.Value{
  5406  					"id": cty.StringVal("foo"),
  5407  				}),
  5408  			},
  5409  		},
  5410  	}
  5411  
  5412  	_, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  5413  	if !diags.HasErrors() {
  5414  		t.Fatal("succeeded; want errors")
  5415  	}
  5416  	if got, want := diags.Err().Error(), `The import block "id" argument depends on resource attributes that cannot be determined until apply`; !strings.Contains(got, want) {
  5417  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  5418  	}
  5419  }
  5420  
  5421  func TestContext2Plan_importIntoModuleWithGeneratedConfig(t *testing.T) {
  5422  	m := testModuleInline(t, map[string]string{
  5423  		"main.tf": `
  5424  import {
  5425    to = test_object.a
  5426    id = "123"
  5427  }
  5428  
  5429  import {
  5430    to = module.mod.test_object.a
  5431    id = "456"
  5432  }
  5433  
  5434  module "mod" {
  5435    source = "./mod"
  5436  }
  5437  `,
  5438  		"./mod/main.tf": `
  5439  resource "test_object" "a" {
  5440    test_string = "bar"
  5441  }
  5442  `,
  5443  	})
  5444  
  5445  	p := simpleMockProvider()
  5446  	ctx := testContext2(t, &ContextOpts{
  5447  		Providers: map[addrs.Provider]providers.Factory{
  5448  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5449  		},
  5450  	})
  5451  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  5452  		NewState: cty.ObjectVal(map[string]cty.Value{
  5453  			"test_string": cty.StringVal("foo"),
  5454  		}),
  5455  	}
  5456  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5457  		ImportedResources: []providers.ImportedResource{
  5458  			{
  5459  				TypeName: "test_object",
  5460  				State: cty.ObjectVal(map[string]cty.Value{
  5461  					"test_string": cty.StringVal("foo"),
  5462  				}),
  5463  			},
  5464  		},
  5465  	}
  5466  
  5467  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5468  		ImportedResources: []providers.ImportedResource{
  5469  			{
  5470  				TypeName: "test_object",
  5471  				State: cty.ObjectVal(map[string]cty.Value{
  5472  					"test_string": cty.StringVal("foo"),
  5473  				}),
  5474  			},
  5475  		},
  5476  	}
  5477  
  5478  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5479  		Mode:               plans.NormalMode,
  5480  		GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty.
  5481  	})
  5482  	if diags.HasErrors() {
  5483  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5484  	}
  5485  
  5486  	one := mustResourceInstanceAddr("test_object.a")
  5487  	two := mustResourceInstanceAddr("module.mod.test_object.a")
  5488  
  5489  	onePlan := plan.Changes.ResourceInstance(one)
  5490  	twoPlan := plan.Changes.ResourceInstance(two)
  5491  
  5492  	// This test is just to make sure things work e2e with modules and generated
  5493  	// config, so we're not too careful about the actual responses - we're just
  5494  	// happy nothing panicked. See the other import tests for actual validation
  5495  	// of responses and the like.
  5496  	if twoPlan.Action != plans.Update {
  5497  		t.Errorf("expected nested item to be updated but was %s", twoPlan.Action)
  5498  	}
  5499  
  5500  	if len(onePlan.GeneratedConfig) == 0 {
  5501  		t.Errorf("expected root item to generate config but it didn't")
  5502  	}
  5503  }
  5504  
  5505  func TestContext2Plan_importIntoNonExistentConfiguration(t *testing.T) {
  5506  	type TestConfiguration struct {
  5507  		Description         string
  5508  		inlineConfiguration map[string]string
  5509  	}
  5510  	configurations := []TestConfiguration{
  5511  		{
  5512  			Description: "Basic missing configuration",
  5513  			inlineConfiguration: map[string]string{
  5514  				"main.tf": `
  5515  import {
  5516    to   = test_object.a
  5517    id   = "123"
  5518  }
  5519  `,
  5520  			},
  5521  		},
  5522  		{
  5523  			Description: "Non-existent module",
  5524  			inlineConfiguration: map[string]string{
  5525  				"main.tf": `
  5526  import {
  5527    to   = module.mod.test_object.a
  5528    id   = "456"
  5529  }
  5530  `,
  5531  			},
  5532  		},
  5533  		{
  5534  			Description: "Wrong module key",
  5535  			inlineConfiguration: map[string]string{
  5536  				"main.tf": `
  5537  import {
  5538    to   = module.mod["non-existent"].test_object.a
  5539    id   = "123"
  5540  }
  5541  
  5542  module "mod" {
  5543    for_each = {
  5544      existent = "1"
  5545    }
  5546    source = "./mod"
  5547  }
  5548  `,
  5549  				"./mod/main.tf": `
  5550  resource "test_object" "a" {
  5551    test_string = "bar"
  5552  }
  5553  `,
  5554  			},
  5555  		},
  5556  		{
  5557  			Description: "Module key without for_each",
  5558  			inlineConfiguration: map[string]string{
  5559  				"main.tf": `
  5560  import {
  5561    to   = module.mod["non-existent"].test_object.a
  5562    id   = "123"
  5563  }
  5564  
  5565  module "mod" {
  5566    source = "./mod"
  5567  }
  5568  `,
  5569  				"./mod/main.tf": `
  5570  resource "test_object" "a" {
  5571    test_string = "bar"
  5572  }
  5573  `,
  5574  			},
  5575  		},
  5576  		{
  5577  			Description: "Non-existent resource key - in module",
  5578  			inlineConfiguration: map[string]string{
  5579  				"main.tf": `
  5580  import {
  5581    to   = module.mod.test_object.a["non-existent"]
  5582    id   = "123"
  5583  }
  5584  
  5585  module "mod" {
  5586    source = "./mod"
  5587  }
  5588  `,
  5589  				"./mod/main.tf": `
  5590  resource "test_object" "a" {
  5591    for_each = {
  5592      existent = "1"
  5593    }
  5594    test_string = "bar"
  5595  }
  5596  `,
  5597  			},
  5598  		},
  5599  		{
  5600  			Description: "Non-existent resource key - in root",
  5601  			inlineConfiguration: map[string]string{
  5602  				"main.tf": `
  5603  import {
  5604    to   = test_object.a[42]
  5605    id   = "123"
  5606  }
  5607  
  5608  resource "test_object" "a" {
  5609    test_string = "bar"
  5610  }
  5611  `,
  5612  			},
  5613  		},
  5614  		{
  5615  			Description: "Existent module key, non-existent resource key",
  5616  			inlineConfiguration: map[string]string{
  5617  				"main.tf": `
  5618  import {
  5619    to   = module.mod["existent"].test_object.b
  5620    id   = "123"
  5621  }
  5622  
  5623  module "mod" {
  5624    for_each = {
  5625      existent = "1"
  5626      existent_two = "2"
  5627    }
  5628    source = "./mod"
  5629  }
  5630  `,
  5631  				"./mod/main.tf": `
  5632  resource "test_object" "a" {
  5633    test_string = "bar"
  5634  }
  5635  `,
  5636  			},
  5637  		},
  5638  	}
  5639  
  5640  	for _, configuration := range configurations {
  5641  		t.Run(configuration.Description, func(t *testing.T) {
  5642  			m := testModuleInline(t, configuration.inlineConfiguration)
  5643  
  5644  			p := simpleMockProvider()
  5645  			ctx := testContext2(t, &ContextOpts{
  5646  				Providers: map[addrs.Provider]providers.Factory{
  5647  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5648  				},
  5649  			})
  5650  
  5651  			_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5652  				Mode: plans.NormalMode,
  5653  			})
  5654  
  5655  			if !diags.HasErrors() {
  5656  				t.Fatalf("expected error")
  5657  			}
  5658  
  5659  			var errNum int
  5660  			for _, diag := range diags {
  5661  				if diag.Severity() == tfdiags.Error {
  5662  					errNum++
  5663  				}
  5664  			}
  5665  			if errNum > 1 {
  5666  				t.Fatalf("expected a single error, but got %d", errNum)
  5667  			}
  5668  
  5669  			if !strings.Contains(diags.Err().Error(), "Configuration for import target does not exist") {
  5670  				t.Fatalf("expected error to be \"Configuration for import target does not exist\", but it was %s", diags.Err().Error())
  5671  			}
  5672  		})
  5673  	}
  5674  }
  5675  
  5676  func TestContext2Plan_importDuplication(t *testing.T) {
  5677  	type TestConfiguration struct {
  5678  		Description         string
  5679  		inlineConfiguration map[string]string
  5680  		expectedError       string
  5681  	}
  5682  	configurations := []TestConfiguration{
  5683  		{
  5684  			Description: "Duplication with dynamic address with a variable",
  5685  			inlineConfiguration: map[string]string{
  5686  				"main.tf": `
  5687  resource "test_object" "a" {
  5688   count = 2
  5689  }
  5690  
  5691  variable "address1" {
  5692   default = 1
  5693  }
  5694  
  5695  variable "address2" {
  5696   default = 1
  5697  }
  5698  
  5699  import {
  5700    to   = test_object.a[var.address1]
  5701    id   = "123"
  5702  }
  5703  
  5704  import {
  5705    to   = test_object.a[var.address2]
  5706    id   = "123"
  5707  }
  5708  `,
  5709  			},
  5710  			expectedError: "Duplicate import configuration for \"test_object.a[1]\"",
  5711  		},
  5712  		{
  5713  			Description: "Duplication with dynamic address with a resource reference",
  5714  			inlineConfiguration: map[string]string{
  5715  				"main.tf": `
  5716  resource "test_object" "example" {
  5717   test_string = "boop"
  5718  }
  5719  
  5720  resource "test_object" "a" {
  5721   for_each = toset(["boop"])
  5722  }
  5723  
  5724  import {
  5725    to   = test_object.a[test_object.example.test_string]
  5726    id   = "123"
  5727  }
  5728  
  5729  import {
  5730    to   = test_object.a[test_object.example.test_string]
  5731    id   = "123"
  5732  }
  5733  `,
  5734  			},
  5735  			expectedError: "Duplicate import configuration for \"test_object.a[\\\"boop\\\"]\"",
  5736  		},
  5737  	}
  5738  
  5739  	for _, configuration := range configurations {
  5740  		t.Run(configuration.Description, func(t *testing.T) {
  5741  			m := testModuleInline(t, configuration.inlineConfiguration)
  5742  
  5743  			p := simpleMockProvider()
  5744  			ctx := testContext2(t, &ContextOpts{
  5745  				Providers: map[addrs.Provider]providers.Factory{
  5746  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5747  				},
  5748  			})
  5749  
  5750  			p.ReadResourceResponse = &providers.ReadResourceResponse{
  5751  				NewState: cty.ObjectVal(map[string]cty.Value{
  5752  					"test_string": cty.StringVal("foo"),
  5753  				}),
  5754  			}
  5755  
  5756  			p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5757  				ImportedResources: []providers.ImportedResource{
  5758  					{
  5759  						TypeName: "test_object",
  5760  						State: cty.ObjectVal(map[string]cty.Value{
  5761  							"test_string": cty.StringVal("foo"),
  5762  						}),
  5763  					},
  5764  				},
  5765  			}
  5766  
  5767  			_, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
  5768  
  5769  			if !diags.HasErrors() {
  5770  				t.Fatalf("expected error")
  5771  			}
  5772  
  5773  			var errNum int
  5774  			for _, diag := range diags {
  5775  				if diag.Severity() == tfdiags.Error {
  5776  					errNum++
  5777  				}
  5778  			}
  5779  			if errNum > 1 {
  5780  				t.Fatalf("expected a single error, but got %d", errNum)
  5781  			}
  5782  
  5783  			if !strings.Contains(diags.Err().Error(), configuration.expectedError) {
  5784  				t.Fatalf("expected error to be %s, but it was %s", configuration.expectedError, diags.Err().Error())
  5785  			}
  5786  		})
  5787  	}
  5788  }
  5789  
  5790  func TestContext2Plan_importResourceConfigGen(t *testing.T) {
  5791  	addr := mustResourceInstanceAddr("test_object.a")
  5792  	m := testModuleInline(t, map[string]string{
  5793  		"main.tf": `
  5794  import {
  5795    to   = test_object.a
  5796    id   = "123"
  5797  }
  5798  `,
  5799  	})
  5800  
  5801  	p := simpleMockProvider()
  5802  	ctx := testContext2(t, &ContextOpts{
  5803  		Providers: map[addrs.Provider]providers.Factory{
  5804  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5805  		},
  5806  	})
  5807  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  5808  		NewState: cty.ObjectVal(map[string]cty.Value{
  5809  			"test_string": cty.StringVal("foo"),
  5810  		}),
  5811  	}
  5812  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5813  		ImportedResources: []providers.ImportedResource{
  5814  			{
  5815  				TypeName: "test_object",
  5816  				State: cty.ObjectVal(map[string]cty.Value{
  5817  					"test_string": cty.StringVal("foo"),
  5818  				}),
  5819  			},
  5820  		},
  5821  	}
  5822  
  5823  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5824  		Mode:               plans.NormalMode,
  5825  		GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty.
  5826  	})
  5827  	if diags.HasErrors() {
  5828  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5829  	}
  5830  
  5831  	t.Run(addr.String(), func(t *testing.T) {
  5832  		instPlan := plan.Changes.ResourceInstance(addr)
  5833  		if instPlan == nil {
  5834  			t.Fatalf("no plan for %s at all", addr)
  5835  		}
  5836  
  5837  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  5838  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  5839  		}
  5840  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  5841  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  5842  		}
  5843  		if got, want := instPlan.Action, plans.NoOp; got != want {
  5844  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  5845  		}
  5846  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  5847  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  5848  		}
  5849  		if instPlan.Importing.ID != "123" {
  5850  			t.Errorf("expected import change from \"123\", got non-import change")
  5851  		}
  5852  
  5853  		want := `resource "test_object" "a" {
  5854    test_bool   = null
  5855    test_list   = null
  5856    test_map    = null
  5857    test_number = null
  5858    test_string = "foo"
  5859  }`
  5860  		got := instPlan.GeneratedConfig
  5861  		if diff := cmp.Diff(want, got); len(diff) > 0 {
  5862  			t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff)
  5863  		}
  5864  	})
  5865  }
  5866  
  5867  func TestContext2Plan_importResourceConfigGenWithAlias(t *testing.T) {
  5868  	addr := mustResourceInstanceAddr("test_object.a")
  5869  	m := testModuleInline(t, map[string]string{
  5870  		"main.tf": `
  5871  provider "test" {
  5872    alias = "backup"
  5873  }
  5874  
  5875  import {
  5876    provider = test.backup
  5877    to       = test_object.a
  5878    id       = "123"
  5879  }
  5880  `,
  5881  	})
  5882  
  5883  	p := simpleMockProvider()
  5884  	ctx := testContext2(t, &ContextOpts{
  5885  		Providers: map[addrs.Provider]providers.Factory{
  5886  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  5887  		},
  5888  	})
  5889  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  5890  		NewState: cty.ObjectVal(map[string]cty.Value{
  5891  			"test_string": cty.StringVal("foo"),
  5892  		}),
  5893  	}
  5894  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  5895  		ImportedResources: []providers.ImportedResource{
  5896  			{
  5897  				TypeName: "test_object",
  5898  				State: cty.ObjectVal(map[string]cty.Value{
  5899  					"test_string": cty.StringVal("foo"),
  5900  				}),
  5901  			},
  5902  		},
  5903  	}
  5904  
  5905  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  5906  		Mode:               plans.NormalMode,
  5907  		GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty.
  5908  	})
  5909  	if diags.HasErrors() {
  5910  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  5911  	}
  5912  
  5913  	t.Run(addr.String(), func(t *testing.T) {
  5914  		instPlan := plan.Changes.ResourceInstance(addr)
  5915  		if instPlan == nil {
  5916  			t.Fatalf("no plan for %s at all", addr)
  5917  		}
  5918  
  5919  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  5920  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  5921  		}
  5922  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  5923  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  5924  		}
  5925  		if got, want := instPlan.Action, plans.NoOp; got != want {
  5926  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  5927  		}
  5928  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  5929  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  5930  		}
  5931  		if instPlan.Importing.ID != "123" {
  5932  			t.Errorf("expected import change from \"123\", got non-import change")
  5933  		}
  5934  
  5935  		want := `resource "test_object" "a" {
  5936    provider    = test.backup
  5937    test_bool   = null
  5938    test_list   = null
  5939    test_map    = null
  5940    test_number = null
  5941    test_string = "foo"
  5942  }`
  5943  		got := instPlan.GeneratedConfig
  5944  		if diff := cmp.Diff(want, got); len(diff) > 0 {
  5945  			t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff)
  5946  		}
  5947  	})
  5948  }
  5949  
  5950  func TestContext2Plan_importResourceConfigGenValidation(t *testing.T) {
  5951  	type TestConfiguration struct {
  5952  		Description         string
  5953  		inlineConfiguration map[string]string
  5954  		expectedError       string
  5955  	}
  5956  	configurations := []TestConfiguration{
  5957  		{
  5958  			Description: "Resource with index",
  5959  			inlineConfiguration: map[string]string{
  5960  				"main.tf": `
  5961  import {
  5962    to   = test_object.a[0]
  5963    id   = "123"
  5964  }
  5965  `,
  5966  			},
  5967  			expectedError: "Configuration generation for count and for_each resources not supported",
  5968  		},
  5969  		{
  5970  			Description: "Resource with dynamic index",
  5971  			inlineConfiguration: map[string]string{
  5972  				"main.tf": `
  5973  locals {
  5974    loc = "something"
  5975  }
  5976  
  5977  import {
  5978    to   = test_object.a[local.loc]
  5979    id   = "123"
  5980  }
  5981  `,
  5982  			},
  5983  			expectedError: "Configuration generation for count and for_each resources not supported",
  5984  		},
  5985  		{
  5986  			Description: "Resource in module",
  5987  			inlineConfiguration: map[string]string{
  5988  				"main.tf": `
  5989  import {
  5990    to   = module.mod.test_object.b
  5991    id   = "456"
  5992  }
  5993  
  5994  module "mod" {
  5995    source = "./mod"
  5996  }
  5997  
  5998  
  5999  `,
  6000  				"./mod/main.tf": `
  6001  resource "test_object" "a" {
  6002    test_string = "bar"
  6003  }
  6004  `,
  6005  			},
  6006  			expectedError: "Cannot generate configuration for resource inside sub-module",
  6007  		},
  6008  		{
  6009  			Description: "Resource in non-existent module",
  6010  			inlineConfiguration: map[string]string{
  6011  				"main.tf": `
  6012  import {
  6013    to   = module.mod.test_object.a
  6014    id   = "456"
  6015  }
  6016  `,
  6017  			},
  6018  			expectedError: "Cannot generate configuration for resource inside sub-module",
  6019  		},
  6020  		{
  6021  			Description: "Wrong module key",
  6022  			inlineConfiguration: map[string]string{
  6023  				"main.tf": `
  6024  import {
  6025    to   = module.mod["non-existent"].test_object.a
  6026    id   = "123"
  6027  }
  6028  
  6029  module "mod" {
  6030    for_each = {
  6031      existent = "1"
  6032    }
  6033    source = "./mod"
  6034  }
  6035  `,
  6036  				"./mod/main.tf": `
  6037  resource "test_object" "a" {
  6038    test_string = "bar"
  6039  }
  6040  `,
  6041  			},
  6042  			expectedError: "Configuration for import target does not exist",
  6043  		},
  6044  		{
  6045  			Description: "In module with module key",
  6046  			inlineConfiguration: map[string]string{
  6047  				"main.tf": `
  6048  import {
  6049    to   = module.mod["existent"].test_object.b
  6050    id   = "123"
  6051  }
  6052  
  6053  module "mod" {
  6054    for_each = {
  6055      existent = "1"
  6056    }
  6057    source = "./mod"
  6058  }
  6059  `,
  6060  				"./mod/main.tf": `
  6061  resource "test_object" "a" {
  6062    test_string = "bar"
  6063  }
  6064  `,
  6065  			},
  6066  			expectedError: "Cannot generate configuration for resource inside sub-module",
  6067  		},
  6068  		{
  6069  			Description: "Module key without for_each",
  6070  			inlineConfiguration: map[string]string{
  6071  				"main.tf": `
  6072  import {
  6073    to   = module.mod["non-existent"].test_object.a
  6074    id   = "123"
  6075  }
  6076  
  6077  module "mod" {
  6078    source = "./mod"
  6079  }
  6080  `,
  6081  				"./mod/main.tf": `
  6082  resource "test_object" "a" {
  6083    test_string = "bar"
  6084  }
  6085  `,
  6086  			},
  6087  			expectedError: "Configuration for import target does not exist",
  6088  		},
  6089  		{
  6090  			Description: "Non-existent resource key - in module",
  6091  			inlineConfiguration: map[string]string{
  6092  				"main.tf": `
  6093  import {
  6094    to   = module.mod.test_object.a["non-existent"]
  6095    id   = "123"
  6096  }
  6097  
  6098  module "mod" {
  6099    source = "./mod"
  6100  }
  6101  `,
  6102  				"./mod/main.tf": `
  6103  resource "test_object" "a" {
  6104    for_each = {
  6105      existent = "1"
  6106    }
  6107    test_string = "bar"
  6108  }
  6109  `,
  6110  			},
  6111  			expectedError: "Configuration for import target does not exist",
  6112  		},
  6113  		{
  6114  			Description: "Non-existent resource key - in root",
  6115  			inlineConfiguration: map[string]string{
  6116  				"main.tf": `
  6117  import {
  6118    to   = test_object.a[42]
  6119    id   = "123"
  6120  }
  6121  
  6122  resource "test_object" "a" {
  6123    test_string = "bar"
  6124  }
  6125  `,
  6126  			},
  6127  			expectedError: "Configuration for import target does not exist",
  6128  		},
  6129  		{
  6130  			Description: "Existent module key, non-existent resource key",
  6131  			inlineConfiguration: map[string]string{
  6132  				"main.tf": `
  6133  import {
  6134    to   = module.mod["existent"].test_object.b
  6135    id   = "123"
  6136  }
  6137  
  6138  module "mod" {
  6139    for_each = {
  6140      existent = "1"
  6141      existent_two = "2"
  6142    }
  6143    source = "./mod"
  6144  }
  6145  `,
  6146  				"./mod/main.tf": `
  6147  resource "test_object" "a" {
  6148    test_string = "bar"
  6149  }
  6150  `,
  6151  			},
  6152  			expectedError: "Cannot generate configuration for resource inside sub-module",
  6153  		},
  6154  	}
  6155  
  6156  	for _, configuration := range configurations {
  6157  		t.Run(configuration.Description, func(t *testing.T) {
  6158  			m := testModuleInline(t, configuration.inlineConfiguration)
  6159  
  6160  			p := simpleMockProvider()
  6161  			ctx := testContext2(t, &ContextOpts{
  6162  				Providers: map[addrs.Provider]providers.Factory{
  6163  					addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6164  				},
  6165  			})
  6166  
  6167  			_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  6168  				Mode:               plans.NormalMode,
  6169  				GenerateConfigPath: "generated.tf",
  6170  			})
  6171  
  6172  			if !diags.HasErrors() {
  6173  				t.Fatalf("expected error")
  6174  			}
  6175  
  6176  			var errNum int
  6177  			for _, diag := range diags {
  6178  				if diag.Severity() == tfdiags.Error {
  6179  					errNum++
  6180  				}
  6181  			}
  6182  			if errNum > 1 {
  6183  				t.Fatalf("expected a single error, but got %d", errNum)
  6184  			}
  6185  
  6186  			if !strings.Contains(diags.Err().Error(), configuration.expectedError) {
  6187  				t.Fatalf("expected error to be %s, but it was %s", configuration.expectedError, diags.Err().Error())
  6188  			}
  6189  		})
  6190  	}
  6191  }
  6192  
  6193  func TestContext2Plan_importResourceConfigGenExpandedResource(t *testing.T) {
  6194  	m := testModuleInline(t, map[string]string{
  6195  		"main.tf": `
  6196  import {
  6197    to       = test_object.a[0]
  6198    id       = "123"
  6199  }
  6200  `,
  6201  	})
  6202  
  6203  	p := simpleMockProvider()
  6204  	ctx := testContext2(t, &ContextOpts{
  6205  		Providers: map[addrs.Provider]providers.Factory{
  6206  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6207  		},
  6208  	})
  6209  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  6210  		NewState: cty.ObjectVal(map[string]cty.Value{
  6211  			"test_string": cty.StringVal("foo"),
  6212  		}),
  6213  	}
  6214  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  6215  		ImportedResources: []providers.ImportedResource{
  6216  			{
  6217  				TypeName: "test_object",
  6218  				State: cty.ObjectVal(map[string]cty.Value{
  6219  					"test_string": cty.StringVal("foo"),
  6220  				}),
  6221  			},
  6222  		},
  6223  	}
  6224  
  6225  	_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  6226  		Mode:               plans.NormalMode,
  6227  		GenerateConfigPath: "generated.tf",
  6228  	})
  6229  	if !diags.HasErrors() {
  6230  		t.Fatalf("expected plan to error, but it did not")
  6231  	}
  6232  	if !strings.Contains(diags.Err().Error(), "Configuration generation for count and for_each resources not supported") {
  6233  		t.Fatalf("expected error to be \"Config generation for count and for_each resources not supported\", but it is %s", diags.Err().Error())
  6234  	}
  6235  }
  6236  
  6237  // config generation still succeeds even when planning fails
  6238  func TestContext2Plan_importResourceConfigGenWithError(t *testing.T) {
  6239  	addr := mustResourceInstanceAddr("test_object.a")
  6240  	m := testModuleInline(t, map[string]string{
  6241  		"main.tf": `
  6242  import {
  6243    to   = test_object.a
  6244    id   = "123"
  6245  }
  6246  `,
  6247  	})
  6248  
  6249  	p := simpleMockProvider()
  6250  	ctx := testContext2(t, &ContextOpts{
  6251  		Providers: map[addrs.Provider]providers.Factory{
  6252  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6253  		},
  6254  	})
  6255  	p.PlanResourceChangeResponse = &providers.PlanResourceChangeResponse{
  6256  		PlannedState: cty.NullVal(cty.DynamicPseudoType),
  6257  		Diagnostics:  tfdiags.Diagnostics(nil).Append(errors.New("plan failed")),
  6258  	}
  6259  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  6260  		NewState: cty.ObjectVal(map[string]cty.Value{
  6261  			"test_string": cty.StringVal("foo"),
  6262  		}),
  6263  	}
  6264  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  6265  		ImportedResources: []providers.ImportedResource{
  6266  			{
  6267  				TypeName: "test_object",
  6268  				State: cty.ObjectVal(map[string]cty.Value{
  6269  					"test_string": cty.StringVal("foo"),
  6270  				}),
  6271  			},
  6272  		},
  6273  	}
  6274  
  6275  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  6276  		Mode:               plans.NormalMode,
  6277  		GenerateConfigPath: "generated.tf", // Actual value here doesn't matter, as long as it is not empty.
  6278  	})
  6279  	if !diags.HasErrors() {
  6280  		t.Fatal("expected error")
  6281  	}
  6282  
  6283  	instPlan := plan.Changes.ResourceInstance(addr)
  6284  	if instPlan == nil {
  6285  		t.Fatalf("no plan for %s at all", addr)
  6286  	}
  6287  
  6288  	want := `resource "test_object" "a" {
  6289    test_bool   = null
  6290    test_list   = null
  6291    test_map    = null
  6292    test_number = null
  6293    test_string = "foo"
  6294  }`
  6295  	got := instPlan.GeneratedConfig
  6296  	if diff := cmp.Diff(want, got); len(diff) > 0 {
  6297  		t.Errorf("got:\n%s\nwant:\n%s\ndiff:\n%s", got, want, diff)
  6298  	}
  6299  }
  6300  
  6301  func TestContext2Plan_plannedState(t *testing.T) {
  6302  	addr := mustResourceInstanceAddr("test_object.a")
  6303  	m := testModuleInline(t, map[string]string{
  6304  		"main.tf": `
  6305  resource "test_object" "a" {
  6306  	test_string = "foo"
  6307  }
  6308  
  6309  locals {
  6310    local_value = test_object.a.test_string
  6311  }
  6312  `,
  6313  	})
  6314  
  6315  	p := simpleMockProvider()
  6316  	ctx := testContext2(t, &ContextOpts{
  6317  		Providers: map[addrs.Provider]providers.Factory{
  6318  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6319  		},
  6320  	})
  6321  
  6322  	state := states.NewState()
  6323  	plan, diags := ctx.Plan(m, state, nil)
  6324  	if diags.HasErrors() {
  6325  		t.Errorf("expected no errors, but got %s", diags)
  6326  	}
  6327  
  6328  	module := state.RootModule()
  6329  
  6330  	// So, the original state shouldn't have been updated at all.
  6331  	if len(module.LocalValues) > 0 {
  6332  		t.Errorf("expected no local values in the state but found %d", len(module.LocalValues))
  6333  	}
  6334  
  6335  	if len(module.Resources) > 0 {
  6336  		t.Errorf("expected no resources in the state but found %d", len(module.LocalValues))
  6337  	}
  6338  
  6339  	// But, this makes it hard for the testing framework to valid things about
  6340  	// the returned plan. So, the plan contains the planned state:
  6341  	module = plan.PlannedState.RootModule()
  6342  
  6343  	if module.LocalValues["local_value"].AsString() != "foo" {
  6344  		t.Errorf("expected local value to be \"foo\" but was \"%s\"", module.LocalValues["local_value"].AsString())
  6345  	}
  6346  
  6347  	if module.ResourceInstance(addr.Resource).Current.Status != states.ObjectPlanned {
  6348  		t.Errorf("expected resource to be in planned state")
  6349  	}
  6350  }
  6351  
  6352  func TestContext2Plan_removedResourceBasic(t *testing.T) {
  6353  	desposedKey := states.DeposedKey("deposed")
  6354  	addr := mustResourceInstanceAddr("test_object.a")
  6355  	m := testModuleInline(t, map[string]string{
  6356  		"main.tf": `
  6357  			removed {
  6358  				from = test_object.a
  6359  			}
  6360  		`,
  6361  	})
  6362  
  6363  	state := states.BuildState(func(s *states.SyncState) {
  6364  		// The prior state tracks test_object.a, which we should be
  6365  		// removed from the state by the "removed" block in the config.
  6366  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6367  			AttrsJSON: []byte(`{}`),
  6368  			Status:    states.ObjectReady,
  6369  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6370  		s.SetResourceInstanceDeposed(
  6371  			mustResourceInstanceAddr(addr.String()),
  6372  			desposedKey,
  6373  			&states.ResourceInstanceObjectSrc{
  6374  				Status:       states.ObjectTainted,
  6375  				AttrsJSON:    []byte(`{"test_string":"old"}`),
  6376  				Dependencies: []addrs.ConfigResource{},
  6377  			},
  6378  			mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  6379  		)
  6380  	})
  6381  
  6382  	p := simpleMockProvider()
  6383  	ctx := testContext2(t, &ContextOpts{
  6384  		Providers: map[addrs.Provider]providers.Factory{
  6385  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6386  		},
  6387  	})
  6388  
  6389  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6390  		Mode: plans.NormalMode,
  6391  		ForceReplace: []addrs.AbsResourceInstance{
  6392  			addr,
  6393  		},
  6394  	})
  6395  	if diags.HasErrors() {
  6396  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6397  	}
  6398  
  6399  	for _, test := range []struct {
  6400  		deposedKey states.DeposedKey
  6401  		wantReason plans.ResourceInstanceChangeActionReason
  6402  	}{{desposedKey, plans.ResourceInstanceChangeNoReason}, {states.NotDeposed, plans.ResourceInstanceDeleteBecauseNoResourceConfig}} {
  6403  		t.Run(addr.String(), func(t *testing.T) {
  6404  			var instPlan *plans.ResourceInstanceChangeSrc
  6405  
  6406  			if test.deposedKey == states.NotDeposed {
  6407  				instPlan = plan.Changes.ResourceInstance(addr)
  6408  			} else {
  6409  				instPlan = plan.Changes.ResourceInstanceDeposed(addr, test.deposedKey)
  6410  			}
  6411  
  6412  			if instPlan == nil {
  6413  				t.Fatalf("no plan for %s at all", addr)
  6414  			}
  6415  
  6416  			if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6417  				t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6418  			}
  6419  			if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6420  				t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6421  			}
  6422  			if got, want := instPlan.Action, plans.Forget; got != want {
  6423  				t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6424  			}
  6425  			if got, want := instPlan.ActionReason, test.wantReason; got != want {
  6426  				t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6427  			}
  6428  		})
  6429  	}
  6430  }
  6431  
  6432  func TestContext2Plan_removedModuleBasic(t *testing.T) {
  6433  	desposedKey := states.DeposedKey("deposed")
  6434  	addr := mustResourceInstanceAddr("module.mod.test_object.a")
  6435  	m := testModuleInline(t, map[string]string{
  6436  		"main.tf": `
  6437  			removed {
  6438  				from = module.mod
  6439  			}
  6440  		`,
  6441  	})
  6442  
  6443  	state := states.BuildState(func(s *states.SyncState) {
  6444  		// The prior state tracks module.mod.test_object.a, which should be
  6445  		// removed from the state by the module's "removed" block in the root module config.
  6446  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6447  			AttrsJSON: []byte(`{}`),
  6448  			Status:    states.ObjectReady,
  6449  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6450  		s.SetResourceInstanceDeposed(
  6451  			mustResourceInstanceAddr(addr.String()),
  6452  			desposedKey,
  6453  			&states.ResourceInstanceObjectSrc{
  6454  				Status:       states.ObjectTainted,
  6455  				AttrsJSON:    []byte(`{"test_string":"old"}`),
  6456  				Dependencies: []addrs.ConfigResource{},
  6457  			},
  6458  			mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  6459  		)
  6460  	})
  6461  
  6462  	p := simpleMockProvider()
  6463  	ctx := testContext2(t, &ContextOpts{
  6464  		Providers: map[addrs.Provider]providers.Factory{
  6465  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6466  		},
  6467  	})
  6468  
  6469  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6470  		Mode: plans.NormalMode,
  6471  		ForceReplace: []addrs.AbsResourceInstance{
  6472  			addr,
  6473  		},
  6474  	})
  6475  	if diags.HasErrors() {
  6476  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6477  	}
  6478  
  6479  	for _, test := range []struct {
  6480  		deposedKey states.DeposedKey
  6481  		wantReason plans.ResourceInstanceChangeActionReason
  6482  	}{{desposedKey, plans.ResourceInstanceChangeNoReason}, {states.NotDeposed, plans.ResourceInstanceDeleteBecauseNoResourceConfig}} {
  6483  		t.Run(addr.String(), func(t *testing.T) {
  6484  			var instPlan *plans.ResourceInstanceChangeSrc
  6485  
  6486  			if test.deposedKey == states.NotDeposed {
  6487  				instPlan = plan.Changes.ResourceInstance(addr)
  6488  			} else {
  6489  				instPlan = plan.Changes.ResourceInstanceDeposed(addr, test.deposedKey)
  6490  			}
  6491  
  6492  			if instPlan == nil {
  6493  				t.Fatalf("no plan for %s at all", addr)
  6494  			}
  6495  
  6496  			if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6497  				t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6498  			}
  6499  			if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6500  				t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6501  			}
  6502  			if got, want := instPlan.Action, plans.Forget; got != want {
  6503  				t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6504  			}
  6505  			if got, want := instPlan.ActionReason, test.wantReason; got != want {
  6506  				t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6507  			}
  6508  		})
  6509  	}
  6510  }
  6511  
  6512  func TestContext2Plan_removedModuleForgetsAllInstances(t *testing.T) {
  6513  	addrFirst := mustResourceInstanceAddr("module.mod[0].test_object.a")
  6514  	addrSecond := mustResourceInstanceAddr("module.mod[1].test_object.a")
  6515  
  6516  	m := testModuleInline(t, map[string]string{
  6517  		"main.tf": `
  6518  			removed {
  6519  				from = module.mod
  6520  			}
  6521  		`,
  6522  	})
  6523  
  6524  	state := states.BuildState(func(s *states.SyncState) {
  6525  		// The prior state tracks module.mod[0].test_object.a and
  6526  		// module.mod[1].test_object.a, which we should be removed
  6527  		// from the state by the "removed" block in the config.
  6528  		s.SetResourceInstanceCurrent(addrFirst, &states.ResourceInstanceObjectSrc{
  6529  			AttrsJSON: []byte(`{}`),
  6530  			Status:    states.ObjectReady,
  6531  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6532  		s.SetResourceInstanceCurrent(addrSecond, &states.ResourceInstanceObjectSrc{
  6533  			AttrsJSON: []byte(`{}`),
  6534  			Status:    states.ObjectReady,
  6535  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6536  	})
  6537  
  6538  	p := simpleMockProvider()
  6539  	ctx := testContext2(t, &ContextOpts{
  6540  		Providers: map[addrs.Provider]providers.Factory{
  6541  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6542  		},
  6543  	})
  6544  
  6545  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6546  		Mode: plans.NormalMode,
  6547  		ForceReplace: []addrs.AbsResourceInstance{
  6548  			addrFirst, addrSecond,
  6549  		},
  6550  	})
  6551  	if diags.HasErrors() {
  6552  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6553  	}
  6554  
  6555  	for _, resourceInstance := range []addrs.AbsResourceInstance{addrFirst, addrSecond} {
  6556  		t.Run(resourceInstance.String(), func(t *testing.T) {
  6557  			instPlan := plan.Changes.ResourceInstance(resourceInstance)
  6558  			if instPlan == nil {
  6559  				t.Fatalf("no plan for %s at all", resourceInstance)
  6560  			}
  6561  
  6562  			if got, want := instPlan.Addr, resourceInstance; !got.Equal(want) {
  6563  				t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6564  			}
  6565  			if got, want := instPlan.PrevRunAddr, resourceInstance; !got.Equal(want) {
  6566  				t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6567  			}
  6568  			if got, want := instPlan.Action, plans.Forget; got != want {
  6569  				t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6570  			}
  6571  			if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6572  				t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6573  			}
  6574  		})
  6575  	}
  6576  }
  6577  
  6578  func TestContext2Plan_removedResourceForgetsAllInstances(t *testing.T) {
  6579  	addrFirst := mustResourceInstanceAddr("test_object.a[0]")
  6580  	addrSecond := mustResourceInstanceAddr("test_object.a[1]")
  6581  
  6582  	m := testModuleInline(t, map[string]string{
  6583  		"main.tf": `
  6584  			removed {
  6585  				from = test_object.a
  6586  			}
  6587  		`,
  6588  	})
  6589  
  6590  	state := states.BuildState(func(s *states.SyncState) {
  6591  		// The prior state tracks test_object.a[0] and
  6592  		// test_object.a[1], which we should be removed from
  6593  		// the state by the "removed" block in the config.
  6594  		s.SetResourceInstanceCurrent(addrFirst, &states.ResourceInstanceObjectSrc{
  6595  			AttrsJSON: []byte(`{}`),
  6596  			Status:    states.ObjectReady,
  6597  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6598  		s.SetResourceInstanceCurrent(addrSecond, &states.ResourceInstanceObjectSrc{
  6599  			AttrsJSON: []byte(`{}`),
  6600  			Status:    states.ObjectReady,
  6601  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6602  	})
  6603  
  6604  	p := simpleMockProvider()
  6605  	ctx := testContext2(t, &ContextOpts{
  6606  		Providers: map[addrs.Provider]providers.Factory{
  6607  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6608  		},
  6609  	})
  6610  
  6611  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6612  		Mode: plans.NormalMode,
  6613  		ForceReplace: []addrs.AbsResourceInstance{
  6614  			addrFirst, addrSecond,
  6615  		},
  6616  	})
  6617  	if diags.HasErrors() {
  6618  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6619  	}
  6620  
  6621  	for _, resourceInstance := range []addrs.AbsResourceInstance{addrFirst, addrSecond} {
  6622  		t.Run(resourceInstance.String(), func(t *testing.T) {
  6623  			instPlan := plan.Changes.ResourceInstance(resourceInstance)
  6624  			if instPlan == nil {
  6625  				t.Fatalf("no plan for %s at all", resourceInstance)
  6626  			}
  6627  
  6628  			if got, want := instPlan.Addr, resourceInstance; !got.Equal(want) {
  6629  				t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6630  			}
  6631  			if got, want := instPlan.PrevRunAddr, resourceInstance; !got.Equal(want) {
  6632  				t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6633  			}
  6634  			if got, want := instPlan.Action, plans.Forget; got != want {
  6635  				t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6636  			}
  6637  			if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6638  				t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6639  			}
  6640  		})
  6641  	}
  6642  }
  6643  
  6644  func TestContext2Plan_removedResourceInChildModuleFromParentModule(t *testing.T) {
  6645  	addr := mustResourceInstanceAddr("module.mod.test_object.a")
  6646  	m := testModuleInline(t, map[string]string{
  6647  		"main.tf": `
  6648  			module "mod" {
  6649  				  source = "./mod"
  6650  			}
  6651  
  6652  			removed {
  6653  				from = module.mod.test_object.a
  6654  			}
  6655  		`,
  6656  		"mod/main.tf": ``,
  6657  	})
  6658  
  6659  	state := states.BuildState(func(s *states.SyncState) {
  6660  		// The prior state tracks module.mod.test_object.a.a, which we should be
  6661  		// removed from the state by the "removed" block in the root config.
  6662  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6663  			AttrsJSON: []byte(`{}`),
  6664  			Status:    states.ObjectReady,
  6665  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6666  	})
  6667  
  6668  	p := simpleMockProvider()
  6669  	ctx := testContext2(t, &ContextOpts{
  6670  		Providers: map[addrs.Provider]providers.Factory{
  6671  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6672  		},
  6673  	})
  6674  
  6675  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6676  		Mode: plans.NormalMode,
  6677  		ForceReplace: []addrs.AbsResourceInstance{
  6678  			addr,
  6679  		},
  6680  	})
  6681  	if diags.HasErrors() {
  6682  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6683  	}
  6684  
  6685  	t.Run(addr.String(), func(t *testing.T) {
  6686  		instPlan := plan.Changes.ResourceInstance(addr)
  6687  		if instPlan == nil {
  6688  			t.Fatalf("no plan for %s at all", addr)
  6689  		}
  6690  
  6691  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6692  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6693  		}
  6694  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6695  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6696  		}
  6697  		if got, want := instPlan.Action, plans.Forget; got != want {
  6698  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6699  		}
  6700  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6701  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6702  		}
  6703  	})
  6704  }
  6705  
  6706  func TestContext2Plan_removedResourceInChildModuleFromChildModule(t *testing.T) {
  6707  	addr := mustResourceInstanceAddr("module.mod.test_object.a")
  6708  	m := testModuleInline(t, map[string]string{
  6709  		"main.tf": `
  6710  			module "mod" {
  6711  				  source = "./mod"
  6712  			}
  6713  		`,
  6714  		"mod/main.tf": `
  6715  			removed {
  6716  				from = test_object.a
  6717  			}
  6718  		`,
  6719  	})
  6720  
  6721  	state := states.BuildState(func(s *states.SyncState) {
  6722  		// The prior state tracks module.mod.test_object.a.a, which we should be
  6723  		// removed from the state by the "removed" block in the child mofule config.
  6724  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6725  			AttrsJSON: []byte(`{}`),
  6726  			Status:    states.ObjectReady,
  6727  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6728  	})
  6729  
  6730  	p := simpleMockProvider()
  6731  	ctx := testContext2(t, &ContextOpts{
  6732  		Providers: map[addrs.Provider]providers.Factory{
  6733  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6734  		},
  6735  	})
  6736  
  6737  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6738  		Mode: plans.NormalMode,
  6739  		ForceReplace: []addrs.AbsResourceInstance{
  6740  			addr,
  6741  		},
  6742  	})
  6743  	if diags.HasErrors() {
  6744  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6745  	}
  6746  
  6747  	t.Run(addr.String(), func(t *testing.T) {
  6748  		instPlan := plan.Changes.ResourceInstance(addr)
  6749  		if instPlan == nil {
  6750  			t.Fatalf("no plan for %s at all", addr)
  6751  		}
  6752  
  6753  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6754  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6755  		}
  6756  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6757  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6758  		}
  6759  		if got, want := instPlan.Action, plans.Forget; got != want {
  6760  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6761  		}
  6762  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6763  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6764  		}
  6765  	})
  6766  }
  6767  
  6768  func TestContext2Plan_removedResourceInGrandchildModuleFromRootModule(t *testing.T) {
  6769  	addr := mustResourceInstanceAddr("module.child.module.grandchild.test_object.a")
  6770  	m := testModuleInline(t, map[string]string{
  6771  		"main.tf": `
  6772  			module "child" {
  6773  				  source = "./child"
  6774  			}
  6775  
  6776  			removed {
  6777  				from = module.child.module.grandchild.test_object.a
  6778  			}
  6779  		`,
  6780  		"child/main.tf": ``,
  6781  	})
  6782  
  6783  	state := states.BuildState(func(s *states.SyncState) {
  6784  		// The prior state tracks module.child.module.grandchild.test_object.a,
  6785  		// which we should be removed from the state by the "removed" block in
  6786  		// the root config.
  6787  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6788  			AttrsJSON: []byte(`{}`),
  6789  			Status:    states.ObjectReady,
  6790  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6791  	})
  6792  
  6793  	p := simpleMockProvider()
  6794  	ctx := testContext2(t, &ContextOpts{
  6795  		Providers: map[addrs.Provider]providers.Factory{
  6796  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6797  		},
  6798  	})
  6799  
  6800  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6801  		Mode: plans.NormalMode,
  6802  		ForceReplace: []addrs.AbsResourceInstance{
  6803  			addr,
  6804  		},
  6805  	})
  6806  	if diags.HasErrors() {
  6807  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6808  	}
  6809  
  6810  	t.Run(addr.String(), func(t *testing.T) {
  6811  		instPlan := plan.Changes.ResourceInstance(addr)
  6812  		if instPlan == nil {
  6813  			t.Fatalf("no plan for %s at all", addr)
  6814  		}
  6815  
  6816  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6817  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6818  		}
  6819  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6820  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6821  		}
  6822  		if got, want := instPlan.Action, plans.Forget; got != want {
  6823  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6824  		}
  6825  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6826  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6827  		}
  6828  	})
  6829  }
  6830  
  6831  func TestContext2Plan_removedChildModuleForgetsResourceInGrandchildModule(t *testing.T) {
  6832  	addr := mustResourceInstanceAddr("module.child.module.grandchild.test_object.a")
  6833  	m := testModuleInline(t, map[string]string{
  6834  		"main.tf": `
  6835  			module "child" {
  6836  				  source = "./child"
  6837  			}
  6838  
  6839  			removed {
  6840  				from = module.child.module.grandchild
  6841  			}
  6842  		`,
  6843  		"child/main.tf": ``,
  6844  	})
  6845  
  6846  	state := states.BuildState(func(s *states.SyncState) {
  6847  		// The prior state tracks module.child.module.grandchild.test_object.a,
  6848  		// which we should be removed from the state by the "removed" block
  6849  		// in the root config.
  6850  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6851  			AttrsJSON: []byte(`{}`),
  6852  			Status:    states.ObjectReady,
  6853  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6854  	})
  6855  
  6856  	p := simpleMockProvider()
  6857  	ctx := testContext2(t, &ContextOpts{
  6858  		Providers: map[addrs.Provider]providers.Factory{
  6859  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6860  		},
  6861  	})
  6862  
  6863  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6864  		Mode: plans.NormalMode,
  6865  		ForceReplace: []addrs.AbsResourceInstance{
  6866  			addr,
  6867  		},
  6868  	})
  6869  	if diags.HasErrors() {
  6870  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6871  	}
  6872  
  6873  	t.Run(addr.String(), func(t *testing.T) {
  6874  		instPlan := plan.Changes.ResourceInstance(addr)
  6875  		if instPlan == nil {
  6876  			t.Fatalf("no plan for %s at all", addr)
  6877  		}
  6878  
  6879  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  6880  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6881  		}
  6882  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  6883  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6884  		}
  6885  		if got, want := instPlan.Action, plans.Forget; got != want {
  6886  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6887  		}
  6888  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoResourceConfig; got != want {
  6889  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6890  		}
  6891  	})
  6892  }
  6893  
  6894  func TestContext2Plan_movedAndRemovedResourceAtTheSameTime(t *testing.T) {
  6895  	// This is the only scenario where the "moved" and "removed" blocks can
  6896  	// coexist while referencing the same resource. In this case, the "moved" logic
  6897  	// will run first, trying to move the resource to a non-existing target.
  6898  	// Usually ,it will cause the resource to be destroyed, but because the
  6899  	// "removed" block is also present, it will be removed from the state instead.
  6900  	addrA := mustResourceInstanceAddr("test_object.a")
  6901  	addrB := mustResourceInstanceAddr("test_object.b")
  6902  	m := testModuleInline(t, map[string]string{
  6903  		"main.tf": `
  6904  			removed {
  6905  				from = test_object.b
  6906  			}
  6907  
  6908  			moved {
  6909  				from = test_object.a
  6910  				to   = test_object.b
  6911  			}
  6912  		`,
  6913  	})
  6914  
  6915  	state := states.BuildState(func(s *states.SyncState) {
  6916  		// The prior state tracks test_object.a, which we should treat as
  6917  		// test_object.b because of the "moved" block in the config.
  6918  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
  6919  			AttrsJSON: []byte(`{}`),
  6920  			Status:    states.ObjectReady,
  6921  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6922  	})
  6923  
  6924  	p := simpleMockProvider()
  6925  	ctx := testContext2(t, &ContextOpts{
  6926  		Providers: map[addrs.Provider]providers.Factory{
  6927  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6928  		},
  6929  	})
  6930  
  6931  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  6932  		Mode: plans.NormalMode,
  6933  		ForceReplace: []addrs.AbsResourceInstance{
  6934  			addrA,
  6935  		},
  6936  	})
  6937  	if diags.HasErrors() {
  6938  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  6939  	}
  6940  
  6941  	t.Run(addrA.String(), func(t *testing.T) {
  6942  		instPlan := plan.Changes.ResourceInstance(addrA)
  6943  		if instPlan != nil {
  6944  			t.Fatalf("unexpected plan for %s; should've moved to %s", addrA, addrB)
  6945  		}
  6946  	})
  6947  	t.Run(addrB.String(), func(t *testing.T) {
  6948  		instPlan := plan.Changes.ResourceInstance(addrB)
  6949  		if instPlan == nil {
  6950  			t.Fatalf("no plan for %s at all", addrB)
  6951  		}
  6952  
  6953  		if got, want := instPlan.Addr, addrB; !got.Equal(want) {
  6954  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  6955  		}
  6956  		if got, want := instPlan.PrevRunAddr, addrA; !got.Equal(want) {
  6957  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  6958  		}
  6959  		if got, want := instPlan.Action, plans.Forget; got != want {
  6960  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  6961  		}
  6962  		if got, want := instPlan.ActionReason, plans.ResourceInstanceDeleteBecauseNoMoveTarget; got != want {
  6963  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  6964  		}
  6965  	})
  6966  }
  6967  
  6968  func TestContext2Plan_removedResourceButResourceBlockStillExists(t *testing.T) {
  6969  	addr := mustResourceInstanceAddr("test_object.a")
  6970  	m := testModuleInline(t, map[string]string{
  6971  		"main.tf": `
  6972  			resource "test_object" "a" {
  6973  				test_string = "foo"
  6974  			}
  6975  
  6976  			removed {
  6977  				from = test_object.a
  6978  			}
  6979  		`,
  6980  	})
  6981  
  6982  	state := states.BuildState(func(s *states.SyncState) {
  6983  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  6984  			AttrsJSON: []byte(`{}`),
  6985  			Status:    states.ObjectReady,
  6986  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  6987  	})
  6988  
  6989  	p := simpleMockProvider()
  6990  	ctx := testContext2(t, &ContextOpts{
  6991  		Providers: map[addrs.Provider]providers.Factory{
  6992  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  6993  		},
  6994  	})
  6995  
  6996  	_, diags := ctx.Plan(m, state, &PlanOpts{
  6997  		Mode: plans.NormalMode,
  6998  		ForceReplace: []addrs.AbsResourceInstance{
  6999  			addr,
  7000  		},
  7001  	})
  7002  
  7003  	if !diags.HasErrors() {
  7004  		t.Fatal("succeeded; want errors")
  7005  	}
  7006  
  7007  	if got, want := diags.Err().Error(), "Removed resource block still exists"; !strings.Contains(got, want) {
  7008  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  7009  	}
  7010  }
  7011  
  7012  func TestContext2Plan_removedResourceButResourceBlockStillExistsInChildModule(t *testing.T) {
  7013  	addr := mustResourceInstanceAddr("module.mod.test_object.a")
  7014  	m := testModuleInline(t, map[string]string{
  7015  		"main.tf": `
  7016  			module "mod" {
  7017  				source = "./mod"
  7018  			}
  7019  			
  7020  			removed {
  7021  				from = module.mod.test_object.a
  7022  			}
  7023  		`,
  7024  		"mod/main.tf": `
  7025  			resource "test_object" "a" {
  7026  				test_string = "foo"
  7027  			}
  7028  		`,
  7029  	})
  7030  
  7031  	state := states.BuildState(func(s *states.SyncState) {
  7032  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  7033  			AttrsJSON: []byte(`{}`),
  7034  			Status:    states.ObjectReady,
  7035  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  7036  	})
  7037  
  7038  	p := simpleMockProvider()
  7039  	ctx := testContext2(t, &ContextOpts{
  7040  		Providers: map[addrs.Provider]providers.Factory{
  7041  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  7042  		},
  7043  	})
  7044  
  7045  	_, diags := ctx.Plan(m, state, &PlanOpts{
  7046  		Mode: plans.NormalMode,
  7047  		ForceReplace: []addrs.AbsResourceInstance{
  7048  			addr,
  7049  		},
  7050  	})
  7051  
  7052  	if !diags.HasErrors() {
  7053  		t.Fatal("succeeded; want errors")
  7054  	}
  7055  
  7056  	if got, want := diags.Err().Error(), "Removed resource block still exists"; !strings.Contains(got, want) {
  7057  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  7058  	}
  7059  }
  7060  
  7061  func TestContext2Plan_removedModuleButModuleBlockStillExists(t *testing.T) {
  7062  	addr := mustResourceInstanceAddr("module.mod.test_object.a")
  7063  	m := testModuleInline(t, map[string]string{
  7064  		"main.tf": `
  7065  			module "mod" {
  7066  				source = "./mod"
  7067  			}
  7068  
  7069  			removed {
  7070  				from = module.mod
  7071  			}
  7072  		`,
  7073  		"mod/main.tf": `
  7074  			resource "test_object" "a" {
  7075  				test_string = "foo"
  7076  			}
  7077  		`,
  7078  	})
  7079  
  7080  	state := states.BuildState(func(s *states.SyncState) {
  7081  		s.SetResourceInstanceCurrent(addr, &states.ResourceInstanceObjectSrc{
  7082  			AttrsJSON: []byte(`{}`),
  7083  			Status:    states.ObjectReady,
  7084  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
  7085  	})
  7086  
  7087  	p := simpleMockProvider()
  7088  	ctx := testContext2(t, &ContextOpts{
  7089  		Providers: map[addrs.Provider]providers.Factory{
  7090  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  7091  		},
  7092  	})
  7093  
  7094  	_, diags := ctx.Plan(m, state, &PlanOpts{
  7095  		Mode: plans.NormalMode,
  7096  		ForceReplace: []addrs.AbsResourceInstance{
  7097  			addr,
  7098  		},
  7099  	})
  7100  
  7101  	if !diags.HasErrors() {
  7102  		t.Fatal("succeeded; want errors")
  7103  	}
  7104  
  7105  	if got, want := diags.Err().Error(), "Removed module block still exists"; !strings.Contains(got, want) {
  7106  		t.Fatalf("wrong error:\ngot:  %s\nwant: message containing %q", got, want)
  7107  	}
  7108  }
  7109  
  7110  func TestContext2Plan_importResourceWithSensitiveDataSource(t *testing.T) {
  7111  	addr := mustResourceInstanceAddr("test_object.b")
  7112  	m := testModuleInline(t, map[string]string{
  7113  		"main.tf": `
  7114  			data "test_data_source" "a" {
  7115  			}
  7116  			resource "test_object" "b" {
  7117  				test_string = data.test_data_source.a.test_string
  7118  			}
  7119  			import {
  7120  			  to   = test_object.b
  7121  			  id   = "123"
  7122  			}
  7123  		`,
  7124  	})
  7125  
  7126  	p := &MockProvider{
  7127  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  7128  			Provider: providers.Schema{Block: simpleTestSchema()},
  7129  			ResourceTypes: map[string]providers.Schema{
  7130  				"test_object": {Block: simpleTestSchema()},
  7131  			},
  7132  			DataSources: map[string]providers.Schema{
  7133  				"test_data_source": {Block: &configschema.Block{
  7134  					Attributes: map[string]*configschema.Attribute{
  7135  						"test_string": {
  7136  							Type:      cty.String,
  7137  							Computed:  true,
  7138  							Sensitive: true,
  7139  						},
  7140  					},
  7141  				}},
  7142  			},
  7143  		},
  7144  	}
  7145  	hook := new(MockHook)
  7146  	ctx := testContext2(t, &ContextOpts{
  7147  		Hooks: []Hook{hook},
  7148  		Providers: map[addrs.Provider]providers.Factory{
  7149  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  7150  		},
  7151  	})
  7152  
  7153  	p.ReadDataSourceResponse = &providers.ReadDataSourceResponse{
  7154  		State: cty.ObjectVal(map[string]cty.Value{
  7155  			"test_string": cty.StringVal("foo"),
  7156  		}),
  7157  	}
  7158  
  7159  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  7160  		NewState: cty.ObjectVal(map[string]cty.Value{
  7161  			"test_string": cty.StringVal("foo"),
  7162  		}),
  7163  	}
  7164  	p.ImportResourceStateResponse = &providers.ImportResourceStateResponse{
  7165  		ImportedResources: []providers.ImportedResource{
  7166  			{
  7167  				TypeName: "test_object",
  7168  				State: cty.ObjectVal(map[string]cty.Value{
  7169  					"test_string": cty.StringVal("foo"),
  7170  				}),
  7171  			},
  7172  		},
  7173  	}
  7174  
  7175  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
  7176  	if diags.HasErrors() {
  7177  		t.Fatalf("unexpected errors\n%s", diags.Err().Error())
  7178  	}
  7179  
  7180  	t.Run(addr.String(), func(t *testing.T) {
  7181  		instPlan := plan.Changes.ResourceInstance(addr)
  7182  		if instPlan == nil {
  7183  			t.Fatalf("no plan for %s at all", addr)
  7184  		}
  7185  
  7186  		if got, want := instPlan.Addr, addr; !got.Equal(want) {
  7187  			t.Errorf("wrong current address\ngot:  %s\nwant: %s", got, want)
  7188  		}
  7189  		if got, want := instPlan.PrevRunAddr, addr; !got.Equal(want) {
  7190  			t.Errorf("wrong previous run address\ngot:  %s\nwant: %s", got, want)
  7191  		}
  7192  		if got, want := instPlan.Action, plans.NoOp; got != want {
  7193  			t.Errorf("wrong planned action\ngot:  %s\nwant: %s", got, want)
  7194  		}
  7195  		if got, want := instPlan.ActionReason, plans.ResourceInstanceChangeNoReason; got != want {
  7196  			t.Errorf("wrong action reason\ngot:  %s\nwant: %s", got, want)
  7197  		}
  7198  		if instPlan.Importing.ID != "123" {
  7199  			t.Errorf("expected import change from \"123\", got non-import change")
  7200  		}
  7201  
  7202  		if !hook.PrePlanImportCalled {
  7203  			t.Fatalf("PostPlanImport hook not called")
  7204  		}
  7205  		if addr, wantAddr := hook.PrePlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  7206  			t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  7207  		}
  7208  
  7209  		if !hook.PostPlanImportCalled {
  7210  			t.Fatalf("PostPlanImport hook not called")
  7211  		}
  7212  		if addr, wantAddr := hook.PostPlanImportAddr, instPlan.Addr; !addr.Equal(wantAddr) {
  7213  			t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  7214  		}
  7215  	})
  7216  }