github.com/opentofu/opentofu@v1.7.1/internal/tofu/context_apply2_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  	"strings"
    13  	"sync"
    14  	"testing"
    15  	"time"
    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/opentofu/opentofu/internal/addrs"
    22  	"github.com/opentofu/opentofu/internal/checks"
    23  	"github.com/opentofu/opentofu/internal/configs/configschema"
    24  	"github.com/opentofu/opentofu/internal/lang/marks"
    25  	"github.com/opentofu/opentofu/internal/plans"
    26  	"github.com/opentofu/opentofu/internal/providers"
    27  	"github.com/opentofu/opentofu/internal/states"
    28  	"github.com/opentofu/opentofu/internal/tfdiags"
    29  )
    30  
    31  // Test that the PreApply hook is called with the correct deposed key
    32  func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) {
    33  	m := testModule(t, "apply-cbd-deposed-only")
    34  	p := testProvider("aws")
    35  	p.PlanResourceChangeFn = testDiffFn
    36  	p.ApplyResourceChangeFn = testApplyFn
    37  
    38  	deposedKey := states.NewDeposedKey()
    39  
    40  	state := states.NewState()
    41  	root := state.EnsureModule(addrs.RootModuleInstance)
    42  	root.SetResourceInstanceCurrent(
    43  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    44  		&states.ResourceInstanceObjectSrc{
    45  			Status:    states.ObjectReady,
    46  			AttrsJSON: []byte(`{"id":"bar"}`),
    47  		},
    48  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
    49  	)
    50  	root.SetResourceInstanceDeposed(
    51  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    52  		deposedKey,
    53  		&states.ResourceInstanceObjectSrc{
    54  			Status:    states.ObjectTainted,
    55  			AttrsJSON: []byte(`{"id":"foo"}`),
    56  		},
    57  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
    58  	)
    59  
    60  	hook := new(MockHook)
    61  	ctx := testContext2(t, &ContextOpts{
    62  		Hooks: []Hook{hook},
    63  		Providers: map[addrs.Provider]providers.Factory{
    64  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
    65  		},
    66  	})
    67  
    68  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    69  	if diags.HasErrors() {
    70  		t.Fatalf("diags: %s", diags.Err())
    71  	} else {
    72  		t.Logf(legacyDiffComparisonString(plan.Changes))
    73  	}
    74  
    75  	_, diags = ctx.Apply(plan, m)
    76  	if diags.HasErrors() {
    77  		t.Fatalf("diags: %s", diags.Err())
    78  	}
    79  
    80  	// Verify PreApply was called correctly
    81  	if !hook.PreApplyCalled {
    82  		t.Fatalf("PreApply hook not called")
    83  	}
    84  	if addr, wantAddr := hook.PreApplyAddr, mustResourceInstanceAddr("aws_instance.bar"); !addr.Equal(wantAddr) {
    85  		t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
    86  	}
    87  	if gen := hook.PreApplyGen; gen != deposedKey {
    88  		t.Errorf("expected gen to be %q, but was %q", deposedKey, gen)
    89  	}
    90  }
    91  
    92  func TestContext2Apply_destroyWithDataSourceExpansion(t *testing.T) {
    93  	// While managed resources store their destroy-time dependencies, data
    94  	// sources do not. This means that if a provider were only included in a
    95  	// destroy graph because of data sources, it could have dependencies which
    96  	// are not correctly ordered. Here we verify that the provider is not
    97  	// included in the destroy operation, and all dependency evaluations
    98  	// succeed.
    99  
   100  	m := testModuleInline(t, map[string]string{
   101  		"main.tf": `
   102  module "mod" {
   103    source = "./mod"
   104  }
   105  
   106  provider "other" {
   107    foo = module.mod.data
   108  }
   109  
   110  # this should not require the provider be present during destroy
   111  data "other_data_source" "a" {
   112  }
   113  `,
   114  
   115  		"mod/main.tf": `
   116  data "test_data_source" "a" {
   117    count = 1
   118  }
   119  
   120  data "test_data_source" "b" {
   121    count = data.test_data_source.a[0].foo == "ok" ? 1 : 0
   122  }
   123  
   124  output "data" {
   125    value = data.test_data_source.a[0].foo == "ok" ? data.test_data_source.b[0].foo : "nope"
   126  }
   127  `,
   128  	})
   129  
   130  	testP := testProvider("test")
   131  	otherP := testProvider("other")
   132  
   133  	readData := func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
   134  		return providers.ReadDataSourceResponse{
   135  			State: cty.ObjectVal(map[string]cty.Value{
   136  				"id":  cty.StringVal("data_source"),
   137  				"foo": cty.StringVal("ok"),
   138  			}),
   139  		}
   140  	}
   141  
   142  	testP.ReadDataSourceFn = readData
   143  	otherP.ReadDataSourceFn = readData
   144  
   145  	ps := map[addrs.Provider]providers.Factory{
   146  		addrs.NewDefaultProvider("test"):  testProviderFuncFixed(testP),
   147  		addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherP),
   148  	}
   149  
   150  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   151  		foo := req.Config.GetAttr("foo")
   152  		if foo.IsNull() || foo.AsString() != "ok" {
   153  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect config val: %#v\n", foo))
   154  		}
   155  		return resp
   156  	}
   157  
   158  	ctx := testContext2(t, &ContextOpts{
   159  		Providers: ps,
   160  	})
   161  
   162  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   163  	if diags.HasErrors() {
   164  		t.Fatal(diags.Err())
   165  	}
   166  
   167  	_, diags = ctx.Apply(plan, m)
   168  	if diags.HasErrors() {
   169  		t.Fatal(diags.Err())
   170  	}
   171  
   172  	// now destroy the whole thing
   173  	ctx = testContext2(t, &ContextOpts{
   174  		Providers: ps,
   175  	})
   176  
   177  	plan, diags = ctx.Plan(m, states.NewState(), &PlanOpts{
   178  		Mode: plans.DestroyMode,
   179  	})
   180  	if diags.HasErrors() {
   181  		t.Fatal(diags.Err())
   182  	}
   183  
   184  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   185  		// should not be used to destroy data sources
   186  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("provider should not be used"))
   187  		return resp
   188  	}
   189  
   190  	_, diags = ctx.Apply(plan, m)
   191  	if diags.HasErrors() {
   192  		t.Fatal(diags.Err())
   193  	}
   194  }
   195  
   196  func TestContext2Apply_destroyThenUpdate(t *testing.T) {
   197  	m := testModuleInline(t, map[string]string{
   198  		"main.tf": `
   199  resource "test_instance" "a" {
   200  	value = "udpated"
   201  }
   202  `,
   203  	})
   204  
   205  	p := testProvider("test")
   206  	p.PlanResourceChangeFn = testDiffFn
   207  
   208  	var orderMu sync.Mutex
   209  	var order []string
   210  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   211  		id := req.PriorState.GetAttr("id").AsString()
   212  		if id == "b" {
   213  			// slow down the b destroy, since a should wait for it
   214  			time.Sleep(100 * time.Millisecond)
   215  		}
   216  
   217  		orderMu.Lock()
   218  		order = append(order, id)
   219  		orderMu.Unlock()
   220  
   221  		resp.NewState = req.PlannedState
   222  		return resp
   223  	}
   224  
   225  	addrA := mustResourceInstanceAddr(`test_instance.a`)
   226  	addrB := mustResourceInstanceAddr(`test_instance.b`)
   227  
   228  	state := states.BuildState(func(s *states.SyncState) {
   229  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
   230  			AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`),
   231  			Status:    states.ObjectReady,
   232  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
   233  
   234  		// test_instance.b depended on test_instance.a, and therefor should be
   235  		// destroyed before any changes to test_instance.a
   236  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
   237  			AttrsJSON:    []byte(`{"id":"b"}`),
   238  			Status:       states.ObjectReady,
   239  			Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
   240  		}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`))
   241  	})
   242  
   243  	ctx := testContext2(t, &ContextOpts{
   244  		Providers: map[addrs.Provider]providers.Factory{
   245  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   246  		},
   247  	})
   248  
   249  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   250  	assertNoErrors(t, diags)
   251  
   252  	_, diags = ctx.Apply(plan, m)
   253  	if diags.HasErrors() {
   254  		t.Fatal(diags.Err())
   255  	}
   256  
   257  	if order[0] != "b" {
   258  		t.Fatalf("expected apply order [b, a], got: %v\n", order)
   259  	}
   260  }
   261  
   262  // verify that dependencies are updated in the state during refresh and apply
   263  func TestApply_updateDependencies(t *testing.T) {
   264  	state := states.NewState()
   265  	root := state.EnsureModule(addrs.RootModuleInstance)
   266  
   267  	fooAddr := mustResourceInstanceAddr("aws_instance.foo")
   268  	barAddr := mustResourceInstanceAddr("aws_instance.bar")
   269  	bazAddr := mustResourceInstanceAddr("aws_instance.baz")
   270  	bamAddr := mustResourceInstanceAddr("aws_instance.bam")
   271  	binAddr := mustResourceInstanceAddr("aws_instance.bin")
   272  	root.SetResourceInstanceCurrent(
   273  		fooAddr.Resource,
   274  		&states.ResourceInstanceObjectSrc{
   275  			Status:    states.ObjectReady,
   276  			AttrsJSON: []byte(`{"id":"foo"}`),
   277  			Dependencies: []addrs.ConfigResource{
   278  				bazAddr.ContainingResource().Config(),
   279  			},
   280  		},
   281  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   282  	)
   283  	root.SetResourceInstanceCurrent(
   284  		binAddr.Resource,
   285  		&states.ResourceInstanceObjectSrc{
   286  			Status:    states.ObjectReady,
   287  			AttrsJSON: []byte(`{"id":"bin","type":"aws_instance","unknown":"ok"}`),
   288  			Dependencies: []addrs.ConfigResource{
   289  				bazAddr.ContainingResource().Config(),
   290  			},
   291  		},
   292  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   293  	)
   294  	root.SetResourceInstanceCurrent(
   295  		bazAddr.Resource,
   296  		&states.ResourceInstanceObjectSrc{
   297  			Status:    states.ObjectReady,
   298  			AttrsJSON: []byte(`{"id":"baz"}`),
   299  			Dependencies: []addrs.ConfigResource{
   300  				// Existing dependencies should not be removed from orphaned instances
   301  				bamAddr.ContainingResource().Config(),
   302  			},
   303  		},
   304  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   305  	)
   306  	root.SetResourceInstanceCurrent(
   307  		barAddr.Resource,
   308  		&states.ResourceInstanceObjectSrc{
   309  			Status:    states.ObjectReady,
   310  			AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`),
   311  		},
   312  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
   313  	)
   314  
   315  	m := testModuleInline(t, map[string]string{
   316  		"main.tf": `
   317  resource "aws_instance" "bar" {
   318    foo = aws_instance.foo.id
   319  }
   320  
   321  resource "aws_instance" "foo" {
   322  }
   323  
   324  resource "aws_instance" "bin" {
   325  }
   326  `,
   327  	})
   328  
   329  	p := testProvider("aws")
   330  
   331  	ctx := testContext2(t, &ContextOpts{
   332  		Providers: map[addrs.Provider]providers.Factory{
   333  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
   334  		},
   335  	})
   336  
   337  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   338  	assertNoErrors(t, diags)
   339  
   340  	bar := plan.PriorState.ResourceInstance(barAddr)
   341  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   342  		t.Fatalf("bar should depend on foo after refresh, but got %s", bar.Current.Dependencies)
   343  	}
   344  
   345  	foo := plan.PriorState.ResourceInstance(fooAddr)
   346  	if len(foo.Current.Dependencies) == 0 || !foo.Current.Dependencies[0].Equal(bazAddr.ContainingResource().Config()) {
   347  		t.Fatalf("foo should depend on baz after refresh because of the update, but got %s", foo.Current.Dependencies)
   348  	}
   349  
   350  	bin := plan.PriorState.ResourceInstance(binAddr)
   351  	if len(bin.Current.Dependencies) != 0 {
   352  		t.Fatalf("bin should depend on nothing after refresh because there is no change, but got %s", bin.Current.Dependencies)
   353  	}
   354  
   355  	baz := plan.PriorState.ResourceInstance(bazAddr)
   356  	if len(baz.Current.Dependencies) == 0 || !baz.Current.Dependencies[0].Equal(bamAddr.ContainingResource().Config()) {
   357  		t.Fatalf("baz should depend on bam after refresh, but got %s", baz.Current.Dependencies)
   358  	}
   359  
   360  	state, diags = ctx.Apply(plan, m)
   361  	if diags.HasErrors() {
   362  		t.Fatal(diags.Err())
   363  	}
   364  
   365  	bar = state.ResourceInstance(barAddr)
   366  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   367  		t.Fatalf("bar should still depend on foo after apply, but got %s", bar.Current.Dependencies)
   368  	}
   369  
   370  	foo = state.ResourceInstance(fooAddr)
   371  	if len(foo.Current.Dependencies) != 0 {
   372  		t.Fatalf("foo should have no deps after apply, but got %s", foo.Current.Dependencies)
   373  	}
   374  
   375  }
   376  
   377  func TestContext2Apply_additionalSensitiveFromState(t *testing.T) {
   378  	// Ensure we're not trying to double-mark values decoded from state
   379  	m := testModuleInline(t, map[string]string{
   380  		"main.tf": `
   381  variable "secret" {
   382    sensitive = true
   383    default = ["secret"]
   384  }
   385  
   386  resource "test_resource" "a" {
   387    sensitive_attr = var.secret
   388  }
   389  
   390  resource "test_resource" "b" {
   391    value = test_resource.a.id
   392  }
   393  `,
   394  	})
   395  
   396  	p := new(MockProvider)
   397  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   398  		ResourceTypes: map[string]*configschema.Block{
   399  			"test_resource": {
   400  				Attributes: map[string]*configschema.Attribute{
   401  					"id": {
   402  						Type:     cty.String,
   403  						Computed: true,
   404  					},
   405  					"value": {
   406  						Type:     cty.String,
   407  						Optional: true,
   408  					},
   409  					"sensitive_attr": {
   410  						Type:      cty.List(cty.String),
   411  						Optional:  true,
   412  						Sensitive: true,
   413  					},
   414  				},
   415  			},
   416  		},
   417  	})
   418  
   419  	state := states.BuildState(func(s *states.SyncState) {
   420  		s.SetResourceInstanceCurrent(
   421  			mustResourceInstanceAddr(`test_resource.a`),
   422  			&states.ResourceInstanceObjectSrc{
   423  				AttrsJSON: []byte(`{"id":"a","sensitive_attr":["secret"]}`),
   424  				AttrSensitivePaths: []cty.PathValueMarks{
   425  					{
   426  						Path:  cty.GetAttrPath("sensitive_attr"),
   427  						Marks: cty.NewValueMarks(marks.Sensitive),
   428  					},
   429  				},
   430  				Status: states.ObjectReady,
   431  			}, mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   432  		)
   433  	})
   434  
   435  	ctx := testContext2(t, &ContextOpts{
   436  		Providers: map[addrs.Provider]providers.Factory{
   437  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   438  		},
   439  	})
   440  
   441  	plan, diags := ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   442  	assertNoErrors(t, diags)
   443  
   444  	_, diags = ctx.Apply(plan, m)
   445  	if diags.HasErrors() {
   446  		t.Fatal(diags.ErrWithWarnings())
   447  	}
   448  }
   449  
   450  func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) {
   451  	// Ensure we're not trying to double-mark values decoded from state
   452  	m := testModuleInline(t, map[string]string{
   453  		"main.tf": `
   454  module "mod" {
   455    source = "./mod"
   456  }
   457  
   458  resource "test_object" "a" {
   459    test_string = module.mod.out
   460  }
   461  `,
   462  
   463  		"mod/main.tf": `
   464  variable "in" {
   465    sensitive = true
   466    default = "foo"
   467  }
   468  output "out" {
   469    value = var.in
   470  }
   471  `,
   472  	})
   473  
   474  	p := simpleMockProvider()
   475  
   476  	ctx := testContext2(t, &ContextOpts{
   477  		Providers: map[addrs.Provider]providers.Factory{
   478  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   479  		},
   480  	})
   481  
   482  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   483  	assertNoErrors(t, diags)
   484  
   485  	state, diags := ctx.Apply(plan, m)
   486  	if diags.HasErrors() {
   487  		t.Fatal(diags.ErrWithWarnings())
   488  	}
   489  
   490  	obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a"))
   491  	if len(obj.Current.AttrSensitivePaths) != 1 {
   492  		t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths)
   493  	}
   494  
   495  	plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
   496  	assertNoErrors(t, diags)
   497  
   498  	// make sure the same marks are compared in the next plan as well
   499  	for _, c := range plan.Changes.Resources {
   500  		if c.Action != plans.NoOp {
   501  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   502  		}
   503  	}
   504  }
   505  
   506  func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) {
   507  	// The impure function call should not cause a planned change with
   508  	// ignore_changes
   509  	m := testModuleInline(t, map[string]string{
   510  		"main.tf": `
   511  variable "pw" {
   512    sensitive = true
   513    default = "foo"
   514  }
   515  
   516  resource "test_object" "x" {
   517    test_map = {
   518  	string = "X${bcrypt(var.pw)}"
   519    }
   520    lifecycle {
   521      ignore_changes = [ test_map["string"] ]
   522    }
   523  }
   524  
   525  resource "test_object" "y" {
   526    test_map = {
   527  	string = "X${bcrypt(var.pw)}"
   528    }
   529    lifecycle {
   530      ignore_changes = [ test_map ]
   531    }
   532  }
   533  
   534  `,
   535  	})
   536  
   537  	p := simpleMockProvider()
   538  
   539  	ctx := testContext2(t, &ContextOpts{
   540  		Providers: map[addrs.Provider]providers.Factory{
   541  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   542  		},
   543  	})
   544  
   545  	plan, diags := ctx.Plan(m, states.NewState(), SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   546  	assertNoErrors(t, diags)
   547  
   548  	state, diags := ctx.Apply(plan, m)
   549  	assertNoErrors(t, diags)
   550  
   551  	// FINAL PLAN:
   552  	plan, diags = ctx.Plan(m, state, SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables)))
   553  	assertNoErrors(t, diags)
   554  
   555  	// make sure the same marks are compared in the next plan as well
   556  	for _, c := range plan.Changes.Resources {
   557  		if c.Action != plans.NoOp {
   558  			t.Logf("marks before: %#v", c.BeforeValMarks)
   559  			t.Logf("marks after:  %#v", c.AfterValMarks)
   560  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   561  		}
   562  	}
   563  }
   564  
   565  func TestContext2Apply_destroyWithDeposed(t *testing.T) {
   566  	m := testModuleInline(t, map[string]string{
   567  		"main.tf": `
   568  resource "test_object" "x" {
   569    test_string = "ok"
   570    lifecycle {
   571      create_before_destroy = true
   572    }
   573  }`,
   574  	})
   575  
   576  	p := simpleMockProvider()
   577  
   578  	deposedKey := states.NewDeposedKey()
   579  
   580  	state := states.NewState()
   581  	root := state.EnsureModule(addrs.RootModuleInstance)
   582  	root.SetResourceInstanceDeposed(
   583  		mustResourceInstanceAddr("test_object.x").Resource,
   584  		deposedKey,
   585  		&states.ResourceInstanceObjectSrc{
   586  			Status:    states.ObjectTainted,
   587  			AttrsJSON: []byte(`{"test_string":"deposed"}`),
   588  		},
   589  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   590  	)
   591  
   592  	ctx := testContext2(t, &ContextOpts{
   593  		Providers: map[addrs.Provider]providers.Factory{
   594  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   595  		},
   596  	})
   597  
   598  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   599  		Mode: plans.DestroyMode,
   600  	})
   601  	if diags.HasErrors() {
   602  		t.Fatalf("plan: %s", diags.Err())
   603  	}
   604  
   605  	_, diags = ctx.Apply(plan, m)
   606  	if diags.HasErrors() {
   607  		t.Fatalf("apply: %s", diags.Err())
   608  	}
   609  
   610  }
   611  
   612  func TestContext2Apply_nullableVariables(t *testing.T) {
   613  	m := testModule(t, "apply-nullable-variables")
   614  	state := states.NewState()
   615  	ctx := testContext2(t, &ContextOpts{})
   616  	plan, diags := ctx.Plan(m, state, &PlanOpts{})
   617  	if diags.HasErrors() {
   618  		t.Fatalf("plan: %s", diags.Err())
   619  	}
   620  	state, diags = ctx.Apply(plan, m)
   621  	if diags.HasErrors() {
   622  		t.Fatalf("apply: %s", diags.Err())
   623  	}
   624  
   625  	outputs := state.Module(addrs.RootModuleInstance).OutputValues
   626  	// we check for null outputs be seeing that they don't exists
   627  	if _, ok := outputs["nullable_null_default"]; ok {
   628  		t.Error("nullable_null_default: expected no output value")
   629  	}
   630  	if _, ok := outputs["nullable_non_null_default"]; ok {
   631  		t.Error("nullable_non_null_default: expected no output value")
   632  	}
   633  	if _, ok := outputs["nullable_no_default"]; ok {
   634  		t.Error("nullable_no_default: expected no output value")
   635  	}
   636  
   637  	if v := outputs["non_nullable_default"].Value; v.AsString() != "ok" {
   638  		t.Fatalf("incorrect 'non_nullable_default' output value: %#v\n", v)
   639  	}
   640  	if v := outputs["non_nullable_no_default"].Value; v.AsString() != "ok" {
   641  		t.Fatalf("incorrect 'non_nullable_no_default' output value: %#v\n", v)
   642  	}
   643  }
   644  
   645  func TestContext2Apply_targetedDestroyWithMoved(t *testing.T) {
   646  	m := testModuleInline(t, map[string]string{
   647  		"main.tf": `
   648  module "modb" {
   649    source = "./mod"
   650    for_each = toset(["a", "b"])
   651  }
   652  `,
   653  		"./mod/main.tf": `
   654  resource "test_object" "a" {
   655  }
   656  
   657  module "sub" {
   658    for_each = toset(["a", "b"])
   659    source = "./sub"
   660  }
   661  
   662  moved {
   663    from = module.old
   664    to = module.sub
   665  }
   666  `,
   667  		"./mod/sub/main.tf": `
   668  resource "test_object" "s" {
   669  }
   670  `})
   671  
   672  	p := simpleMockProvider()
   673  
   674  	ctx := testContext2(t, &ContextOpts{
   675  		Providers: map[addrs.Provider]providers.Factory{
   676  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   677  		},
   678  	})
   679  
   680  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   681  	assertNoErrors(t, diags)
   682  
   683  	state, diags := ctx.Apply(plan, m)
   684  	assertNoErrors(t, diags)
   685  
   686  	// destroy only a single instance not included in the moved statements
   687  	_, diags = ctx.Plan(m, state, &PlanOpts{
   688  		Mode:    plans.DestroyMode,
   689  		Targets: []addrs.Targetable{mustResourceInstanceAddr(`module.modb["a"].test_object.a`)},
   690  	})
   691  	assertNoErrors(t, diags)
   692  }
   693  
   694  func TestContext2Apply_graphError(t *testing.T) {
   695  	m := testModuleInline(t, map[string]string{
   696  		"main.tf": `
   697  resource "test_object" "a" {
   698    test_string = "ok"
   699  }
   700  
   701  resource "test_object" "b" {
   702    test_string = test_object.a.test_string
   703  }
   704  `,
   705  	})
   706  
   707  	p := simpleMockProvider()
   708  
   709  	state := states.NewState()
   710  	root := state.EnsureModule(addrs.RootModuleInstance)
   711  	root.SetResourceInstanceCurrent(
   712  		mustResourceInstanceAddr("test_object.a").Resource,
   713  		&states.ResourceInstanceObjectSrc{
   714  			Status:    states.ObjectTainted,
   715  			AttrsJSON: []byte(`{"test_string":"ok"}`),
   716  		},
   717  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   718  	)
   719  	root.SetResourceInstanceCurrent(
   720  		mustResourceInstanceAddr("test_object.b").Resource,
   721  		&states.ResourceInstanceObjectSrc{
   722  			Status:    states.ObjectTainted,
   723  			AttrsJSON: []byte(`{"test_string":"ok"}`),
   724  		},
   725  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
   726  	)
   727  
   728  	ctx := testContext2(t, &ContextOpts{
   729  		Providers: map[addrs.Provider]providers.Factory{
   730  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   731  		},
   732  	})
   733  
   734  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   735  		Mode: plans.DestroyMode,
   736  	})
   737  	if diags.HasErrors() {
   738  		t.Fatalf("plan: %s", diags.Err())
   739  	}
   740  
   741  	// We're going to corrupt the stored state so that the dependencies will
   742  	// cause a cycle when building the apply graph.
   743  	testObjA := plan.PriorState.Modules[""].Resources["test_object.a"].Instances[addrs.NoKey].Current
   744  	testObjA.Dependencies = append(testObjA.Dependencies, mustResourceInstanceAddr("test_object.b").ContainingResource().Config())
   745  
   746  	_, diags = ctx.Apply(plan, m)
   747  	if !diags.HasErrors() {
   748  		t.Fatal("expected cycle error from apply")
   749  	}
   750  }
   751  
   752  func TestContext2Apply_resourcePostcondition(t *testing.T) {
   753  	m := testModuleInline(t, map[string]string{
   754  		"main.tf": `
   755  variable "boop" {
   756    type = string
   757  }
   758  
   759  resource "test_resource" "a" {
   760  	value = var.boop
   761  }
   762  
   763  resource "test_resource" "b" {
   764    value = test_resource.a.output
   765    lifecycle {
   766      postcondition {
   767        condition     = self.output != ""
   768        error_message = "Output must not be blank."
   769      }
   770    }
   771  }
   772  
   773  resource "test_resource" "c" {
   774    value = test_resource.b.output
   775  }
   776  `,
   777  	})
   778  
   779  	p := testProvider("test")
   780  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   781  		ResourceTypes: map[string]*configschema.Block{
   782  			"test_resource": {
   783  				Attributes: map[string]*configschema.Attribute{
   784  					"value": {
   785  						Type:     cty.String,
   786  						Required: true,
   787  					},
   788  					"output": {
   789  						Type:     cty.String,
   790  						Computed: true,
   791  					},
   792  				},
   793  			},
   794  		},
   795  	})
   796  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
   797  		m := req.ProposedNewState.AsValueMap()
   798  		m["output"] = cty.UnknownVal(cty.String)
   799  
   800  		resp.PlannedState = cty.ObjectVal(m)
   801  		resp.LegacyTypeSystem = true
   802  		return resp
   803  	}
   804  	ctx := testContext2(t, &ContextOpts{
   805  		Providers: map[addrs.Provider]providers.Factory{
   806  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   807  		},
   808  	})
   809  
   810  	t.Run("condition pass", func(t *testing.T) {
   811  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
   812  			Mode: plans.NormalMode,
   813  			SetVariables: InputValues{
   814  				"boop": &InputValue{
   815  					Value:      cty.StringVal("boop"),
   816  					SourceType: ValueFromCLIArg,
   817  				},
   818  			},
   819  		})
   820  		assertNoErrors(t, diags)
   821  		if len(plan.Changes.Resources) != 3 {
   822  			t.Fatalf("unexpected plan changes: %#v", plan.Changes)
   823  		}
   824  
   825  		p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   826  			m := req.PlannedState.AsValueMap()
   827  			m["output"] = cty.StringVal(fmt.Sprintf("new-%s", m["value"].AsString()))
   828  
   829  			resp.NewState = cty.ObjectVal(m)
   830  			return resp
   831  		}
   832  		state, diags := ctx.Apply(plan, m)
   833  		assertNoErrors(t, diags)
   834  
   835  		wantResourceAttrs := map[string]struct{ value, output string }{
   836  			"a": {"boop", "new-boop"},
   837  			"b": {"new-boop", "new-new-boop"},
   838  			"c": {"new-new-boop", "new-new-new-boop"},
   839  		}
   840  		for name, attrs := range wantResourceAttrs {
   841  			addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
   842  			r := state.ResourceInstance(addr)
   843  			rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
   844  				"value":  cty.String,
   845  				"output": cty.String,
   846  			}))
   847  			if err != nil {
   848  				t.Fatalf("error decoding test_resource.a: %s", err)
   849  			}
   850  			want := cty.ObjectVal(map[string]cty.Value{
   851  				"value":  cty.StringVal(attrs.value),
   852  				"output": cty.StringVal(attrs.output),
   853  			})
   854  			if !cmp.Equal(want, rd.Value, valueComparer) {
   855  				t.Errorf("wrong attrs for %s\n%s", addr, cmp.Diff(want, rd.Value, valueComparer))
   856  			}
   857  		}
   858  	})
   859  	t.Run("condition fail", func(t *testing.T) {
   860  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
   861  			Mode: plans.NormalMode,
   862  			SetVariables: InputValues{
   863  				"boop": &InputValue{
   864  					Value:      cty.StringVal("boop"),
   865  					SourceType: ValueFromCLIArg,
   866  				},
   867  			},
   868  		})
   869  		assertNoErrors(t, diags)
   870  		if len(plan.Changes.Resources) != 3 {
   871  			t.Fatalf("unexpected plan changes: %#v", plan.Changes)
   872  		}
   873  
   874  		p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   875  			m := req.PlannedState.AsValueMap()
   876  
   877  			// For the resource with a constraint, fudge the output to make the
   878  			// condition fail.
   879  			if value := m["value"].AsString(); value == "new-boop" {
   880  				m["output"] = cty.StringVal("")
   881  			} else {
   882  				m["output"] = cty.StringVal(fmt.Sprintf("new-%s", value))
   883  			}
   884  
   885  			resp.NewState = cty.ObjectVal(m)
   886  			return resp
   887  		}
   888  		state, diags := ctx.Apply(plan, m)
   889  		if !diags.HasErrors() {
   890  			t.Fatal("succeeded; want errors")
   891  		}
   892  		if got, want := diags.Err().Error(), "Resource postcondition failed: Output must not be blank."; got != want {
   893  			t.Fatalf("wrong error:\ngot:  %s\nwant: %q", got, want)
   894  		}
   895  
   896  		// Resources a and b should still be recorded in state
   897  		wantResourceAttrs := map[string]struct{ value, output string }{
   898  			"a": {"boop", "new-boop"},
   899  			"b": {"new-boop", ""},
   900  		}
   901  		for name, attrs := range wantResourceAttrs {
   902  			addr := mustResourceInstanceAddr(fmt.Sprintf("test_resource.%s", name))
   903  			r := state.ResourceInstance(addr)
   904  			rd, err := r.Current.Decode(cty.Object(map[string]cty.Type{
   905  				"value":  cty.String,
   906  				"output": cty.String,
   907  			}))
   908  			if err != nil {
   909  				t.Fatalf("error decoding test_resource.a: %s", err)
   910  			}
   911  			want := cty.ObjectVal(map[string]cty.Value{
   912  				"value":  cty.StringVal(attrs.value),
   913  				"output": cty.StringVal(attrs.output),
   914  			})
   915  			if !cmp.Equal(want, rd.Value, valueComparer) {
   916  				t.Errorf("wrong attrs for %s\n%s", addr, cmp.Diff(want, rd.Value, valueComparer))
   917  			}
   918  		}
   919  
   920  		// Resource c should not be in state
   921  		if state.ResourceInstance(mustResourceInstanceAddr("test_resource.c")) != nil {
   922  			t.Error("test_resource.c should not exist in state, but is")
   923  		}
   924  	})
   925  }
   926  
   927  func TestContext2Apply_outputValuePrecondition(t *testing.T) {
   928  	m := testModuleInline(t, map[string]string{
   929  		"main.tf": `
   930  			variable "input" {
   931  				type = string
   932  			}
   933  
   934  			module "child" {
   935  				source = "./child"
   936  
   937  				input = var.input
   938  			}
   939  
   940  			output "result" {
   941  				value = module.child.result
   942  
   943  				precondition {
   944  					condition     = var.input != ""
   945  					error_message = "Input must not be empty."
   946  				}
   947  			}
   948  		`,
   949  		"child/main.tf": `
   950  			variable "input" {
   951  				type = string
   952  			}
   953  
   954  			output "result" {
   955  				value = var.input
   956  
   957  				precondition {
   958  					condition     = var.input != ""
   959  					error_message = "Input must not be empty."
   960  				}
   961  			}
   962  		`,
   963  	})
   964  
   965  	checkableObjects := []addrs.Checkable{
   966  		addrs.OutputValue{Name: "result"}.Absolute(addrs.RootModuleInstance),
   967  		addrs.OutputValue{Name: "result"}.Absolute(addrs.RootModuleInstance.Child("child", addrs.NoKey)),
   968  	}
   969  
   970  	t.Run("pass", func(t *testing.T) {
   971  		ctx := testContext2(t, &ContextOpts{})
   972  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
   973  			Mode: plans.NormalMode,
   974  			SetVariables: InputValues{
   975  				"input": &InputValue{
   976  					Value:      cty.StringVal("beep"),
   977  					SourceType: ValueFromCLIArg,
   978  				},
   979  			},
   980  		})
   981  		assertNoDiagnostics(t, diags)
   982  
   983  		for _, addr := range checkableObjects {
   984  			result := plan.Checks.GetObjectResult(addr)
   985  			if result == nil {
   986  				t.Fatalf("no check result for %s in the plan", addr)
   987  			}
   988  			if got, want := result.Status, checks.StatusPass; got != want {
   989  				t.Fatalf("wrong check status for %s during planning\ngot:  %s\nwant: %s", addr, got, want)
   990  			}
   991  		}
   992  
   993  		state, diags := ctx.Apply(plan, m)
   994  		assertNoDiagnostics(t, diags)
   995  		for _, addr := range checkableObjects {
   996  			result := state.CheckResults.GetObjectResult(addr)
   997  			if result == nil {
   998  				t.Fatalf("no check result for %s in the final state", addr)
   999  			}
  1000  			if got, want := result.Status, checks.StatusPass; got != want {
  1001  				t.Errorf("wrong check status for %s after apply\ngot:  %s\nwant: %s", addr, got, want)
  1002  			}
  1003  		}
  1004  	})
  1005  
  1006  	t.Run("fail", func(t *testing.T) {
  1007  		// NOTE: This test actually catches a failure during planning and so
  1008  		// cannot proceed to apply, so it's really more of a plan test
  1009  		// than an apply test but better to keep all of these
  1010  		// thematically-related test cases together.
  1011  		ctx := testContext2(t, &ContextOpts{})
  1012  		_, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1013  			Mode: plans.NormalMode,
  1014  			SetVariables: InputValues{
  1015  				"input": &InputValue{
  1016  					Value:      cty.StringVal(""),
  1017  					SourceType: ValueFromCLIArg,
  1018  				},
  1019  			},
  1020  		})
  1021  		if !diags.HasErrors() {
  1022  			t.Fatalf("succeeded; want error")
  1023  		}
  1024  
  1025  		const wantSummary = "Module output value precondition failed"
  1026  		found := false
  1027  		for _, diag := range diags {
  1028  			if diag.Severity() == tfdiags.Error && diag.Description().Summary == wantSummary {
  1029  				found = true
  1030  				break
  1031  			}
  1032  		}
  1033  
  1034  		if !found {
  1035  			t.Fatalf("missing expected error\nwant summary: %s\ngot: %s", wantSummary, diags.Err().Error())
  1036  		}
  1037  	})
  1038  }
  1039  
  1040  func TestContext2Apply_resourceConditionApplyTimeFail(t *testing.T) {
  1041  	// This tests the less common situation where a condition fails due to
  1042  	// a change in a resource other than the one the condition is attached to,
  1043  	// and the condition result is unknown during planning.
  1044  	//
  1045  	// This edge case is a tricky one because it relies on OpenTofu still
  1046  	// visiting test_resource.b (in the configuration below) to evaluate
  1047  	// its conditions even though there aren't any changes directly planned
  1048  	// for it, so that we can consider whether changes to test_resource.a
  1049  	// have changed the outcome.
  1050  
  1051  	m := testModuleInline(t, map[string]string{
  1052  		"main.tf": `
  1053  			variable "input" {
  1054  				type = string
  1055  			}
  1056  
  1057  			resource "test_resource" "a" {
  1058  				value = var.input
  1059  			}
  1060  
  1061  			resource "test_resource" "b" {
  1062  				value = "beep"
  1063  
  1064  				lifecycle {
  1065  					postcondition {
  1066  						condition     = test_resource.a.output == self.output
  1067  						error_message = "Outputs must match."
  1068  					}
  1069  				}
  1070  			}
  1071  		`,
  1072  	})
  1073  
  1074  	p := testProvider("test")
  1075  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1076  		ResourceTypes: map[string]*configschema.Block{
  1077  			"test_resource": {
  1078  				Attributes: map[string]*configschema.Attribute{
  1079  					"value": {
  1080  						Type:     cty.String,
  1081  						Required: true,
  1082  					},
  1083  					"output": {
  1084  						Type:     cty.String,
  1085  						Computed: true,
  1086  					},
  1087  				},
  1088  			},
  1089  		},
  1090  	})
  1091  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1092  		// Whenever "value" changes, "output" follows it during the apply step,
  1093  		// but is initially unknown during the plan step.
  1094  
  1095  		m := req.ProposedNewState.AsValueMap()
  1096  		priorVal := cty.NullVal(cty.String)
  1097  		if !req.PriorState.IsNull() {
  1098  			priorVal = req.PriorState.GetAttr("value")
  1099  		}
  1100  		if m["output"].IsNull() || !priorVal.RawEquals(m["value"]) {
  1101  			m["output"] = cty.UnknownVal(cty.String)
  1102  		}
  1103  
  1104  		resp.PlannedState = cty.ObjectVal(m)
  1105  		resp.LegacyTypeSystem = true
  1106  		return resp
  1107  	}
  1108  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
  1109  		m := req.PlannedState.AsValueMap()
  1110  		m["output"] = m["value"]
  1111  		resp.NewState = cty.ObjectVal(m)
  1112  		return resp
  1113  	}
  1114  	ctx := testContext2(t, &ContextOpts{
  1115  		Providers: map[addrs.Provider]providers.Factory{
  1116  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1117  		},
  1118  	})
  1119  	instA := mustResourceInstanceAddr("test_resource.a")
  1120  	instB := mustResourceInstanceAddr("test_resource.b")
  1121  
  1122  	// Preparation: an initial plan and apply with a correct input variable
  1123  	// should succeed and give us a valid and complete state to use for the
  1124  	// subsequent plan and apply that we'll expect to fail.
  1125  	var prevRunState *states.State
  1126  	{
  1127  		plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1128  			Mode: plans.NormalMode,
  1129  			SetVariables: InputValues{
  1130  				"input": &InputValue{
  1131  					Value:      cty.StringVal("beep"),
  1132  					SourceType: ValueFromCLIArg,
  1133  				},
  1134  			},
  1135  		})
  1136  		assertNoErrors(t, diags)
  1137  		planA := plan.Changes.ResourceInstance(instA)
  1138  		if planA == nil || planA.Action != plans.Create {
  1139  			t.Fatalf("incorrect initial plan for instance A\nwant a 'create' change\ngot: %s", spew.Sdump(planA))
  1140  		}
  1141  		planB := plan.Changes.ResourceInstance(instB)
  1142  		if planB == nil || planB.Action != plans.Create {
  1143  			t.Fatalf("incorrect initial plan for instance B\nwant a 'create' change\ngot: %s", spew.Sdump(planB))
  1144  		}
  1145  
  1146  		state, diags := ctx.Apply(plan, m)
  1147  		assertNoErrors(t, diags)
  1148  
  1149  		stateA := state.ResourceInstance(instA)
  1150  		if stateA == nil || stateA.Current == nil || !bytes.Contains(stateA.Current.AttrsJSON, []byte(`"beep"`)) {
  1151  			t.Fatalf("incorrect initial state for instance A\ngot: %s", spew.Sdump(stateA))
  1152  		}
  1153  		stateB := state.ResourceInstance(instB)
  1154  		if stateB == nil || stateB.Current == nil || !bytes.Contains(stateB.Current.AttrsJSON, []byte(`"beep"`)) {
  1155  			t.Fatalf("incorrect initial state for instance B\ngot: %s", spew.Sdump(stateB))
  1156  		}
  1157  		prevRunState = state
  1158  	}
  1159  
  1160  	// Now we'll run another plan and apply with a different value for
  1161  	// var.input that should cause the test_resource.b condition to be unknown
  1162  	// during planning and then fail during apply.
  1163  	{
  1164  		plan, diags := ctx.Plan(m, prevRunState, &PlanOpts{
  1165  			Mode: plans.NormalMode,
  1166  			SetVariables: InputValues{
  1167  				"input": &InputValue{
  1168  					Value:      cty.StringVal("boop"), // NOTE: This has changed
  1169  					SourceType: ValueFromCLIArg,
  1170  				},
  1171  			},
  1172  		})
  1173  		assertNoErrors(t, diags)
  1174  		planA := plan.Changes.ResourceInstance(instA)
  1175  		if planA == nil || planA.Action != plans.Update {
  1176  			t.Fatalf("incorrect initial plan for instance A\nwant an 'update' change\ngot: %s", spew.Sdump(planA))
  1177  		}
  1178  		planB := plan.Changes.ResourceInstance(instB)
  1179  		if planB == nil || planB.Action != plans.NoOp {
  1180  			t.Fatalf("incorrect initial plan for instance B\nwant a 'no-op' change\ngot: %s", spew.Sdump(planB))
  1181  		}
  1182  
  1183  		_, diags = ctx.Apply(plan, m)
  1184  		if !diags.HasErrors() {
  1185  			t.Fatal("final apply succeeded, but should've failed with a postcondition error")
  1186  		}
  1187  		if len(diags) != 1 {
  1188  			t.Fatalf("expected exactly one diagnostic, but got: %s", diags.Err().Error())
  1189  		}
  1190  		if got, want := diags[0].Description().Summary, "Resource postcondition failed"; got != want {
  1191  			t.Fatalf("wrong diagnostic summary\ngot:  %s\nwant: %s", got, want)
  1192  		}
  1193  	}
  1194  }
  1195  
  1196  // pass an input through some expanded values, and back to a provider to make
  1197  // sure we can fully evaluate a provider configuration during a destroy plan.
  1198  func TestContext2Apply_destroyWithConfiguredProvider(t *testing.T) {
  1199  	m := testModuleInline(t, map[string]string{
  1200  		"main.tf": `
  1201  variable "in" {
  1202    type = map(string)
  1203    default = {
  1204      "a" = "first"
  1205      "b" = "second"
  1206    }
  1207  }
  1208  
  1209  module "mod" {
  1210    source = "./mod"
  1211    for_each = var.in
  1212    in = each.value
  1213  }
  1214  
  1215  locals {
  1216    config = [for each in module.mod : each.out]
  1217  }
  1218  
  1219  provider "other" {
  1220    output = [for each in module.mod : each.out]
  1221    local = local.config
  1222    var = var.in
  1223  }
  1224  
  1225  resource "other_object" "other" {
  1226  }
  1227  `,
  1228  		"./mod/main.tf": `
  1229  variable "in" {
  1230    type = string
  1231  }
  1232  
  1233  data "test_object" "d" {
  1234    test_string = var.in
  1235  }
  1236  
  1237  resource "test_object" "a" {
  1238    test_string = var.in
  1239  }
  1240  
  1241  output "out" {
  1242    value = data.test_object.d.output
  1243  }
  1244  `})
  1245  
  1246  	testProvider := &MockProvider{
  1247  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  1248  			Provider: providers.Schema{Block: simpleTestSchema()},
  1249  			ResourceTypes: map[string]providers.Schema{
  1250  				"test_object": providers.Schema{Block: simpleTestSchema()},
  1251  			},
  1252  			DataSources: map[string]providers.Schema{
  1253  				"test_object": providers.Schema{
  1254  					Block: &configschema.Block{
  1255  						Attributes: map[string]*configschema.Attribute{
  1256  							"test_string": {
  1257  								Type:     cty.String,
  1258  								Optional: true,
  1259  							},
  1260  							"output": {
  1261  								Type:     cty.String,
  1262  								Computed: true,
  1263  							},
  1264  						},
  1265  					},
  1266  				},
  1267  			},
  1268  		},
  1269  	}
  1270  
  1271  	testProvider.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
  1272  		cfg := req.Config.AsValueMap()
  1273  		s := cfg["test_string"].AsString()
  1274  		if !strings.Contains("firstsecond", s) {
  1275  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("expected 'first' or 'second', got %s", s))
  1276  			return resp
  1277  		}
  1278  
  1279  		cfg["output"] = cty.StringVal(s + "-ok")
  1280  		resp.State = cty.ObjectVal(cfg)
  1281  		return resp
  1282  	}
  1283  
  1284  	otherProvider := &MockProvider{
  1285  		GetProviderSchemaResponse: &providers.GetProviderSchemaResponse{
  1286  			Provider: providers.Schema{
  1287  				Block: &configschema.Block{
  1288  					Attributes: map[string]*configschema.Attribute{
  1289  						"output": {
  1290  							Type:     cty.List(cty.String),
  1291  							Optional: true,
  1292  						},
  1293  						"local": {
  1294  							Type:     cty.List(cty.String),
  1295  							Optional: true,
  1296  						},
  1297  						"var": {
  1298  							Type:     cty.Map(cty.String),
  1299  							Optional: true,
  1300  						},
  1301  					},
  1302  				},
  1303  			},
  1304  			ResourceTypes: map[string]providers.Schema{
  1305  				"other_object": providers.Schema{Block: simpleTestSchema()},
  1306  			},
  1307  		},
  1308  	}
  1309  
  1310  	ctx := testContext2(t, &ContextOpts{
  1311  		Providers: map[addrs.Provider]providers.Factory{
  1312  			addrs.NewDefaultProvider("test"):  testProviderFuncFixed(testProvider),
  1313  			addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherProvider),
  1314  		},
  1315  	})
  1316  
  1317  	opts := SimplePlanOpts(plans.NormalMode, testInputValuesUnset(m.Module.Variables))
  1318  	plan, diags := ctx.Plan(m, states.NewState(), opts)
  1319  	assertNoErrors(t, diags)
  1320  
  1321  	state, diags := ctx.Apply(plan, m)
  1322  	assertNoErrors(t, diags)
  1323  
  1324  	// Resource changes which have dependencies across providers which
  1325  	// themselves depend on resources can result in cycles.
  1326  	// Because other_object transitively depends on the module resources
  1327  	// through its provider, we trigger changes on both sides of this boundary
  1328  	// to ensure we can create a valid plan.
  1329  	//
  1330  	// Taint the object to make sure a replacement works in the plan.
  1331  	otherObjAddr := mustResourceInstanceAddr("other_object.other")
  1332  	otherObj := state.ResourceInstance(otherObjAddr)
  1333  	otherObj.Current.Status = states.ObjectTainted
  1334  	// Force a change which needs to be reverted.
  1335  	testObjAddr := mustResourceInstanceAddr(`module.mod["a"].test_object.a`)
  1336  	testObjA := state.ResourceInstance(testObjAddr)
  1337  	testObjA.Current.AttrsJSON = []byte(`{"test_bool":null,"test_list":null,"test_map":null,"test_number":null,"test_string":"changed"}`)
  1338  
  1339  	_, diags = ctx.Plan(m, state, opts)
  1340  	assertNoErrors(t, diags)
  1341  	return
  1342  	// TODO: unreachable code
  1343  	otherProvider.ConfigureProviderCalled = false
  1344  	otherProvider.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
  1345  		// check that our config is complete, even during a destroy plan
  1346  		expected := cty.ObjectVal(map[string]cty.Value{
  1347  			"local":  cty.ListVal([]cty.Value{cty.StringVal("first-ok"), cty.StringVal("second-ok")}),
  1348  			"output": cty.ListVal([]cty.Value{cty.StringVal("first-ok"), cty.StringVal("second-ok")}),
  1349  			"var": cty.MapVal(map[string]cty.Value{
  1350  				"a": cty.StringVal("first"),
  1351  				"b": cty.StringVal("second"),
  1352  			}),
  1353  		})
  1354  
  1355  		if !req.Config.RawEquals(expected) {
  1356  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf(
  1357  				`incorrect provider config:
  1358  expected: %#v
  1359  got:      %#v`,
  1360  				expected, req.Config))
  1361  		}
  1362  
  1363  		return resp
  1364  	}
  1365  
  1366  	opts.Mode = plans.DestroyMode
  1367  	// skip refresh so that we don't configure the provider before the destroy plan
  1368  	opts.SkipRefresh = true
  1369  
  1370  	// destroy only a single instance not included in the moved statements
  1371  	_, diags = ctx.Plan(m, state, opts)
  1372  	assertNoErrors(t, diags)
  1373  
  1374  	if !otherProvider.ConfigureProviderCalled {
  1375  		t.Fatal("failed to configure provider during destroy plan")
  1376  	}
  1377  }
  1378  
  1379  // check that a provider can verify a planned destroy
  1380  func TestContext2Apply_plannedDestroy(t *testing.T) {
  1381  	m := testModuleInline(t, map[string]string{
  1382  		"main.tf": `
  1383  resource "test_object" "x" {
  1384    test_string = "ok"
  1385  }`,
  1386  	})
  1387  
  1388  	p := simpleMockProvider()
  1389  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1390  		if !req.ProposedNewState.IsNull() {
  1391  			// we should only be destroying in this test
  1392  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unexpected plan with %#v", req.ProposedNewState))
  1393  			return resp
  1394  		}
  1395  
  1396  		resp.PlannedState = req.ProposedNewState
  1397  		// we're going to verify the destroy plan by inserting private data required for destroy
  1398  		resp.PlannedPrivate = append(resp.PlannedPrivate, []byte("planned")...)
  1399  		return resp
  1400  	}
  1401  
  1402  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
  1403  		// if the value is nil, we return that directly to correspond to a delete
  1404  		if !req.PlannedState.IsNull() {
  1405  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("unexpected apply with %#v", req.PlannedState))
  1406  			return resp
  1407  		}
  1408  
  1409  		resp.NewState = req.PlannedState
  1410  
  1411  		// make sure we get our private data from the plan
  1412  		private := string(req.PlannedPrivate)
  1413  		if private != "planned" {
  1414  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("missing private data from plan, got %q", private))
  1415  		}
  1416  		return resp
  1417  	}
  1418  
  1419  	state := states.NewState()
  1420  	root := state.EnsureModule(addrs.RootModuleInstance)
  1421  	root.SetResourceInstanceCurrent(
  1422  		mustResourceInstanceAddr("test_object.x").Resource,
  1423  		&states.ResourceInstanceObjectSrc{
  1424  			Status:    states.ObjectReady,
  1425  			AttrsJSON: []byte(`{"test_string":"ok"}`),
  1426  		},
  1427  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  1428  	)
  1429  
  1430  	ctx := testContext2(t, &ContextOpts{
  1431  		Providers: map[addrs.Provider]providers.Factory{
  1432  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1433  		},
  1434  	})
  1435  
  1436  	plan, diags := ctx.Plan(m, state, &PlanOpts{
  1437  		Mode: plans.DestroyMode,
  1438  		// we don't want to refresh, because that actually runs a normal plan
  1439  		SkipRefresh: true,
  1440  	})
  1441  	if diags.HasErrors() {
  1442  		t.Fatalf("plan: %s", diags.Err())
  1443  	}
  1444  
  1445  	_, diags = ctx.Apply(plan, m)
  1446  	if diags.HasErrors() {
  1447  		t.Fatalf("apply: %s", diags.Err())
  1448  	}
  1449  }
  1450  
  1451  func TestContext2Apply_missingOrphanedResource(t *testing.T) {
  1452  	m := testModuleInline(t, map[string]string{
  1453  		"main.tf": `
  1454  # changed resource address to create a new object
  1455  resource "test_object" "y" {
  1456    test_string = "y"
  1457  }
  1458  `,
  1459  	})
  1460  
  1461  	p := simpleMockProvider()
  1462  
  1463  	// report the prior value is missing
  1464  	p.ReadResourceFn = func(req providers.ReadResourceRequest) (resp providers.ReadResourceResponse) {
  1465  		resp.NewState = cty.NullVal(req.PriorState.Type())
  1466  		return resp
  1467  	}
  1468  
  1469  	state := states.NewState()
  1470  	root := state.EnsureModule(addrs.RootModuleInstance)
  1471  	root.SetResourceInstanceCurrent(
  1472  		mustResourceInstanceAddr("test_object.x").Resource,
  1473  		&states.ResourceInstanceObjectSrc{
  1474  			Status:    states.ObjectReady,
  1475  			AttrsJSON: []byte(`{"test_string":"x"}`),
  1476  		},
  1477  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  1478  	)
  1479  
  1480  	ctx := testContext2(t, &ContextOpts{
  1481  		Providers: map[addrs.Provider]providers.Factory{
  1482  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1483  		},
  1484  	})
  1485  
  1486  	opts := SimplePlanOpts(plans.NormalMode, nil)
  1487  	plan, diags := ctx.Plan(m, state, opts)
  1488  	assertNoErrors(t, diags)
  1489  
  1490  	_, diags = ctx.Apply(plan, m)
  1491  	assertNoErrors(t, diags)
  1492  }
  1493  
  1494  // Outputs should not cause evaluation errors during destroy
  1495  // Check eval from both root level outputs and module outputs, which are
  1496  // handled differently during apply.
  1497  func TestContext2Apply_outputsNotToEvaluate(t *testing.T) {
  1498  	m := testModuleInline(t, map[string]string{
  1499  		"main.tf": `
  1500  module "mod" {
  1501    source = "./mod"
  1502    cond = false
  1503  }
  1504  
  1505  output "from_resource" {
  1506    value = module.mod.from_resource
  1507  }
  1508  
  1509  output "from_data" {
  1510    value = module.mod.from_data
  1511  }
  1512  `,
  1513  
  1514  		"./mod/main.tf": `
  1515  variable "cond" {
  1516    type = bool
  1517  }
  1518  
  1519  module "mod" {
  1520    source = "../mod2/"
  1521    cond = var.cond
  1522  }
  1523  
  1524  output "from_resource" {
  1525    value = module.mod.resource
  1526  }
  1527  
  1528  output "from_data" {
  1529    value = module.mod.data
  1530  }
  1531  `,
  1532  
  1533  		"./mod2/main.tf": `
  1534  variable "cond" {
  1535    type = bool
  1536  }
  1537  
  1538  resource "test_object" "x" {
  1539    count = var.cond ? 0:1
  1540  }
  1541  
  1542  data "test_object" "d" {
  1543    count = var.cond ? 0:1
  1544  }
  1545  
  1546  output "resource" {
  1547    value = var.cond ? null : test_object.x.*.test_string[0]
  1548  }
  1549  
  1550  output "data" {
  1551    value = one(data.test_object.d[*].test_string)
  1552  }
  1553  `})
  1554  
  1555  	p := simpleMockProvider()
  1556  	p.ReadDataSourceFn = func(req providers.ReadDataSourceRequest) (resp providers.ReadDataSourceResponse) {
  1557  		resp.State = req.Config
  1558  		return resp
  1559  	}
  1560  
  1561  	ctx := testContext2(t, &ContextOpts{
  1562  		Providers: map[addrs.Provider]providers.Factory{
  1563  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1564  		},
  1565  	})
  1566  
  1567  	// apply the state
  1568  	opts := SimplePlanOpts(plans.NormalMode, nil)
  1569  	plan, diags := ctx.Plan(m, states.NewState(), opts)
  1570  	assertNoErrors(t, diags)
  1571  
  1572  	state, diags := ctx.Apply(plan, m)
  1573  	assertNoErrors(t, diags)
  1574  
  1575  	// and destroy
  1576  	opts = SimplePlanOpts(plans.DestroyMode, nil)
  1577  	plan, diags = ctx.Plan(m, state, opts)
  1578  	assertNoErrors(t, diags)
  1579  
  1580  	state, diags = ctx.Apply(plan, m)
  1581  	assertNoErrors(t, diags)
  1582  
  1583  	// and destroy again with no state
  1584  	if !state.Empty() {
  1585  		t.Fatal("expected empty state, got", state)
  1586  	}
  1587  
  1588  	opts = SimplePlanOpts(plans.DestroyMode, nil)
  1589  	plan, diags = ctx.Plan(m, state, opts)
  1590  	assertNoErrors(t, diags)
  1591  
  1592  	_, diags = ctx.Apply(plan, m)
  1593  	assertNoErrors(t, diags)
  1594  }
  1595  
  1596  // don't evaluate conditions on outputs when destroying
  1597  func TestContext2Apply_noOutputChecksOnDestroy(t *testing.T) {
  1598  	m := testModuleInline(t, map[string]string{
  1599  		"main.tf": `
  1600  module "mod" {
  1601    source = "./mod"
  1602  }
  1603  
  1604  output "from_resource" {
  1605    value = module.mod.from_resource
  1606  }
  1607  `,
  1608  
  1609  		"./mod/main.tf": `
  1610  resource "test_object" "x" {
  1611    test_string = "wrong val"
  1612  }
  1613  
  1614  output "from_resource" {
  1615    value = test_object.x.test_string
  1616    precondition {
  1617      condition     = test_object.x.test_string == "ok"
  1618      error_message = "resource error"
  1619    }
  1620  }
  1621  `})
  1622  
  1623  	p := simpleMockProvider()
  1624  
  1625  	state := states.NewState()
  1626  	mod := state.EnsureModule(addrs.RootModuleInstance.Child("mod", addrs.NoKey))
  1627  	mod.SetResourceInstanceCurrent(
  1628  		mustResourceInstanceAddr("test_object.x").Resource,
  1629  		&states.ResourceInstanceObjectSrc{
  1630  			Status:    states.ObjectReady,
  1631  			AttrsJSON: []byte(`{"test_string":"wrong_val"}`),
  1632  		},
  1633  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  1634  	)
  1635  
  1636  	ctx := testContext2(t, &ContextOpts{
  1637  		Providers: map[addrs.Provider]providers.Factory{
  1638  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1639  		},
  1640  	})
  1641  
  1642  	opts := SimplePlanOpts(plans.DestroyMode, nil)
  1643  	plan, diags := ctx.Plan(m, state, opts)
  1644  	assertNoErrors(t, diags)
  1645  
  1646  	_, diags = ctx.Apply(plan, m)
  1647  	assertNoErrors(t, diags)
  1648  }
  1649  
  1650  // -refresh-only should update checks
  1651  func TestContext2Apply_refreshApplyUpdatesChecks(t *testing.T) {
  1652  	m := testModuleInline(t, map[string]string{
  1653  		"main.tf": `
  1654  resource "test_object" "x" {
  1655    test_string = "ok"
  1656    lifecycle {
  1657      postcondition {
  1658        condition = self.test_string == "ok"
  1659        error_message = "wrong val"
  1660      }
  1661    }
  1662  }
  1663  
  1664  output "from_resource" {
  1665    value = test_object.x.test_string
  1666    precondition {
  1667  	condition     = test_object.x.test_string == "ok"
  1668  	error_message = "wrong val"
  1669    }
  1670  }
  1671  `})
  1672  
  1673  	p := simpleMockProvider()
  1674  	p.ReadResourceResponse = &providers.ReadResourceResponse{
  1675  		NewState: cty.ObjectVal(map[string]cty.Value{
  1676  			"test_string": cty.StringVal("ok"),
  1677  		}),
  1678  	}
  1679  
  1680  	state := states.NewState()
  1681  	mod := state.EnsureModule(addrs.RootModuleInstance)
  1682  	mod.SetResourceInstanceCurrent(
  1683  		mustResourceInstanceAddr("test_object.x").Resource,
  1684  		&states.ResourceInstanceObjectSrc{
  1685  			Status:    states.ObjectReady,
  1686  			AttrsJSON: []byte(`{"test_string":"wrong val"}`),
  1687  		},
  1688  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  1689  	)
  1690  	mod.SetOutputValue("from_resource", cty.StringVal("wrong val"), false)
  1691  
  1692  	ctx := testContext2(t, &ContextOpts{
  1693  		Providers: map[addrs.Provider]providers.Factory{
  1694  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1695  		},
  1696  	})
  1697  
  1698  	opts := SimplePlanOpts(plans.RefreshOnlyMode, nil)
  1699  	plan, diags := ctx.Plan(m, state, opts)
  1700  	assertNoErrors(t, diags)
  1701  
  1702  	state, diags = ctx.Apply(plan, m)
  1703  	assertNoErrors(t, diags)
  1704  
  1705  	resCheck := state.CheckResults.GetObjectResult(mustResourceInstanceAddr("test_object.x"))
  1706  	if resCheck.Status != checks.StatusPass {
  1707  		t.Fatalf("unexpected check %s: %s\n", resCheck.Status, resCheck.FailureMessages)
  1708  	}
  1709  
  1710  	outAddr := addrs.AbsOutputValue{
  1711  		Module: addrs.RootModuleInstance,
  1712  		OutputValue: addrs.OutputValue{
  1713  			Name: "from_resource",
  1714  		},
  1715  	}
  1716  	outCheck := state.CheckResults.GetObjectResult(outAddr)
  1717  	if outCheck.Status != checks.StatusPass {
  1718  		t.Fatalf("unexpected check %s: %s\n", outCheck.Status, outCheck.FailureMessages)
  1719  	}
  1720  }
  1721  
  1722  // NoOp changes may have conditions to evaluate, but should not re-plan and
  1723  // apply the entire resource.
  1724  func TestContext2Apply_noRePlanNoOp(t *testing.T) {
  1725  	m := testModuleInline(t, map[string]string{
  1726  		"main.tf": `
  1727  resource "test_object" "x" {
  1728  }
  1729  
  1730  resource "test_object" "y" {
  1731    # test_object.w is being re-created, so this precondition must be evaluated
  1732    # during apply, however this resource should otherwise be a NoOp.
  1733    lifecycle {
  1734      precondition {
  1735        condition     = test_object.x.test_string == null
  1736        error_message = "test_object.x.test_string should be null"
  1737      }
  1738    }
  1739  }
  1740  `})
  1741  
  1742  	p := simpleMockProvider()
  1743  	// make sure we can compute the attr
  1744  	testString := p.GetProviderSchemaResponse.ResourceTypes["test_object"].Block.Attributes["test_string"]
  1745  	testString.Computed = true
  1746  	testString.Optional = false
  1747  
  1748  	yAddr := mustResourceInstanceAddr("test_object.y")
  1749  
  1750  	state := states.NewState()
  1751  	mod := state.RootModule()
  1752  	mod.SetResourceInstanceCurrent(
  1753  		yAddr.Resource,
  1754  		&states.ResourceInstanceObjectSrc{
  1755  			Status:    states.ObjectReady,
  1756  			AttrsJSON: []byte(`{"test_string":"y"}`),
  1757  		},
  1758  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/test"]`),
  1759  	)
  1760  
  1761  	ctx := testContext2(t, &ContextOpts{
  1762  		Providers: map[addrs.Provider]providers.Factory{
  1763  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1764  		},
  1765  	})
  1766  
  1767  	opts := SimplePlanOpts(plans.NormalMode, nil)
  1768  	plan, diags := ctx.Plan(m, state, opts)
  1769  	assertNoErrors(t, diags)
  1770  
  1771  	for _, c := range plan.Changes.Resources {
  1772  		if c.Addr.Equal(yAddr) && c.Action != plans.NoOp {
  1773  			t.Fatalf("unexpected %s change for test_object.y", c.Action)
  1774  		}
  1775  	}
  1776  
  1777  	// test_object.y is a NoOp change from the plan, but is included in the
  1778  	// graph due to the conditions which must be evaluated. This however should
  1779  	// not cause the resource to be re-planned.
  1780  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
  1781  		testString := req.ProposedNewState.GetAttr("test_string")
  1782  		if !testString.IsNull() && testString.AsString() == "y" {
  1783  			resp.Diagnostics = resp.Diagnostics.Append(errors.New("Unexpected apply-time plan for test_object.y. Original plan was a NoOp"))
  1784  		}
  1785  		resp.PlannedState = req.ProposedNewState
  1786  		return resp
  1787  	}
  1788  
  1789  	_, diags = ctx.Apply(plan, m)
  1790  	assertNoErrors(t, diags)
  1791  }
  1792  
  1793  // ensure all references from preconditions are tracked through plan and apply
  1794  func TestContext2Apply_preconditionErrorMessageRef(t *testing.T) {
  1795  	p := testProvider("test")
  1796  	ctx := testContext2(t, &ContextOpts{
  1797  		Providers: map[addrs.Provider]providers.Factory{
  1798  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1799  		},
  1800  	})
  1801  
  1802  	m := testModuleInline(t, map[string]string{
  1803  		"main.tf": `
  1804  module "nested" {
  1805    source = "./mod"
  1806  }
  1807  
  1808  output "nested_a" {
  1809    value = module.nested.a
  1810  }
  1811  `,
  1812  
  1813  		"mod/main.tf": `
  1814  variable "boop" {
  1815    default = "boop"
  1816  }
  1817  
  1818  variable "msg" {
  1819    default = "Incorrect boop."
  1820  }
  1821  
  1822  output "a" {
  1823    value     = "x"
  1824  
  1825    precondition {
  1826      condition     = var.boop == "boop"
  1827      error_message = var.msg
  1828    }
  1829  }
  1830  `,
  1831  	})
  1832  
  1833  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1834  		Mode: plans.NormalMode,
  1835  	})
  1836  	assertNoErrors(t, diags)
  1837  	_, diags = ctx.Apply(plan, m)
  1838  	assertNoErrors(t, diags)
  1839  }
  1840  
  1841  func TestContext2Apply_destroyNullModuleOutput(t *testing.T) {
  1842  	p := testProvider("test")
  1843  	ctx := testContext2(t, &ContextOpts{
  1844  		Providers: map[addrs.Provider]providers.Factory{
  1845  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1846  		},
  1847  	})
  1848  
  1849  	m := testModuleInline(t, map[string]string{
  1850  		"main.tf": `
  1851  module "null_module" {
  1852    source = "./mod"
  1853  }
  1854  
  1855  locals {
  1856    module_output = module.null_module.null_module_test
  1857  }
  1858  
  1859  output "test_root" {
  1860    value = module.null_module.test_output
  1861  }
  1862  
  1863  output "root_module" {
  1864    value = local.module_output #fails
  1865  }
  1866  `,
  1867  
  1868  		"mod/main.tf": `
  1869  output "test_output" {
  1870    value = "test"
  1871  }
  1872  
  1873  output "null_module_test" {
  1874    value = null
  1875  }
  1876  `,
  1877  	})
  1878  
  1879  	// verify plan and apply
  1880  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1881  		Mode: plans.NormalMode,
  1882  	})
  1883  	assertNoErrors(t, diags)
  1884  	state, diags := ctx.Apply(plan, m)
  1885  	assertNoErrors(t, diags)
  1886  
  1887  	// now destroy
  1888  	plan, diags = ctx.Plan(m, state, &PlanOpts{
  1889  		Mode: plans.DestroyMode,
  1890  	})
  1891  	assertNoErrors(t, diags)
  1892  	_, diags = ctx.Apply(plan, m)
  1893  	assertNoErrors(t, diags)
  1894  }
  1895  
  1896  func TestContext2Apply_moduleOutputWithSensitiveAttrs(t *testing.T) {
  1897  	// Ensure that nested sensitive marks are stored when accessing non-root
  1898  	// module outputs, and that they do not cause the entire output value to
  1899  	// become sensitive.
  1900  	m := testModuleInline(t, map[string]string{
  1901  		"main.tf": `
  1902  module "mod" {
  1903    source = "./mod"
  1904  }
  1905  
  1906  resource "test_resource" "b" {
  1907    // if the module output were wholly sensitive it would not be valid to use in
  1908    // for_each
  1909    for_each = module.mod.resources
  1910    value = each.value.output
  1911  }
  1912  
  1913  output "root_output" {
  1914    // The root output cannot contain any sensitive marks at all.
  1915    // Applying nonsensitive would fail here if the nested sensitive mark were
  1916    // not maintained through the output.
  1917    value = [ for k, v in module.mod.resources : nonsensitive(v.output) ]
  1918  }
  1919  `,
  1920  		"./mod/main.tf": `
  1921  resource "test_resource" "a" {
  1922    for_each = {"key": "value"}
  1923    value = each.key
  1924  }
  1925  
  1926  output "resources" {
  1927    value = test_resource.a
  1928  }
  1929  `,
  1930  	})
  1931  
  1932  	p := testProvider("test")
  1933  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1934  		ResourceTypes: map[string]*configschema.Block{
  1935  			"test_resource": {
  1936  				Attributes: map[string]*configschema.Attribute{
  1937  					"value": {
  1938  						Type:     cty.String,
  1939  						Required: true,
  1940  					},
  1941  					"output": {
  1942  						Type:      cty.String,
  1943  						Sensitive: true,
  1944  						Computed:  true,
  1945  					},
  1946  				},
  1947  			},
  1948  		},
  1949  	})
  1950  	ctx := testContext2(t, &ContextOpts{
  1951  		Providers: map[addrs.Provider]providers.Factory{
  1952  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  1953  		},
  1954  	})
  1955  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  1956  		Mode: plans.NormalMode,
  1957  	})
  1958  	assertNoErrors(t, diags)
  1959  	_, diags = ctx.Apply(plan, m)
  1960  	assertNoErrors(t, diags)
  1961  }
  1962  
  1963  func TestContext2Apply_timestamps(t *testing.T) {
  1964  	m := testModuleInline(t, map[string]string{
  1965  		"main.tf": `
  1966  resource "test_resource" "a" {
  1967    id = "timestamp"
  1968    value = timestamp()
  1969  }
  1970  
  1971  resource "test_resource" "b" {
  1972    id = "plantimestamp"
  1973    value = plantimestamp()
  1974  }
  1975  `,
  1976  	})
  1977  
  1978  	var plantime time.Time
  1979  
  1980  	p := testProvider("test")
  1981  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  1982  		ResourceTypes: map[string]*configschema.Block{
  1983  			"test_resource": {
  1984  				Attributes: map[string]*configschema.Attribute{
  1985  					"id": {
  1986  						Type:     cty.String,
  1987  						Required: true,
  1988  					},
  1989  					"value": {
  1990  						Type:     cty.String,
  1991  						Required: true,
  1992  					},
  1993  				},
  1994  			},
  1995  		},
  1996  	})
  1997  	p.PlanResourceChangeFn = func(request providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  1998  		values := request.ProposedNewState.AsValueMap()
  1999  		if id := values["id"]; id.AsString() == "plantimestamp" {
  2000  			var err error
  2001  			plantime, err = time.Parse(time.RFC3339, values["value"].AsString())
  2002  			if err != nil {
  2003  				t.Errorf("couldn't parse plan time: %s", err)
  2004  			}
  2005  		}
  2006  
  2007  		return providers.PlanResourceChangeResponse{
  2008  			PlannedState: request.ProposedNewState,
  2009  		}
  2010  	}
  2011  	p.ApplyResourceChangeFn = func(request providers.ApplyResourceChangeRequest) providers.ApplyResourceChangeResponse {
  2012  		values := request.PlannedState.AsValueMap()
  2013  		if id := values["id"]; id.AsString() == "timestamp" {
  2014  			applytime, err := time.Parse(time.RFC3339, values["value"].AsString())
  2015  			if err != nil {
  2016  				t.Errorf("couldn't parse apply time: %s", err)
  2017  			}
  2018  
  2019  			if applytime.Before(plantime) {
  2020  				t.Errorf("applytime (%s) should be after plantime (%s)", applytime.Format(time.RFC3339), plantime.Format(time.RFC3339))
  2021  			}
  2022  		} else if id.AsString() == "plantimestamp" {
  2023  			otherplantime, err := time.Parse(time.RFC3339, values["value"].AsString())
  2024  			if err != nil {
  2025  				t.Errorf("couldn't parse plan time: %s", err)
  2026  			}
  2027  
  2028  			if !plantime.Equal(otherplantime) {
  2029  				t.Errorf("plantime changed from (%s) to (%s) during apply", plantime.Format(time.RFC3339), otherplantime.Format(time.RFC3339))
  2030  			}
  2031  		}
  2032  
  2033  		return providers.ApplyResourceChangeResponse{
  2034  			NewState: request.PlannedState,
  2035  		}
  2036  	}
  2037  	ctx := testContext2(t, &ContextOpts{
  2038  		Providers: map[addrs.Provider]providers.Factory{
  2039  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2040  		},
  2041  	})
  2042  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2043  		Mode: plans.NormalMode,
  2044  	})
  2045  	assertNoErrors(t, diags)
  2046  
  2047  	_, diags = ctx.Apply(plan, m)
  2048  	assertNoErrors(t, diags)
  2049  }
  2050  
  2051  func TestContext2Apply_destroyUnusedModuleProvider(t *testing.T) {
  2052  	// an unsued provider within a module should not be called during destroy
  2053  	unusedProvider := testProvider("unused")
  2054  	testProvider := testProvider("test")
  2055  	ctx := testContext2(t, &ContextOpts{
  2056  		Providers: map[addrs.Provider]providers.Factory{
  2057  			addrs.NewDefaultProvider("test"):   testProviderFuncFixed(testProvider),
  2058  			addrs.NewDefaultProvider("unused"): testProviderFuncFixed(unusedProvider),
  2059  		},
  2060  	})
  2061  
  2062  	unusedProvider.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
  2063  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("configuration failed"))
  2064  		return resp
  2065  	}
  2066  
  2067  	m := testModuleInline(t, map[string]string{
  2068  		"main.tf": `
  2069  module "mod" {
  2070    source = "./mod"
  2071  }
  2072  
  2073  resource "test_resource" "test" {
  2074  }
  2075  `,
  2076  
  2077  		"mod/main.tf": `
  2078  provider "unused" {
  2079  }
  2080  
  2081  resource "unused_resource" "test" {
  2082  }
  2083  `,
  2084  	})
  2085  
  2086  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2087  		Mode: plans.DestroyMode,
  2088  	})
  2089  	assertNoErrors(t, diags)
  2090  	_, diags = ctx.Apply(plan, m)
  2091  	assertNoErrors(t, diags)
  2092  }
  2093  
  2094  func TestContext2Apply_import(t *testing.T) {
  2095  	m := testModuleInline(t, map[string]string{
  2096  		"main.tf": `
  2097  resource "test_resource" "a" {
  2098    id = "importable"
  2099  }
  2100  
  2101  import {
  2102    to = test_resource.a
  2103    id = "importable" 
  2104  }
  2105  `,
  2106  	})
  2107  
  2108  	p := testProvider("test")
  2109  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
  2110  		ResourceTypes: map[string]*configschema.Block{
  2111  			"test_resource": {
  2112  				Attributes: map[string]*configschema.Attribute{
  2113  					"id": {
  2114  						Type:     cty.String,
  2115  						Required: true,
  2116  					},
  2117  				},
  2118  			},
  2119  		},
  2120  	})
  2121  	p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) providers.PlanResourceChangeResponse {
  2122  		return providers.PlanResourceChangeResponse{
  2123  			PlannedState: req.ProposedNewState,
  2124  		}
  2125  	}
  2126  	p.ImportResourceStateFn = func(req providers.ImportResourceStateRequest) providers.ImportResourceStateResponse {
  2127  		return providers.ImportResourceStateResponse{
  2128  			ImportedResources: []providers.ImportedResource{
  2129  				{
  2130  					TypeName: "test_instance",
  2131  					State: cty.ObjectVal(map[string]cty.Value{
  2132  						"id": cty.StringVal("importable"),
  2133  					}),
  2134  				},
  2135  			},
  2136  		}
  2137  	}
  2138  	hook := new(MockHook)
  2139  	ctx := testContext2(t, &ContextOpts{
  2140  		Hooks: []Hook{hook},
  2141  		Providers: map[addrs.Provider]providers.Factory{
  2142  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2143  		},
  2144  	})
  2145  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2146  		Mode: plans.NormalMode,
  2147  	})
  2148  	assertNoErrors(t, diags)
  2149  
  2150  	_, diags = ctx.Apply(plan, m)
  2151  	assertNoErrors(t, diags)
  2152  
  2153  	if !hook.PreApplyImportCalled {
  2154  		t.Fatalf("PreApplyImport hook not called")
  2155  	}
  2156  	if addr, wantAddr := hook.PreApplyImportAddr, mustResourceInstanceAddr("test_resource.a"); !addr.Equal(wantAddr) {
  2157  		t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  2158  	}
  2159  
  2160  	if !hook.PostApplyImportCalled {
  2161  		t.Fatalf("PostApplyImport hook not called")
  2162  	}
  2163  	if addr, wantAddr := hook.PostApplyImportAddr, mustResourceInstanceAddr("test_resource.a"); !addr.Equal(wantAddr) {
  2164  		t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
  2165  	}
  2166  }
  2167  
  2168  func TestContext2Apply_noExternalReferences(t *testing.T) {
  2169  	m := testModuleInline(t, map[string]string{
  2170  		"main.tf": `
  2171  resource "test_object" "a" {
  2172  	test_string = "foo"
  2173  }
  2174  
  2175  locals {
  2176    local_value = test_object.a.test_string
  2177  }
  2178  `,
  2179  	})
  2180  
  2181  	p := simpleMockProvider()
  2182  	ctx := testContext2(t, &ContextOpts{
  2183  		Providers: map[addrs.Provider]providers.Factory{
  2184  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2185  		},
  2186  	})
  2187  
  2188  	plan, diags := ctx.Plan(m, states.NewState(), nil)
  2189  	if diags.HasErrors() {
  2190  		t.Errorf("expected no errors, but got %s", diags)
  2191  	}
  2192  
  2193  	state, diags := ctx.Apply(plan, m)
  2194  	if diags.HasErrors() {
  2195  		t.Errorf("expected no errors, but got %s", diags)
  2196  	}
  2197  
  2198  	// We didn't specify any external references, so the unreferenced local
  2199  	// value should have been tidied up and never made it into the state.
  2200  	module := state.RootModule()
  2201  	if len(module.LocalValues) > 0 {
  2202  		t.Errorf("expected no local values in the state but found %d", len(module.LocalValues))
  2203  	}
  2204  }
  2205  
  2206  func TestContext2Apply_withExternalReferences(t *testing.T) {
  2207  	m := testModuleInline(t, map[string]string{
  2208  		"main.tf": `
  2209  resource "test_object" "a" {
  2210  	test_string = "foo"
  2211  }
  2212  
  2213  locals {
  2214    local_value = test_object.a.test_string
  2215  }
  2216  `,
  2217  	})
  2218  
  2219  	p := simpleMockProvider()
  2220  	ctx := testContext2(t, &ContextOpts{
  2221  		Providers: map[addrs.Provider]providers.Factory{
  2222  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
  2223  		},
  2224  	})
  2225  
  2226  	plan, diags := ctx.Plan(m, states.NewState(), &PlanOpts{
  2227  		Mode: plans.NormalMode,
  2228  		ExternalReferences: []*addrs.Reference{
  2229  			mustReference("local.local_value"),
  2230  		},
  2231  	})
  2232  	if diags.HasErrors() {
  2233  		t.Errorf("expected no errors, but got %s", diags)
  2234  	}
  2235  
  2236  	state, diags := ctx.Apply(plan, m)
  2237  	if diags.HasErrors() {
  2238  		t.Errorf("expected no errors, but got %s", diags)
  2239  	}
  2240  
  2241  	// We did specify the local value in the external references, so it should
  2242  	// have been preserved even though it is not referenced by anything directly
  2243  	// in the config.
  2244  	module := state.RootModule()
  2245  	if module.LocalValues["local_value"].AsString() != "foo" {
  2246  		t.Errorf("expected local value to be \"foo\" but was \"%s\"", module.LocalValues["local_value"].AsString())
  2247  	}
  2248  }
  2249  
  2250  func TestContext2Apply_forgetOrphanAndDeposed(t *testing.T) {
  2251  	desposedKey := states.DeposedKey("deposed")
  2252  	addr := "aws_instance.baz"
  2253  	m := testModuleInline(t, map[string]string{
  2254  		"main.tf": `
  2255  			removed {
  2256  				from = aws_instance.baz
  2257  			}
  2258  		`,
  2259  	})
  2260  	hook := new(MockHook)
  2261  	p := testProvider("aws")
  2262  	state := states.NewState()
  2263  	root := state.EnsureModule(addrs.RootModuleInstance)
  2264  	root.SetResourceInstanceCurrent(
  2265  		mustResourceInstanceAddr(addr).Resource,
  2266  		&states.ResourceInstanceObjectSrc{
  2267  			Status:    states.ObjectReady,
  2268  			AttrsJSON: []byte(`{"id":"bar"}`),
  2269  		},
  2270  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
  2271  	)
  2272  	root.SetResourceInstanceDeposed(
  2273  		mustResourceInstanceAddr(addr).Resource,
  2274  		desposedKey,
  2275  		&states.ResourceInstanceObjectSrc{
  2276  			Status:       states.ObjectTainted,
  2277  			AttrsJSON:    []byte(`{"id":"bar"}`),
  2278  			Dependencies: []addrs.ConfigResource{},
  2279  		},
  2280  		mustProviderConfig(`provider["registry.opentofu.org/hashicorp/aws"]`),
  2281  	)
  2282  	ctx := testContext2(t, &ContextOpts{
  2283  		Providers: map[addrs.Provider]providers.Factory{
  2284  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
  2285  		},
  2286  	})
  2287  
  2288  	p.PlanResourceChangeFn = testDiffFn
  2289  
  2290  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
  2291  	assertNoErrors(t, diags)
  2292  
  2293  	s, diags := ctx.Apply(plan, m)
  2294  	if diags.HasErrors() {
  2295  		t.Fatalf("diags: %s", diags.Err())
  2296  	}
  2297  
  2298  	if !s.Empty() {
  2299  		t.Fatalf("State should be empty")
  2300  	}
  2301  
  2302  	if p.ApplyResourceChangeCalled {
  2303  		t.Fatalf("When we forget we don't call the provider's ApplyResourceChange unlike in destroy")
  2304  	}
  2305  
  2306  	if hook.PostApplyCalled {
  2307  		t.Fatalf("PostApply hook should not be called as part of forget")
  2308  	}
  2309  }