diff --git a/terraform/context_eval_test.go b/terraform/context_eval_test.go new file mode 100644 index 000000000..145ef1372 --- /dev/null +++ b/terraform/context_eval_test.go @@ -0,0 +1,85 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/providers" + "github.com/zclconf/go-cty/cty" +) + +func TestContextEval(t *testing.T) { + // This test doesn't check the "Want" value for impure funcs, so the value + // on those doesn't matter. + tests := []struct { + Input string + Want cty.Value + ImpureFunc bool + }{ + { // An impure function: allowed in the console, but the result is nondeterministic + `bcrypt("example")`, + cty.NilVal, + true, + }, + { + `keys(var.map)`, + cty.ListVal([]cty.Value{ + cty.StringVal("foo"), + cty.StringVal("baz"), + }), + true, + }, + { + `local.result`, + cty.NumberIntVal(6), + false, + }, + { + `module.child.result`, + cty.UnknownVal(cty.Number), + false, + }, + } + + // This module has a little bit of everything (and if it is missing somehitng, add to it): + // resources, variables, locals, modules, output + m := testModule(t, "eval-context-basic") + p := testProvider("test") + ctx := testContext2(t, &ContextOpts{ + Config: m, + Providers: map[addrs.Provider]providers.Factory{ + addrs.NewDefaultProvider("test"): testProviderFuncFixed(p), + }, + }) + + scope, diags := ctx.Eval(addrs.RootModuleInstance) + if diags.HasErrors() { + t.Fatalf("Eval errors: %s", diags.Err()) + } + + // Since we're testing 'eval' (used by terraform console), impure functions + // should be allowed by the scope. + if scope.PureOnly == true { + t.Fatal("wrong result: eval should allow impure funcs") + } + + for _, test := range tests { + t.Run(test.Input, func(t *testing.T) { + // Parse the test input as an expression + expr, _ := hclsyntax.ParseExpression([]byte(test.Input), "", hcl.Pos{Line: 1, Column: 1}) + got, diags := scope.EvalExpr(expr, cty.DynamicPseudoType) + + if diags.HasErrors() { + t.Fatalf("unexpected error: %s", diags.Err()) + } + + if !test.ImpureFunc { + if !got.RawEquals(test.Want) { + t.Fatalf("wrong result: want %#v, got %#v", test.Want, got) + } + } + }) + } +} diff --git a/terraform/evaluate.go b/terraform/evaluate.go index fad95b472..730263a09 100644 --- a/terraform/evaluate.go +++ b/terraform/evaluate.go @@ -71,7 +71,7 @@ func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope return &lang.Scope{ Data: data, SelfAddr: self, - PureOnly: e.Operation != walkApply && e.Operation != walkDestroy, + PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval, BaseDir: ".", // Always current working directory for now. } } diff --git a/terraform/testdata/eval-context-basic/child/main.tf b/terraform/testdata/eval-context-basic/child/main.tf new file mode 100644 index 000000000..e24069df7 --- /dev/null +++ b/terraform/testdata/eval-context-basic/child/main.tf @@ -0,0 +1,7 @@ +variable "list" { +} + + +output "result" { + value = length(var.list) +} diff --git a/terraform/testdata/eval-context-basic/main.tf b/terraform/testdata/eval-context-basic/main.tf new file mode 100644 index 000000000..2dc96ad86 --- /dev/null +++ b/terraform/testdata/eval-context-basic/main.tf @@ -0,0 +1,39 @@ +variable "number" { + default = 3 +} + +variable "string" { + default = "Hello, World" +} + +variable "map" { + type = map(string) + default = { + "foo" = "bar", + "baz" = "bat", + } +} + +locals { + result = length(var.list) +} + +variable "list" { + type = list(string) + default = ["red", "orange", "yellow", "green", "blue", "purple"] +} + +resource "test_resource" "example" { + for_each = var.map + name = each.key + tag = each.value +} + +module "child" { + source = "./child" + list = var.list +} + +output "result" { + value = module.child.result +}