github.com/opentofu/opentofu@v1.7.1/internal/tofu/evaluate_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  	"sync"
    10  	"testing"
    11  
    12  	"github.com/davecgh/go-spew/spew"
    13  	"github.com/zclconf/go-cty/cty"
    14  
    15  	"github.com/opentofu/opentofu/internal/addrs"
    16  	"github.com/opentofu/opentofu/internal/configs"
    17  	"github.com/opentofu/opentofu/internal/configs/configschema"
    18  	"github.com/opentofu/opentofu/internal/lang/marks"
    19  	"github.com/opentofu/opentofu/internal/plans"
    20  	"github.com/opentofu/opentofu/internal/providers"
    21  	"github.com/opentofu/opentofu/internal/states"
    22  	"github.com/opentofu/opentofu/internal/tfdiags"
    23  )
    24  
    25  func TestEvaluatorGetTerraformAttr(t *testing.T) {
    26  	evaluator := &Evaluator{
    27  		Meta: &ContextMeta{
    28  			Env: "foo",
    29  		},
    30  	}
    31  	data := &evaluationStateData{
    32  		Evaluator: evaluator,
    33  	}
    34  	scope := evaluator.Scope(data, nil, nil, nil)
    35  
    36  	t.Run("workspace", func(t *testing.T) {
    37  		want := cty.StringVal("foo")
    38  		got, diags := scope.Data.GetTerraformAttr(addrs.TerraformAttr{
    39  			Name: "workspace",
    40  		}, tfdiags.SourceRange{})
    41  		if len(diags) != 0 {
    42  			t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
    43  		}
    44  		if !got.RawEquals(want) {
    45  			t.Errorf("wrong result %q; want %q", got, want)
    46  		}
    47  	})
    48  }
    49  
    50  func TestEvaluatorGetPathAttr(t *testing.T) {
    51  	evaluator := &Evaluator{
    52  		Meta: &ContextMeta{
    53  			Env: "foo",
    54  		},
    55  		Config: &configs.Config{
    56  			Module: &configs.Module{
    57  				SourceDir: "bar/baz",
    58  			},
    59  		},
    60  	}
    61  	data := &evaluationStateData{
    62  		Evaluator: evaluator,
    63  	}
    64  	scope := evaluator.Scope(data, nil, nil, nil)
    65  
    66  	t.Run("module", func(t *testing.T) {
    67  		want := cty.StringVal("bar/baz")
    68  		got, diags := scope.Data.GetPathAttr(addrs.PathAttr{
    69  			Name: "module",
    70  		}, tfdiags.SourceRange{})
    71  		if len(diags) != 0 {
    72  			t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
    73  		}
    74  		if !got.RawEquals(want) {
    75  			t.Errorf("wrong result %#v; want %#v", got, want)
    76  		}
    77  	})
    78  
    79  	t.Run("root", func(t *testing.T) {
    80  		want := cty.StringVal("bar/baz")
    81  		got, diags := scope.Data.GetPathAttr(addrs.PathAttr{
    82  			Name: "root",
    83  		}, tfdiags.SourceRange{})
    84  		if len(diags) != 0 {
    85  			t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
    86  		}
    87  		if !got.RawEquals(want) {
    88  			t.Errorf("wrong result %#v; want %#v", got, want)
    89  		}
    90  	})
    91  }
    92  
    93  func TestEvaluatorGetOutputValue(t *testing.T) {
    94  	evaluator := &Evaluator{
    95  		Meta: &ContextMeta{
    96  			Env: "foo",
    97  		},
    98  		Config: &configs.Config{
    99  			Module: &configs.Module{
   100  				Outputs: map[string]*configs.Output{
   101  					"some_output": {
   102  						Name:      "some_output",
   103  						Sensitive: true,
   104  					},
   105  					"some_other_output": {
   106  						Name: "some_other_output",
   107  					},
   108  				},
   109  			},
   110  		},
   111  		State: states.BuildState(func(state *states.SyncState) {
   112  			state.SetOutputValue(addrs.AbsOutputValue{
   113  				Module: addrs.RootModuleInstance,
   114  				OutputValue: addrs.OutputValue{
   115  					Name: "some_output",
   116  				},
   117  			}, cty.StringVal("first"), true)
   118  			state.SetOutputValue(addrs.AbsOutputValue{
   119  				Module: addrs.RootModuleInstance,
   120  				OutputValue: addrs.OutputValue{
   121  					Name: "some_other_output",
   122  				},
   123  			}, cty.StringVal("second"), false)
   124  		}).SyncWrapper(),
   125  	}
   126  
   127  	data := &evaluationStateData{
   128  		Evaluator: evaluator,
   129  	}
   130  	scope := evaluator.Scope(data, nil, nil, nil)
   131  
   132  	want := cty.StringVal("first").Mark(marks.Sensitive)
   133  	got, diags := scope.Data.GetOutput(addrs.OutputValue{
   134  		Name: "some_output",
   135  	}, tfdiags.SourceRange{})
   136  
   137  	if len(diags) != 0 {
   138  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   139  	}
   140  	if !got.RawEquals(want) {
   141  		t.Errorf("wrong result %#v; want %#v", got, want)
   142  	}
   143  
   144  	want = cty.StringVal("second")
   145  	got, diags = scope.Data.GetOutput(addrs.OutputValue{
   146  		Name: "some_other_output",
   147  	}, tfdiags.SourceRange{})
   148  
   149  	if len(diags) != 0 {
   150  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   151  	}
   152  	if !got.RawEquals(want) {
   153  		t.Errorf("wrong result %#v; want %#v", got, want)
   154  	}
   155  }
   156  
   157  // This particularly tests that a sensitive attribute in config
   158  // results in a value that has a "sensitive" cty Mark
   159  func TestEvaluatorGetInputVariable(t *testing.T) {
   160  	evaluator := &Evaluator{
   161  		Meta: &ContextMeta{
   162  			Env: "foo",
   163  		},
   164  		Config: &configs.Config{
   165  			Module: &configs.Module{
   166  				Variables: map[string]*configs.Variable{
   167  					"some_var": {
   168  						Name:           "some_var",
   169  						Sensitive:      true,
   170  						Default:        cty.StringVal("foo"),
   171  						Type:           cty.String,
   172  						ConstraintType: cty.String,
   173  					},
   174  					// Avoid double marking a value
   175  					"some_other_var": {
   176  						Name:           "some_other_var",
   177  						Sensitive:      true,
   178  						Default:        cty.StringVal("bar"),
   179  						Type:           cty.String,
   180  						ConstraintType: cty.String,
   181  					},
   182  				},
   183  			},
   184  		},
   185  		VariableValues: map[string]map[string]cty.Value{
   186  			"": {
   187  				"some_var":       cty.StringVal("bar"),
   188  				"some_other_var": cty.StringVal("boop").Mark(marks.Sensitive),
   189  			},
   190  		},
   191  		VariableValuesLock: &sync.Mutex{},
   192  	}
   193  
   194  	data := &evaluationStateData{
   195  		Evaluator: evaluator,
   196  	}
   197  	scope := evaluator.Scope(data, nil, nil, nil)
   198  
   199  	want := cty.StringVal("bar").Mark(marks.Sensitive)
   200  	got, diags := scope.Data.GetInputVariable(addrs.InputVariable{
   201  		Name: "some_var",
   202  	}, tfdiags.SourceRange{})
   203  
   204  	if len(diags) != 0 {
   205  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   206  	}
   207  	if !got.RawEquals(want) {
   208  		t.Errorf("wrong result %#v; want %#v", got, want)
   209  	}
   210  
   211  	want = cty.StringVal("boop").Mark(marks.Sensitive)
   212  	got, diags = scope.Data.GetInputVariable(addrs.InputVariable{
   213  		Name: "some_other_var",
   214  	}, tfdiags.SourceRange{})
   215  
   216  	if len(diags) != 0 {
   217  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   218  	}
   219  	if !got.RawEquals(want) {
   220  		t.Errorf("wrong result %#v; want %#v", got, want)
   221  	}
   222  }
   223  
   224  func TestEvaluatorGetResource(t *testing.T) {
   225  	stateSync := states.BuildState(func(ss *states.SyncState) {
   226  		ss.SetResourceInstanceCurrent(
   227  			addrs.Resource{
   228  				Mode: addrs.ManagedResourceMode,
   229  				Type: "test_resource",
   230  				Name: "foo",
   231  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   232  			&states.ResourceInstanceObjectSrc{
   233  				Status:    states.ObjectReady,
   234  				AttrsJSON: []byte(`{"id":"foo", "nesting_list": [{"sensitive_value":"abc"}], "nesting_map": {"foo":{"foo":"x"}}, "nesting_set": [{"baz":"abc"}], "nesting_single": {"boop":"abc"}, "nesting_nesting": {"nesting_list":[{"sensitive_value":"abc"}]}, "value":"hello"}`),
   235  			},
   236  			addrs.AbsProviderConfig{
   237  				Provider: addrs.NewDefaultProvider("test"),
   238  				Module:   addrs.RootModule,
   239  			},
   240  		)
   241  	}).SyncWrapper()
   242  
   243  	rc := &configs.Resource{
   244  		Mode: addrs.ManagedResourceMode,
   245  		Type: "test_resource",
   246  		Name: "foo",
   247  		Config: configs.SynthBody("", map[string]cty.Value{
   248  			"id": cty.StringVal("foo"),
   249  		}),
   250  		Provider: addrs.Provider{
   251  			Hostname:  addrs.DefaultProviderRegistryHost,
   252  			Namespace: "hashicorp",
   253  			Type:      "test",
   254  		},
   255  	}
   256  
   257  	evaluator := &Evaluator{
   258  		Meta: &ContextMeta{
   259  			Env: "foo",
   260  		},
   261  		Changes: plans.NewChanges().SyncWrapper(),
   262  		Config: &configs.Config{
   263  			Module: &configs.Module{
   264  				ManagedResources: map[string]*configs.Resource{
   265  					"test_resource.foo": rc,
   266  				},
   267  			},
   268  		},
   269  		State: stateSync,
   270  		Plugins: schemaOnlyProvidersForTesting(map[addrs.Provider]providers.ProviderSchema{
   271  			addrs.NewDefaultProvider("test"): {
   272  				ResourceTypes: map[string]providers.Schema{
   273  					"test_resource": {
   274  						Block: &configschema.Block{
   275  							Attributes: map[string]*configschema.Attribute{
   276  								"id": {
   277  									Type:     cty.String,
   278  									Computed: true,
   279  								},
   280  								"value": {
   281  									Type:      cty.String,
   282  									Computed:  true,
   283  									Sensitive: true,
   284  								},
   285  							},
   286  							BlockTypes: map[string]*configschema.NestedBlock{
   287  								"nesting_list": {
   288  									Block: configschema.Block{
   289  										Attributes: map[string]*configschema.Attribute{
   290  											"value":           {Type: cty.String, Optional: true},
   291  											"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
   292  										},
   293  									},
   294  									Nesting: configschema.NestingList,
   295  								},
   296  								"nesting_map": {
   297  									Block: configschema.Block{
   298  										Attributes: map[string]*configschema.Attribute{
   299  											"foo": {Type: cty.String, Optional: true, Sensitive: true},
   300  										},
   301  									},
   302  									Nesting: configschema.NestingMap,
   303  								},
   304  								"nesting_set": {
   305  									Block: configschema.Block{
   306  										Attributes: map[string]*configschema.Attribute{
   307  											"baz": {Type: cty.String, Optional: true, Sensitive: true},
   308  										},
   309  									},
   310  									Nesting: configschema.NestingSet,
   311  								},
   312  								"nesting_single": {
   313  									Block: configschema.Block{
   314  										Attributes: map[string]*configschema.Attribute{
   315  											"boop": {Type: cty.String, Optional: true, Sensitive: true},
   316  										},
   317  									},
   318  									Nesting: configschema.NestingSingle,
   319  								},
   320  								"nesting_nesting": {
   321  									Block: configschema.Block{
   322  										BlockTypes: map[string]*configschema.NestedBlock{
   323  											"nesting_list": {
   324  												Block: configschema.Block{
   325  													Attributes: map[string]*configschema.Attribute{
   326  														"value":           {Type: cty.String, Optional: true},
   327  														"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
   328  													},
   329  												},
   330  												Nesting: configschema.NestingList,
   331  											},
   332  										},
   333  									},
   334  									Nesting: configschema.NestingSingle,
   335  								},
   336  							},
   337  						},
   338  					},
   339  				},
   340  			},
   341  		}, t),
   342  	}
   343  
   344  	data := &evaluationStateData{
   345  		Evaluator: evaluator,
   346  	}
   347  	scope := evaluator.Scope(data, nil, nil, nil)
   348  
   349  	want := cty.ObjectVal(map[string]cty.Value{
   350  		"id": cty.StringVal("foo"),
   351  		"nesting_list": cty.ListVal([]cty.Value{
   352  			cty.ObjectVal(map[string]cty.Value{
   353  				"sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive),
   354  				"value":           cty.NullVal(cty.String),
   355  			}),
   356  		}),
   357  		"nesting_map": cty.MapVal(map[string]cty.Value{
   358  			"foo": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("x").Mark(marks.Sensitive)}),
   359  		}),
   360  		"nesting_nesting": cty.ObjectVal(map[string]cty.Value{
   361  			"nesting_list": cty.ListVal([]cty.Value{
   362  				cty.ObjectVal(map[string]cty.Value{
   363  					"sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive),
   364  					"value":           cty.NullVal(cty.String),
   365  				}),
   366  			}),
   367  		}),
   368  		"nesting_set": cty.SetVal([]cty.Value{
   369  			cty.ObjectVal(map[string]cty.Value{
   370  				"baz": cty.StringVal("abc").Mark(marks.Sensitive),
   371  			}),
   372  		}),
   373  		"nesting_single": cty.ObjectVal(map[string]cty.Value{
   374  			"boop": cty.StringVal("abc").Mark(marks.Sensitive),
   375  		}),
   376  		"value": cty.StringVal("hello").Mark(marks.Sensitive),
   377  	})
   378  
   379  	addr := addrs.Resource{
   380  		Mode: addrs.ManagedResourceMode,
   381  		Type: "test_resource",
   382  		Name: "foo",
   383  	}
   384  	got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{})
   385  
   386  	if len(diags) != 0 {
   387  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   388  	}
   389  
   390  	if !got.RawEquals(want) {
   391  		t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want)
   392  	}
   393  }
   394  
   395  // GetResource will return a planned object's After value
   396  // if there is a change for that resource instance.
   397  func TestEvaluatorGetResource_changes(t *testing.T) {
   398  	// Set up existing state
   399  	stateSync := states.BuildState(func(ss *states.SyncState) {
   400  		ss.SetResourceInstanceCurrent(
   401  			addrs.Resource{
   402  				Mode: addrs.ManagedResourceMode,
   403  				Type: "test_resource",
   404  				Name: "foo",
   405  			}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
   406  			&states.ResourceInstanceObjectSrc{
   407  				Status:    states.ObjectPlanned,
   408  				AttrsJSON: []byte(`{"id":"foo", "to_mark_val":"tacos", "sensitive_value":"abc"}`),
   409  			},
   410  			addrs.AbsProviderConfig{
   411  				Provider: addrs.NewDefaultProvider("test"),
   412  				Module:   addrs.RootModule,
   413  			},
   414  		)
   415  	}).SyncWrapper()
   416  
   417  	// Create a change for the existing state resource,
   418  	// to exercise retrieving the After value of the change
   419  	changesSync := plans.NewChanges().SyncWrapper()
   420  	change := &plans.ResourceInstanceChange{
   421  		Addr: mustResourceInstanceAddr("test_resource.foo"),
   422  		ProviderAddr: addrs.AbsProviderConfig{
   423  			Module:   addrs.RootModule,
   424  			Provider: addrs.NewDefaultProvider("test"),
   425  		},
   426  		Change: plans.Change{
   427  			Action: plans.Update,
   428  			// Provide an After value that contains a marked value
   429  			After: cty.ObjectVal(map[string]cty.Value{
   430  				"id":              cty.StringVal("foo"),
   431  				"to_mark_val":     cty.StringVal("pizza").Mark(marks.Sensitive),
   432  				"sensitive_value": cty.StringVal("abc"),
   433  				"sensitive_collection": cty.MapVal(map[string]cty.Value{
   434  					"boop": cty.StringVal("beep"),
   435  				}),
   436  			}),
   437  		},
   438  	}
   439  
   440  	// Set up our schemas
   441  	schemas := &Schemas{
   442  		Providers: map[addrs.Provider]providers.ProviderSchema{
   443  			addrs.NewDefaultProvider("test"): {
   444  				ResourceTypes: map[string]providers.Schema{
   445  					"test_resource": {
   446  						Block: &configschema.Block{
   447  							Attributes: map[string]*configschema.Attribute{
   448  								"id": {
   449  									Type:     cty.String,
   450  									Computed: true,
   451  								},
   452  								"to_mark_val": {
   453  									Type:     cty.String,
   454  									Computed: true,
   455  								},
   456  								"sensitive_value": {
   457  									Type:      cty.String,
   458  									Computed:  true,
   459  									Sensitive: true,
   460  								},
   461  								"sensitive_collection": {
   462  									Type:      cty.Map(cty.String),
   463  									Computed:  true,
   464  									Sensitive: true,
   465  								},
   466  							},
   467  						},
   468  					},
   469  				},
   470  			},
   471  		},
   472  	}
   473  
   474  	// The resource we'll inspect
   475  	addr := addrs.Resource{
   476  		Mode: addrs.ManagedResourceMode,
   477  		Type: "test_resource",
   478  		Name: "foo",
   479  	}
   480  	schema, _ := schemas.ResourceTypeConfig(addrs.NewDefaultProvider("test"), addr.Mode, addr.Type)
   481  	// This encoding separates out the After's marks into its AfterValMarks
   482  	csrc, _ := change.Encode(schema.ImpliedType())
   483  	changesSync.AppendResourceInstanceChange(csrc)
   484  
   485  	evaluator := &Evaluator{
   486  		Meta: &ContextMeta{
   487  			Env: "foo",
   488  		},
   489  		Changes: changesSync,
   490  		Config: &configs.Config{
   491  			Module: &configs.Module{
   492  				ManagedResources: map[string]*configs.Resource{
   493  					"test_resource.foo": {
   494  						Mode: addrs.ManagedResourceMode,
   495  						Type: "test_resource",
   496  						Name: "foo",
   497  						Provider: addrs.Provider{
   498  							Hostname:  addrs.DefaultProviderRegistryHost,
   499  							Namespace: "hashicorp",
   500  							Type:      "test",
   501  						},
   502  					},
   503  				},
   504  			},
   505  		},
   506  		State:   stateSync,
   507  		Plugins: schemaOnlyProvidersForTesting(schemas.Providers, t),
   508  	}
   509  
   510  	data := &evaluationStateData{
   511  		Evaluator: evaluator,
   512  	}
   513  	scope := evaluator.Scope(data, nil, nil, nil)
   514  
   515  	want := cty.ObjectVal(map[string]cty.Value{
   516  		"id":              cty.StringVal("foo"),
   517  		"to_mark_val":     cty.StringVal("pizza").Mark(marks.Sensitive),
   518  		"sensitive_value": cty.StringVal("abc").Mark(marks.Sensitive),
   519  		"sensitive_collection": cty.MapVal(map[string]cty.Value{
   520  			"boop": cty.StringVal("beep"),
   521  		}).Mark(marks.Sensitive),
   522  	})
   523  
   524  	got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{})
   525  
   526  	if len(diags) != 0 {
   527  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   528  	}
   529  
   530  	if !got.RawEquals(want) {
   531  		t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want)
   532  	}
   533  }
   534  
   535  func TestEvaluatorGetModule(t *testing.T) {
   536  	// Create a new evaluator with an existing state
   537  	stateSync := states.BuildState(func(ss *states.SyncState) {
   538  		ss.SetOutputValue(
   539  			addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}),
   540  			cty.StringVal("bar"),
   541  			true,
   542  		)
   543  	}).SyncWrapper()
   544  	evaluator := evaluatorForModule(stateSync, plans.NewChanges().SyncWrapper())
   545  	data := &evaluationStateData{
   546  		Evaluator: evaluator,
   547  	}
   548  	scope := evaluator.Scope(data, nil, nil, nil)
   549  	want := cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("bar").Mark(marks.Sensitive)})
   550  	got, diags := scope.Data.GetModule(addrs.ModuleCall{
   551  		Name: "mod",
   552  	}, tfdiags.SourceRange{})
   553  
   554  	if len(diags) != 0 {
   555  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   556  	}
   557  	if !got.RawEquals(want) {
   558  		t.Errorf("wrong result %#v; want %#v", got, want)
   559  	}
   560  
   561  	// Changes should override the state value
   562  	changesSync := plans.NewChanges().SyncWrapper()
   563  	change := &plans.OutputChange{
   564  		Addr:      addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}),
   565  		Sensitive: true,
   566  		Change: plans.Change{
   567  			After: cty.StringVal("baz"),
   568  		},
   569  	}
   570  	cs, _ := change.Encode()
   571  	changesSync.AppendOutputChange(cs)
   572  	evaluator = evaluatorForModule(stateSync, changesSync)
   573  	data = &evaluationStateData{
   574  		Evaluator: evaluator,
   575  	}
   576  	scope = evaluator.Scope(data, nil, nil, nil)
   577  	want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)})
   578  	got, diags = scope.Data.GetModule(addrs.ModuleCall{
   579  		Name: "mod",
   580  	}, tfdiags.SourceRange{})
   581  
   582  	if len(diags) != 0 {
   583  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   584  	}
   585  	if !got.RawEquals(want) {
   586  		t.Errorf("wrong result %#v; want %#v", got, want)
   587  	}
   588  
   589  	// Test changes with empty state
   590  	evaluator = evaluatorForModule(states.NewState().SyncWrapper(), changesSync)
   591  	data = &evaluationStateData{
   592  		Evaluator: evaluator,
   593  	}
   594  	scope = evaluator.Scope(data, nil, nil, nil)
   595  	want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark(marks.Sensitive)})
   596  	got, diags = scope.Data.GetModule(addrs.ModuleCall{
   597  		Name: "mod",
   598  	}, tfdiags.SourceRange{})
   599  
   600  	if len(diags) != 0 {
   601  		t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
   602  	}
   603  	if !got.RawEquals(want) {
   604  		t.Errorf("wrong result %#v; want %#v", got, want)
   605  	}
   606  }
   607  
   608  func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync) *Evaluator {
   609  	return &Evaluator{
   610  		Meta: &ContextMeta{
   611  			Env: "foo",
   612  		},
   613  		Config: &configs.Config{
   614  			Module: &configs.Module{
   615  				ModuleCalls: map[string]*configs.ModuleCall{
   616  					"mod": {
   617  						Name: "mod",
   618  					},
   619  				},
   620  			},
   621  			Children: map[string]*configs.Config{
   622  				"mod": {
   623  					Path: addrs.Module{"module.mod"},
   624  					Module: &configs.Module{
   625  						Outputs: map[string]*configs.Output{
   626  							"out": {
   627  								Name:      "out",
   628  								Sensitive: true,
   629  							},
   630  						},
   631  					},
   632  				},
   633  			},
   634  		},
   635  		State:   stateSync,
   636  		Changes: changesSync,
   637  	}
   638  }