terraform console: enable use of impure functions (#25442)

* command/console: allow use of impure functions in terraform console
* add tests for Context Eval
This commit is contained in:
Kristin Laemmert 2020-07-01 09:43:07 -04:00 committed by GitHub
parent fdab1704cc
commit f3a1f1a263
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 132 additions and 1 deletions

View File

@ -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), "<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)
}
}
})
}
}

View File

@ -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.
}
}

View File

@ -0,0 +1,7 @@
variable "list" {
}
output "result" {
value = length(var.list)
}

View File

@ -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
}