Skip to content

Commit f3a1f1a

Browse files
authored
terraform console: enable use of impure functions (#25442)
* command/console: allow use of impure functions in terraform console * add tests for Context Eval
1 parent fdab170 commit f3a1f1a

File tree

4 files changed

+132
-1
lines changed

4 files changed

+132
-1
lines changed

terraform/context_eval_test.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package terraform
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/hcl/v2"
7+
"github.com/hashicorp/hcl/v2/hclsyntax"
8+
"github.com/hashicorp/terraform/addrs"
9+
"github.com/hashicorp/terraform/providers"
10+
"github.com/zclconf/go-cty/cty"
11+
)
12+
13+
func TestContextEval(t *testing.T) {
14+
// This test doesn't check the "Want" value for impure funcs, so the value
15+
// on those doesn't matter.
16+
tests := []struct {
17+
Input string
18+
Want cty.Value
19+
ImpureFunc bool
20+
}{
21+
{ // An impure function: allowed in the console, but the result is nondeterministic
22+
`bcrypt("example")`,
23+
cty.NilVal,
24+
true,
25+
},
26+
{
27+
`keys(var.map)`,
28+
cty.ListVal([]cty.Value{
29+
cty.StringVal("foo"),
30+
cty.StringVal("baz"),
31+
}),
32+
true,
33+
},
34+
{
35+
`local.result`,
36+
cty.NumberIntVal(6),
37+
false,
38+
},
39+
{
40+
`module.child.result`,
41+
cty.UnknownVal(cty.Number),
42+
false,
43+
},
44+
}
45+
46+
// This module has a little bit of everything (and if it is missing somehitng, add to it):
47+
// resources, variables, locals, modules, output
48+
m := testModule(t, "eval-context-basic")
49+
p := testProvider("test")
50+
ctx := testContext2(t, &ContextOpts{
51+
Config: m,
52+
Providers: map[addrs.Provider]providers.Factory{
53+
addrs.NewDefaultProvider("test"): testProviderFuncFixed(p),
54+
},
55+
})
56+
57+
scope, diags := ctx.Eval(addrs.RootModuleInstance)
58+
if diags.HasErrors() {
59+
t.Fatalf("Eval errors: %s", diags.Err())
60+
}
61+
62+
// Since we're testing 'eval' (used by terraform console), impure functions
63+
// should be allowed by the scope.
64+
if scope.PureOnly == true {
65+
t.Fatal("wrong result: eval should allow impure funcs")
66+
}
67+
68+
for _, test := range tests {
69+
t.Run(test.Input, func(t *testing.T) {
70+
// Parse the test input as an expression
71+
expr, _ := hclsyntax.ParseExpression([]byte(test.Input), "<test-input>", hcl.Pos{Line: 1, Column: 1})
72+
got, diags := scope.EvalExpr(expr, cty.DynamicPseudoType)
73+
74+
if diags.HasErrors() {
75+
t.Fatalf("unexpected error: %s", diags.Err())
76+
}
77+
78+
if !test.ImpureFunc {
79+
if !got.RawEquals(test.Want) {
80+
t.Fatalf("wrong result: want %#v, got %#v", test.Want, got)
81+
}
82+
}
83+
})
84+
}
85+
}

terraform/evaluate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ func (e *Evaluator) Scope(data lang.Data, self addrs.Referenceable) *lang.Scope
7171
return &lang.Scope{
7272
Data: data,
7373
SelfAddr: self,
74-
PureOnly: e.Operation != walkApply && e.Operation != walkDestroy,
74+
PureOnly: e.Operation != walkApply && e.Operation != walkDestroy && e.Operation != walkEval,
7575
BaseDir: ".", // Always current working directory for now.
7676
}
7777
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
variable "list" {
2+
}
3+
4+
5+
output "result" {
6+
value = length(var.list)
7+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
variable "number" {
2+
default = 3
3+
}
4+
5+
variable "string" {
6+
default = "Hello, World"
7+
}
8+
9+
variable "map" {
10+
type = map(string)
11+
default = {
12+
"foo" = "bar",
13+
"baz" = "bat",
14+
}
15+
}
16+
17+
locals {
18+
result = length(var.list)
19+
}
20+
21+
variable "list" {
22+
type = list(string)
23+
default = ["red", "orange", "yellow", "green", "blue", "purple"]
24+
}
25+
26+
resource "test_resource" "example" {
27+
for_each = var.map
28+
name = each.key
29+
tag = each.value
30+
}
31+
32+
module "child" {
33+
source = "./child"
34+
list = var.list
35+
}
36+
37+
output "result" {
38+
value = module.child.result
39+
}

0 commit comments

Comments
 (0)