opentofu/terraform/evaluate_test.go
Pam Selle 394e60608c Allocate new copies of paths to avoid append drama
Create new copies of the Path to avoid possible append
related dramas. Also add a test to cover nested block
within blocks
2020-10-19 15:24:14 -04:00

404 lines
11 KiB
Go

package terraform
import (
"sync"
"testing"
"github.com/davecgh/go-spew/spew"
"github.com/zclconf/go-cty/cty"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/configs"
"github.com/hashicorp/terraform/configs/configschema"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/tfdiags"
)
func TestEvaluatorGetTerraformAttr(t *testing.T) {
evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
}
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
t.Run("workspace", func(t *testing.T) {
want := cty.StringVal("foo")
got, diags := scope.Data.GetTerraformAttr(addrs.TerraformAttr{
Name: "workspace",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %q; want %q", got, want)
}
})
}
func TestEvaluatorGetPathAttr(t *testing.T) {
evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
Config: &configs.Config{
Module: &configs.Module{
SourceDir: "bar/baz",
},
},
}
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
t.Run("module", func(t *testing.T) {
want := cty.StringVal("bar/baz")
got, diags := scope.Data.GetPathAttr(addrs.PathAttr{
Name: "module",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
})
t.Run("root", func(t *testing.T) {
want := cty.StringVal("bar/baz")
got, diags := scope.Data.GetPathAttr(addrs.PathAttr{
Name: "root",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
})
}
// This particularly tests that a sensitive attribute in config
// results in a value that has a "sensitive" cty Mark
func TestEvaluatorGetInputVariable(t *testing.T) {
evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
Config: &configs.Config{
Module: &configs.Module{
Variables: map[string]*configs.Variable{
"some_var": {
Name: "some_var",
Sensitive: true,
Default: cty.StringVal("foo"),
},
},
},
},
VariableValues: map[string]map[string]cty.Value{
"": {"some_var": cty.StringVal("bar")},
},
VariableValuesLock: &sync.Mutex{},
}
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
want := cty.StringVal("bar").Mark("sensitive")
got, diags := scope.Data.GetInputVariable(addrs.InputVariable{
Name: "some_var",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
}
func TestEvaluatorGetResource(t *testing.T) {
stateSync := states.BuildState(func(ss *states.SyncState) {
ss.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
Status: states.ObjectReady,
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"}`),
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("test"),
Module: addrs.RootModule,
},
)
}).SyncWrapper()
rc := &configs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
Config: configs.SynthBody("", map[string]cty.Value{
"id": cty.StringVal("foo"),
}),
Provider: addrs.Provider{
Hostname: addrs.DefaultRegistryHost,
Namespace: "hashicorp",
Type: "test",
},
}
evaluator := &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
Changes: plans.NewChanges().SyncWrapper(),
Config: &configs.Config{
Module: &configs.Module{
ManagedResources: map[string]*configs.Resource{
"test_resource.foo": rc,
},
},
},
State: stateSync,
Schemas: &Schemas{
Providers: map[addrs.Provider]*ProviderSchema{
addrs.NewDefaultProvider("test"): {
Provider: &configschema.Block{},
ResourceTypes: map[string]*configschema.Block{
"test_resource": {
Attributes: map[string]*configschema.Attribute{
"id": {
Type: cty.String,
Computed: true,
},
"value": {
Type: cty.String,
Computed: true,
Sensitive: true,
},
},
BlockTypes: map[string]*configschema.NestedBlock{
"nesting_list": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingList,
},
"nesting_map": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"foo": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingMap,
},
"nesting_set": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"baz": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingSet,
},
"nesting_single": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"boop": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingSingle,
},
"nesting_nesting": {
Block: configschema.Block{
BlockTypes: map[string]*configschema.NestedBlock{
"nesting_list": {
Block: configschema.Block{
Attributes: map[string]*configschema.Attribute{
"value": {Type: cty.String, Optional: true},
"sensitive_value": {Type: cty.String, Optional: true, Sensitive: true},
},
},
Nesting: configschema.NestingList,
},
},
},
Nesting: configschema.NestingSingle,
},
},
},
},
},
},
},
}
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
want := cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("foo"),
"nesting_list": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"sensitive_value": cty.StringVal("abc").Mark("sensitive"),
"value": cty.NullVal(cty.String),
}),
}),
"nesting_map": cty.MapVal(map[string]cty.Value{
"foo": cty.ObjectVal(map[string]cty.Value{"foo": cty.StringVal("x").Mark("sensitive")}),
}),
"nesting_nesting": cty.ObjectVal(map[string]cty.Value{
"nesting_list": cty.ListVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"sensitive_value": cty.StringVal("abc").Mark("sensitive"),
"value": cty.NullVal(cty.String),
}),
}),
}),
"nesting_set": cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"baz": cty.StringVal("abc").Mark("sensitive"),
}),
}),
"nesting_single": cty.ObjectVal(map[string]cty.Value{
"boop": cty.StringVal("abc").Mark("sensitive"),
}),
"value": cty.StringVal("hello").Mark("sensitive"),
})
addr := addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_resource",
Name: "foo",
}
got, diags := scope.Data.GetResource(addr, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result:\ngot: %#v\nwant: %#v", got, want)
}
}
func TestEvaluatorGetModule(t *testing.T) {
// Create a new evaluator with an existing state
stateSync := states.BuildState(func(ss *states.SyncState) {
ss.SetOutputValue(
addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}),
cty.StringVal("bar"),
true,
)
}).SyncWrapper()
evaluator := evaluatorForModule(stateSync, plans.NewChanges().SyncWrapper())
data := &evaluationStateData{
Evaluator: evaluator,
}
scope := evaluator.Scope(data, nil)
want := cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("bar").Mark("sensitive")})
got, diags := scope.Data.GetModule(addrs.ModuleCall{
Name: "mod",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
// Changes should override the state value
changesSync := plans.NewChanges().SyncWrapper()
change := &plans.OutputChange{
Addr: addrs.OutputValue{Name: "out"}.Absolute(addrs.ModuleInstance{addrs.ModuleInstanceStep{Name: "mod"}}),
Sensitive: true,
Change: plans.Change{
After: cty.StringVal("baz"),
},
}
cs, _ := change.Encode()
changesSync.AppendOutputChange(cs)
evaluator = evaluatorForModule(stateSync, changesSync)
data = &evaluationStateData{
Evaluator: evaluator,
}
scope = evaluator.Scope(data, nil)
want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark("sensitive")})
got, diags = scope.Data.GetModule(addrs.ModuleCall{
Name: "mod",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
// Test changes with empty state
evaluator = evaluatorForModule(states.NewState().SyncWrapper(), changesSync)
data = &evaluationStateData{
Evaluator: evaluator,
}
scope = evaluator.Scope(data, nil)
want = cty.ObjectVal(map[string]cty.Value{"out": cty.StringVal("baz").Mark("sensitive")})
got, diags = scope.Data.GetModule(addrs.ModuleCall{
Name: "mod",
}, tfdiags.SourceRange{})
if len(diags) != 0 {
t.Errorf("unexpected diagnostics %s", spew.Sdump(diags))
}
if !got.RawEquals(want) {
t.Errorf("wrong result %#v; want %#v", got, want)
}
}
func evaluatorForModule(stateSync *states.SyncState, changesSync *plans.ChangesSync) *Evaluator {
return &Evaluator{
Meta: &ContextMeta{
Env: "foo",
},
Config: &configs.Config{
Module: &configs.Module{
ModuleCalls: map[string]*configs.ModuleCall{
"mod": {
Name: "mod",
},
},
},
Children: map[string]*configs.Config{
"mod": {
Path: addrs.Module{"module.mod"},
Module: &configs.Module{
Outputs: map[string]*configs.Output{
"out": {
Name: "out",
Sensitive: true,
},
},
},
},
},
},
State: stateSync,
Changes: changesSync,
}
}