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

     1  package terraform
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/muratcelep/terraform/not-internal/addrs"
    11  	"github.com/muratcelep/terraform/not-internal/configs/configschema"
    12  	"github.com/muratcelep/terraform/not-internal/lang/marks"
    13  	"github.com/muratcelep/terraform/not-internal/plans"
    14  	"github.com/muratcelep/terraform/not-internal/providers"
    15  	"github.com/muratcelep/terraform/not-internal/states"
    16  	"github.com/zclconf/go-cty/cty"
    17  )
    18  
    19  // Test that the PreApply hook is called with the correct deposed key
    20  func TestContext2Apply_createBeforeDestroy_deposedKeyPreApply(t *testing.T) {
    21  	m := testModule(t, "apply-cbd-deposed-only")
    22  	p := testProvider("aws")
    23  	p.PlanResourceChangeFn = testDiffFn
    24  	p.ApplyResourceChangeFn = testApplyFn
    25  
    26  	deposedKey := states.NewDeposedKey()
    27  
    28  	state := states.NewState()
    29  	root := state.EnsureModule(addrs.RootModuleInstance)
    30  	root.SetResourceInstanceCurrent(
    31  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    32  		&states.ResourceInstanceObjectSrc{
    33  			Status:    states.ObjectReady,
    34  			AttrsJSON: []byte(`{"id":"bar"}`),
    35  		},
    36  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
    37  	)
    38  	root.SetResourceInstanceDeposed(
    39  		mustResourceInstanceAddr("aws_instance.bar").Resource,
    40  		deposedKey,
    41  		&states.ResourceInstanceObjectSrc{
    42  			Status:    states.ObjectTainted,
    43  			AttrsJSON: []byte(`{"id":"foo"}`),
    44  		},
    45  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
    46  	)
    47  
    48  	hook := new(MockHook)
    49  	ctx := testContext2(t, &ContextOpts{
    50  		Hooks: []Hook{hook},
    51  		Providers: map[addrs.Provider]providers.Factory{
    52  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
    53  		},
    54  	})
    55  
    56  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
    57  	if diags.HasErrors() {
    58  		t.Fatalf("diags: %s", diags.Err())
    59  	} else {
    60  		t.Logf(legacyDiffComparisonString(plan.Changes))
    61  	}
    62  
    63  	_, diags = ctx.Apply(plan, m)
    64  	if diags.HasErrors() {
    65  		t.Fatalf("diags: %s", diags.Err())
    66  	}
    67  
    68  	// Verify PreApply was called correctly
    69  	if !hook.PreApplyCalled {
    70  		t.Fatalf("PreApply hook not called")
    71  	}
    72  	if addr, wantAddr := hook.PreApplyAddr, mustResourceInstanceAddr("aws_instance.bar"); !addr.Equal(wantAddr) {
    73  		t.Errorf("expected addr to be %s, but was %s", wantAddr, addr)
    74  	}
    75  	if gen := hook.PreApplyGen; gen != deposedKey {
    76  		t.Errorf("expected gen to be %q, but was %q", deposedKey, gen)
    77  	}
    78  }
    79  
    80  func TestContext2Apply_destroyWithDataSourceExpansion(t *testing.T) {
    81  	// While managed resources store their destroy-time dependencies, data
    82  	// sources do not. This means that if a provider were only included in a
    83  	// destroy graph because of data sources, it could have dependencies which
    84  	// are not correctly ordered. Here we verify that the provider is not
    85  	// included in the destroy operation, and all dependency evaluations
    86  	// succeed.
    87  
    88  	m := testModuleInline(t, map[string]string{
    89  		"main.tf": `
    90  module "mod" {
    91    source = "./mod"
    92  }
    93  
    94  provider "other" {
    95    foo = module.mod.data
    96  }
    97  
    98  # this should not require the provider be present during destroy
    99  data "other_data_source" "a" {
   100  }
   101  `,
   102  
   103  		"mod/main.tf": `
   104  data "test_data_source" "a" {
   105    count = 1
   106  }
   107  
   108  data "test_data_source" "b" {
   109    count = data.test_data_source.a[0].foo == "ok" ? 1 : 0
   110  }
   111  
   112  output "data" {
   113    value = data.test_data_source.a[0].foo == "ok" ? data.test_data_source.b[0].foo : "nope"
   114  }
   115  `,
   116  	})
   117  
   118  	testP := testProvider("test")
   119  	otherP := testProvider("other")
   120  
   121  	readData := func(req providers.ReadDataSourceRequest) providers.ReadDataSourceResponse {
   122  		return providers.ReadDataSourceResponse{
   123  			State: cty.ObjectVal(map[string]cty.Value{
   124  				"id":  cty.StringVal("data_source"),
   125  				"foo": cty.StringVal("ok"),
   126  			}),
   127  		}
   128  	}
   129  
   130  	testP.ReadDataSourceFn = readData
   131  	otherP.ReadDataSourceFn = readData
   132  
   133  	ps := map[addrs.Provider]providers.Factory{
   134  		addrs.NewDefaultProvider("test"):  testProviderFuncFixed(testP),
   135  		addrs.NewDefaultProvider("other"): testProviderFuncFixed(otherP),
   136  	}
   137  
   138  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   139  		foo := req.Config.GetAttr("foo")
   140  		if foo.IsNull() || foo.AsString() != "ok" {
   141  			resp.Diagnostics = resp.Diagnostics.Append(fmt.Errorf("incorrect config val: %#v\n", foo))
   142  		}
   143  		return resp
   144  	}
   145  
   146  	ctx := testContext2(t, &ContextOpts{
   147  		Providers: ps,
   148  	})
   149  
   150  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   151  	if diags.HasErrors() {
   152  		t.Fatal(diags.Err())
   153  	}
   154  
   155  	_, diags = ctx.Apply(plan, m)
   156  	if diags.HasErrors() {
   157  		t.Fatal(diags.Err())
   158  	}
   159  
   160  	// now destroy the whole thing
   161  	ctx = testContext2(t, &ContextOpts{
   162  		Providers: ps,
   163  	})
   164  
   165  	plan, diags = ctx.Plan(m, states.NewState(), &PlanOpts{
   166  		Mode: plans.DestroyMode,
   167  	})
   168  	if diags.HasErrors() {
   169  		t.Fatal(diags.Err())
   170  	}
   171  
   172  	otherP.ConfigureProviderFn = func(req providers.ConfigureProviderRequest) (resp providers.ConfigureProviderResponse) {
   173  		// should not be used to destroy data sources
   174  		resp.Diagnostics = resp.Diagnostics.Append(errors.New("provider should not be used"))
   175  		return resp
   176  	}
   177  
   178  	_, diags = ctx.Apply(plan, m)
   179  	if diags.HasErrors() {
   180  		t.Fatal(diags.Err())
   181  	}
   182  }
   183  
   184  func TestContext2Apply_destroyThenUpdate(t *testing.T) {
   185  	m := testModuleInline(t, map[string]string{
   186  		"main.tf": `
   187  resource "test_instance" "a" {
   188  	value = "udpated"
   189  }
   190  `,
   191  	})
   192  
   193  	p := testProvider("test")
   194  	p.PlanResourceChangeFn = testDiffFn
   195  
   196  	var orderMu sync.Mutex
   197  	var order []string
   198  	p.ApplyResourceChangeFn = func(req providers.ApplyResourceChangeRequest) (resp providers.ApplyResourceChangeResponse) {
   199  		id := req.PriorState.GetAttr("id").AsString()
   200  		if id == "b" {
   201  			// slow down the b destroy, since a should wait for it
   202  			time.Sleep(100 * time.Millisecond)
   203  		}
   204  
   205  		orderMu.Lock()
   206  		order = append(order, id)
   207  		orderMu.Unlock()
   208  
   209  		resp.NewState = req.PlannedState
   210  		return resp
   211  	}
   212  
   213  	addrA := mustResourceInstanceAddr(`test_instance.a`)
   214  	addrB := mustResourceInstanceAddr(`test_instance.b`)
   215  
   216  	state := states.BuildState(func(s *states.SyncState) {
   217  		s.SetResourceInstanceCurrent(addrA, &states.ResourceInstanceObjectSrc{
   218  			AttrsJSON: []byte(`{"id":"a","value":"old","type":"test"}`),
   219  			Status:    states.ObjectReady,
   220  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   221  
   222  		// test_instance.b depended on test_instance.a, and therefor should be
   223  		// destroyed before any changes to test_instance.a
   224  		s.SetResourceInstanceCurrent(addrB, &states.ResourceInstanceObjectSrc{
   225  			AttrsJSON:    []byte(`{"id":"b"}`),
   226  			Status:       states.ObjectReady,
   227  			Dependencies: []addrs.ConfigResource{addrA.ContainingResource().Config()},
   228  		}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`))
   229  	})
   230  
   231  	ctx := testContext2(t, &ContextOpts{
   232  		Providers: map[addrs.Provider]providers.Factory{
   233  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   234  		},
   235  	})
   236  
   237  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   238  	assertNoErrors(t, diags)
   239  
   240  	_, diags = ctx.Apply(plan, m)
   241  	if diags.HasErrors() {
   242  		t.Fatal(diags.Err())
   243  	}
   244  
   245  	if order[0] != "b" {
   246  		t.Fatalf("expected apply order [b, a], got: %v\n", order)
   247  	}
   248  }
   249  
   250  // verify that dependencies are updated in the state during refresh and apply
   251  func TestApply_updateDependencies(t *testing.T) {
   252  	state := states.NewState()
   253  	root := state.EnsureModule(addrs.RootModuleInstance)
   254  
   255  	fooAddr := mustResourceInstanceAddr("aws_instance.foo")
   256  	barAddr := mustResourceInstanceAddr("aws_instance.bar")
   257  	bazAddr := mustResourceInstanceAddr("aws_instance.baz")
   258  	bamAddr := mustResourceInstanceAddr("aws_instance.bam")
   259  	binAddr := mustResourceInstanceAddr("aws_instance.bin")
   260  	root.SetResourceInstanceCurrent(
   261  		fooAddr.Resource,
   262  		&states.ResourceInstanceObjectSrc{
   263  			Status:    states.ObjectReady,
   264  			AttrsJSON: []byte(`{"id":"foo"}`),
   265  			Dependencies: []addrs.ConfigResource{
   266  				bazAddr.ContainingResource().Config(),
   267  			},
   268  		},
   269  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   270  	)
   271  	root.SetResourceInstanceCurrent(
   272  		binAddr.Resource,
   273  		&states.ResourceInstanceObjectSrc{
   274  			Status:    states.ObjectReady,
   275  			AttrsJSON: []byte(`{"id":"bin","type":"aws_instance","unknown":"ok"}`),
   276  			Dependencies: []addrs.ConfigResource{
   277  				bazAddr.ContainingResource().Config(),
   278  			},
   279  		},
   280  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   281  	)
   282  	root.SetResourceInstanceCurrent(
   283  		bazAddr.Resource,
   284  		&states.ResourceInstanceObjectSrc{
   285  			Status:    states.ObjectReady,
   286  			AttrsJSON: []byte(`{"id":"baz"}`),
   287  			Dependencies: []addrs.ConfigResource{
   288  				// Existing dependencies should not be removed from orphaned instances
   289  				bamAddr.ContainingResource().Config(),
   290  			},
   291  		},
   292  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   293  	)
   294  	root.SetResourceInstanceCurrent(
   295  		barAddr.Resource,
   296  		&states.ResourceInstanceObjectSrc{
   297  			Status:    states.ObjectReady,
   298  			AttrsJSON: []byte(`{"id":"bar","foo":"foo"}`),
   299  		},
   300  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/aws"]`),
   301  	)
   302  
   303  	m := testModuleInline(t, map[string]string{
   304  		"main.tf": `
   305  resource "aws_instance" "bar" {
   306    foo = aws_instance.foo.id
   307  }
   308  
   309  resource "aws_instance" "foo" {
   310  }
   311  
   312  resource "aws_instance" "bin" {
   313  }
   314  `,
   315  	})
   316  
   317  	p := testProvider("aws")
   318  
   319  	ctx := testContext2(t, &ContextOpts{
   320  		Providers: map[addrs.Provider]providers.Factory{
   321  			addrs.NewDefaultProvider("aws"): testProviderFuncFixed(p),
   322  		},
   323  	})
   324  
   325  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   326  	assertNoErrors(t, diags)
   327  
   328  	bar := plan.PriorState.ResourceInstance(barAddr)
   329  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   330  		t.Fatalf("bar should depend on foo after refresh, but got %s", bar.Current.Dependencies)
   331  	}
   332  
   333  	foo := plan.PriorState.ResourceInstance(fooAddr)
   334  	if len(foo.Current.Dependencies) == 0 || !foo.Current.Dependencies[0].Equal(bazAddr.ContainingResource().Config()) {
   335  		t.Fatalf("foo should depend on baz after refresh because of the update, but got %s", foo.Current.Dependencies)
   336  	}
   337  
   338  	bin := plan.PriorState.ResourceInstance(binAddr)
   339  	if len(bin.Current.Dependencies) != 0 {
   340  		t.Fatalf("bin should depend on nothing after refresh because there is no change, but got %s", bin.Current.Dependencies)
   341  	}
   342  
   343  	baz := plan.PriorState.ResourceInstance(bazAddr)
   344  	if len(baz.Current.Dependencies) == 0 || !baz.Current.Dependencies[0].Equal(bamAddr.ContainingResource().Config()) {
   345  		t.Fatalf("baz should depend on bam after refresh, but got %s", baz.Current.Dependencies)
   346  	}
   347  
   348  	state, diags = ctx.Apply(plan, m)
   349  	if diags.HasErrors() {
   350  		t.Fatal(diags.Err())
   351  	}
   352  
   353  	bar = state.ResourceInstance(barAddr)
   354  	if len(bar.Current.Dependencies) == 0 || !bar.Current.Dependencies[0].Equal(fooAddr.ContainingResource().Config()) {
   355  		t.Fatalf("bar should still depend on foo after apply, but got %s", bar.Current.Dependencies)
   356  	}
   357  
   358  	foo = state.ResourceInstance(fooAddr)
   359  	if len(foo.Current.Dependencies) != 0 {
   360  		t.Fatalf("foo should have no deps after apply, but got %s", foo.Current.Dependencies)
   361  	}
   362  
   363  }
   364  
   365  func TestContext2Apply_additionalSensitiveFromState(t *testing.T) {
   366  	// Ensure we're not trying to double-mark values decoded from state
   367  	m := testModuleInline(t, map[string]string{
   368  		"main.tf": `
   369  variable "secret" {
   370    sensitive = true
   371    default = ["secret"]
   372  }
   373  
   374  resource "test_resource" "a" {
   375    sensitive_attr = var.secret
   376  }
   377  
   378  resource "test_resource" "b" {
   379    value = test_resource.a.id
   380  }
   381  `,
   382  	})
   383  
   384  	p := new(MockProvider)
   385  	p.GetProviderSchemaResponse = getProviderSchemaResponseFromProviderSchema(&ProviderSchema{
   386  		ResourceTypes: map[string]*configschema.Block{
   387  			"test_resource": {
   388  				Attributes: map[string]*configschema.Attribute{
   389  					"id": {
   390  						Type:     cty.String,
   391  						Computed: true,
   392  					},
   393  					"value": {
   394  						Type:     cty.String,
   395  						Optional: true,
   396  					},
   397  					"sensitive_attr": {
   398  						Type:      cty.List(cty.String),
   399  						Optional:  true,
   400  						Sensitive: true,
   401  					},
   402  				},
   403  			},
   404  		},
   405  	})
   406  
   407  	state := states.BuildState(func(s *states.SyncState) {
   408  		s.SetResourceInstanceCurrent(
   409  			mustResourceInstanceAddr(`test_resource.a`),
   410  			&states.ResourceInstanceObjectSrc{
   411  				AttrsJSON: []byte(`{"id":"a","sensitive_attr":["secret"]}`),
   412  				AttrSensitivePaths: []cty.PathValueMarks{
   413  					{
   414  						Path:  cty.GetAttrPath("sensitive_attr"),
   415  						Marks: cty.NewValueMarks(marks.Sensitive),
   416  					},
   417  				},
   418  				Status: states.ObjectReady,
   419  			}, mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   420  		)
   421  	})
   422  
   423  	ctx := testContext2(t, &ContextOpts{
   424  		Providers: map[addrs.Provider]providers.Factory{
   425  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   426  		},
   427  	})
   428  
   429  	plan, diags := ctx.Plan(m, state, DefaultPlanOpts)
   430  	assertNoErrors(t, diags)
   431  
   432  	_, diags = ctx.Apply(plan, m)
   433  	if diags.HasErrors() {
   434  		t.Fatal(diags.ErrWithWarnings())
   435  	}
   436  }
   437  
   438  func TestContext2Apply_sensitiveOutputPassthrough(t *testing.T) {
   439  	// Ensure we're not trying to double-mark values decoded from state
   440  	m := testModuleInline(t, map[string]string{
   441  		"main.tf": `
   442  module "mod" {
   443    source = "./mod"
   444  }
   445  
   446  resource "test_object" "a" {
   447    test_string = module.mod.out
   448  }
   449  `,
   450  
   451  		"mod/main.tf": `
   452  variable "in" {
   453    sensitive = true
   454    default = "foo"
   455  }
   456  output "out" {
   457    value = var.in
   458  }
   459  `,
   460  	})
   461  
   462  	p := simpleMockProvider()
   463  
   464  	ctx := testContext2(t, &ContextOpts{
   465  		Providers: map[addrs.Provider]providers.Factory{
   466  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   467  		},
   468  	})
   469  
   470  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   471  	assertNoErrors(t, diags)
   472  
   473  	state, diags := ctx.Apply(plan, m)
   474  	if diags.HasErrors() {
   475  		t.Fatal(diags.ErrWithWarnings())
   476  	}
   477  
   478  	obj := state.ResourceInstance(mustResourceInstanceAddr("test_object.a"))
   479  	if len(obj.Current.AttrSensitivePaths) != 1 {
   480  		t.Fatalf("Expected 1 sensitive mark for test_object.a, got %#v\n", obj.Current.AttrSensitivePaths)
   481  	}
   482  
   483  	plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
   484  	assertNoErrors(t, diags)
   485  
   486  	// make sure the same marks are compared in the next plan as well
   487  	for _, c := range plan.Changes.Resources {
   488  		if c.Action != plans.NoOp {
   489  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   490  		}
   491  	}
   492  }
   493  
   494  func TestContext2Apply_ignoreImpureFunctionChanges(t *testing.T) {
   495  	// The impure function call should not cause a planned change with
   496  	// ignore_changes
   497  	m := testModuleInline(t, map[string]string{
   498  		"main.tf": `
   499  variable "pw" {
   500    sensitive = true
   501    default = "foo"
   502  }
   503  
   504  resource "test_object" "x" {
   505    test_map = {
   506  	string = "X${bcrypt(var.pw)}"
   507    }
   508    lifecycle {
   509      ignore_changes = [ test_map["string"] ]
   510    }
   511  }
   512  
   513  resource "test_object" "y" {
   514    test_map = {
   515  	string = "X${bcrypt(var.pw)}"
   516    }
   517    lifecycle {
   518      ignore_changes = [ test_map ]
   519    }
   520  }
   521  
   522  `,
   523  	})
   524  
   525  	p := simpleMockProvider()
   526  
   527  	ctx := testContext2(t, &ContextOpts{
   528  		Providers: map[addrs.Provider]providers.Factory{
   529  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   530  		},
   531  	})
   532  
   533  	plan, diags := ctx.Plan(m, states.NewState(), DefaultPlanOpts)
   534  	assertNoErrors(t, diags)
   535  
   536  	state, diags := ctx.Apply(plan, m)
   537  	assertNoErrors(t, diags)
   538  
   539  	// FINAL PLAN:
   540  	plan, diags = ctx.Plan(m, state, DefaultPlanOpts)
   541  	assertNoErrors(t, diags)
   542  
   543  	// make sure the same marks are compared in the next plan as well
   544  	for _, c := range plan.Changes.Resources {
   545  		if c.Action != plans.NoOp {
   546  			t.Logf("marks before: %#v", c.BeforeValMarks)
   547  			t.Logf("marks after:  %#v", c.AfterValMarks)
   548  			t.Errorf("Unexpcetd %s change for %s", c.Action, c.Addr)
   549  		}
   550  	}
   551  }
   552  
   553  func TestContext2Apply_destroyWithDeposed(t *testing.T) {
   554  	m := testModuleInline(t, map[string]string{
   555  		"main.tf": `
   556  resource "test_object" "x" {
   557    test_string = "ok"
   558    lifecycle {
   559      create_before_destroy = true
   560    }
   561  }`,
   562  	})
   563  
   564  	p := simpleMockProvider()
   565  
   566  	deposedKey := states.NewDeposedKey()
   567  
   568  	state := states.NewState()
   569  	root := state.EnsureModule(addrs.RootModuleInstance)
   570  	root.SetResourceInstanceDeposed(
   571  		mustResourceInstanceAddr("test_object.x").Resource,
   572  		deposedKey,
   573  		&states.ResourceInstanceObjectSrc{
   574  			Status:    states.ObjectTainted,
   575  			AttrsJSON: []byte(`{"test_string":"deposed"}`),
   576  		},
   577  		mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`),
   578  	)
   579  
   580  	ctx := testContext2(t, &ContextOpts{
   581  		Providers: map[addrs.Provider]providers.Factory{
   582  			addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
   583  		},
   584  	})
   585  
   586  	plan, diags := ctx.Plan(m, state, &PlanOpts{
   587  		Mode: plans.DestroyMode,
   588  	})
   589  	if diags.HasErrors() {
   590  		t.Fatalf("plan: %s", diags.Err())
   591  	}
   592  
   593  	_, diags = ctx.Apply(plan, m)
   594  	if diags.HasErrors() {
   595  		t.Fatalf("apply: %s", diags.Err())
   596  	}
   597  
   598  }
   599  
   600  func TestContext2Apply_nullableVariables(t *testing.T) {
   601  	m := testModule(t, "apply-nullable-variables")
   602  	state := states.NewState()
   603  	ctx := testContext2(t, &ContextOpts{})
   604  	plan, diags := ctx.Plan(m, state, &PlanOpts{})
   605  	if diags.HasErrors() {
   606  		t.Fatalf("plan: %s", diags.Err())
   607  	}
   608  	state, diags = ctx.Apply(plan, m)
   609  	if diags.HasErrors() {
   610  		t.Fatalf("apply: %s", diags.Err())
   611  	}
   612  
   613  	outputs := state.Module(addrs.RootModuleInstance).OutputValues
   614  	// we check for null outputs be seeing that they don't exists
   615  	if _, ok := outputs["nullable_null_default"]; ok {
   616  		t.Error("nullable_null_default: expected no output value")
   617  	}
   618  	if _, ok := outputs["nullable_non_null_default"]; ok {
   619  		t.Error("nullable_non_null_default: expected no output value")
   620  	}
   621  	if _, ok := outputs["nullable_no_default"]; ok {
   622  		t.Error("nullable_no_default: expected no output value")
   623  	}
   624  
   625  	if v := outputs["non_nullable_default"].Value; v.AsString() != "ok" {
   626  		t.Fatalf("incorrect 'non_nullable_default' output value: %#v\n", v)
   627  	}
   628  	if v := outputs["non_nullable_no_default"].Value; v.AsString() != "ok" {
   629  		t.Fatalf("incorrect 'non_nullable_no_default' output value: %#v\n", v)
   630  	}
   631  }