Skip to content

Commit 95df102

Browse files
mildwonkeyalisdair
authored andcommitted
jsonconfig: properly unwind and enumerate references (#28884)
The "references" included in the expression representation now properly unwrap for each traversal step, to match what was documented.
1 parent 80bd093 commit 95df102

File tree

5 files changed

+131
-25
lines changed

5 files changed

+131
-25
lines changed

internal/command/jsonconfig/expression.go

Lines changed: 70 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package jsonconfig
22

33
import (
4+
"bytes"
45
"encoding/json"
6+
"fmt"
57

68
"github.com/hashicorp/hcl/v2"
79
"github.com/hashicorp/hcl/v2/hcldec"
10+
"github.com/hashicorp/terraform/internal/addrs"
811
"github.com/hashicorp/terraform/internal/configs/configschema"
912
"github.com/hashicorp/terraform/internal/lang"
1013
"github.com/zclconf/go-cty/cty"
@@ -30,22 +33,49 @@ type expression struct {
3033

3134
func marshalExpression(ex hcl.Expression) expression {
3235
var ret expression
33-
if ex != nil {
34-
val, _ := ex.Value(nil)
35-
if val != cty.NilVal {
36-
valJSON, _ := ctyjson.Marshal(val, val.Type())
37-
ret.ConstantValue = valJSON
38-
}
39-
vars, _ := lang.ReferencesInExpr(ex)
36+
if ex == nil {
37+
return ret
38+
}
39+
40+
val, _ := ex.Value(nil)
41+
if val != cty.NilVal {
42+
valJSON, _ := ctyjson.Marshal(val, val.Type())
43+
ret.ConstantValue = valJSON
44+
}
45+
46+
refs, _ := lang.ReferencesInExpr(ex)
47+
if len(refs) > 0 {
4048
var varString []string
41-
if len(vars) > 0 {
42-
for _, v := range vars {
43-
varString = append(varString, v.Subject.String())
49+
for _, ref := range refs {
50+
// We work backwards here, starting with the full reference +
51+
// reamining traversal, and then unwrapping the remaining traversals
52+
// into parts until we end up at the smallest referencable address.
53+
remains := ref.Remaining
54+
for len(remains) > 0 {
55+
varString = append(varString, fmt.Sprintf("%s%s", ref.Subject, traversalStr(remains)))
56+
remains = remains[:(len(remains) - 1)]
57+
}
58+
varString = append(varString, ref.Subject.String())
59+
60+
switch ref.Subject.(type) {
61+
case addrs.ModuleCallInstance:
62+
if ref.Subject.(addrs.ModuleCallInstance).Key != addrs.NoKey {
63+
// Include the module call, without the key
64+
varString = append(varString, ref.Subject.(addrs.ModuleCallInstance).Call.String())
65+
}
66+
case addrs.ResourceInstance:
67+
if ref.Subject.(addrs.ResourceInstance).Key != addrs.NoKey {
68+
// Include the resource, without the key
69+
varString = append(varString, ref.Subject.(addrs.ResourceInstance).Resource.String())
70+
}
71+
case addrs.AbsModuleCallOutput:
72+
// Include the module name, without the output name
73+
varString = append(varString, ref.Subject.(addrs.AbsModuleCallOutput).Call.String())
4474
}
45-
ret.References = varString
4675
}
47-
return ret
76+
ret.References = varString
4877
}
78+
4979
return ret
5080
}
5181

@@ -117,3 +147,31 @@ func marshalExpressions(body hcl.Body, schema *configschema.Block) expressions {
117147

118148
return ret
119149
}
150+
151+
// traversalStr produces a representation of an HCL traversal that is compact,
152+
// resembles HCL native syntax, and is suitable for display in the UI.
153+
//
154+
// This was copied (and simplified) from internal/command/views/json/diagnostic.go.
155+
func traversalStr(traversal hcl.Traversal) string {
156+
var buf bytes.Buffer
157+
for _, step := range traversal {
158+
switch tStep := step.(type) {
159+
case hcl.TraverseRoot:
160+
buf.WriteString(tStep.Name)
161+
case hcl.TraverseAttr:
162+
buf.WriteByte('.')
163+
buf.WriteString(tStep.Name)
164+
case hcl.TraverseIndex:
165+
buf.WriteByte('[')
166+
switch tStep.Key.Type() {
167+
case cty.String:
168+
buf.WriteString(fmt.Sprintf("%q", tStep.Key.AsString()))
169+
case cty.Number:
170+
bf := tStep.Key.AsBigFloat()
171+
buf.WriteString(bf.Text('g', 10))
172+
}
173+
buf.WriteByte(']')
174+
}
175+
}
176+
return buf.String()
177+
}

internal/command/jsonconfig/expression_test.go

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import (
99

1010
"github.com/hashicorp/hcl/v2"
1111
"github.com/hashicorp/hcl/v2/hclsyntax"
12+
"github.com/hashicorp/hcl/v2/hcltest"
1213
"github.com/hashicorp/terraform/internal/configs/configschema"
1314
)
1415

1516
func TestMarshalExpressions(t *testing.T) {
1617
tests := []struct {
17-
Input hcl.Body
18-
Schema *configschema.Block
19-
Want expressions
18+
Input hcl.Body
19+
Want expressions
2020
}{
2121
{
2222
&hclsyntax.Body{
@@ -28,27 +28,73 @@ func TestMarshalExpressions(t *testing.T) {
2828
},
2929
},
3030
},
31-
&configschema.Block{
32-
Attributes: map[string]*configschema.Attribute{
31+
expressions{
32+
"foo": expression{
33+
ConstantValue: json.RawMessage([]byte(`"bar"`)),
34+
References: []string(nil),
35+
},
36+
},
37+
},
38+
{
39+
hcltest.MockBody(&hcl.BodyContent{
40+
Attributes: hcl.Attributes{
41+
"foo": {
42+
Name: "foo",
43+
Expr: hcltest.MockExprTraversalSrc(`var.list[1]`),
44+
},
45+
},
46+
}),
47+
expressions{
48+
"foo": expression{
49+
References: []string{"var.list[1]", "var.list"},
50+
},
51+
},
52+
},
53+
{
54+
hcltest.MockBody(&hcl.BodyContent{
55+
Attributes: hcl.Attributes{
3356
"foo": {
34-
Type: cty.String,
35-
Optional: true,
57+
Name: "foo",
58+
Expr: hcltest.MockExprTraversalSrc(`data.template_file.foo[1].vars["baz"]`),
3659
},
3760
},
61+
}),
62+
expressions{
63+
"foo": expression{
64+
References: []string{"data.template_file.foo[1].vars[\"baz\"]", "data.template_file.foo[1].vars", "data.template_file.foo[1]", "data.template_file.foo"},
65+
},
3866
},
67+
},
68+
{
69+
hcltest.MockBody(&hcl.BodyContent{
70+
Attributes: hcl.Attributes{
71+
"foo": {
72+
Name: "foo",
73+
Expr: hcltest.MockExprTraversalSrc(`module.foo.bar`),
74+
},
75+
},
76+
}),
3977
expressions{
4078
"foo": expression{
41-
ConstantValue: json.RawMessage([]byte(`"bar"`)),
42-
References: []string(nil),
79+
References: []string{"module.foo.bar", "module.foo"},
4380
},
4481
},
4582
},
4683
}
4784

4885
for _, test := range tests {
49-
got := marshalExpressions(test.Input, test.Schema)
86+
schema := &configschema.Block{
87+
Attributes: map[string]*configschema.Attribute{
88+
"foo": {
89+
Type: cty.String,
90+
Optional: true,
91+
},
92+
},
93+
}
94+
95+
got := marshalExpressions(test.Input, schema)
5096
if !reflect.DeepEqual(got, test.Want) {
51-
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
97+
t.Errorf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
5298
}
5399
}
54100
}

internal/command/show_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ func TestShow_json_output(t *testing.T) {
337337
expectError := strings.Contains(entry.Name(), "error")
338338

339339
providerSource, close := newMockProviderSource(t, map[string][]string{
340-
"test": []string{"1.2.3"},
340+
"test": {"1.2.3"},
341341
})
342342
defer close()
343343

internal/command/testdata/show-json/modules/output.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@
196196
"test": {
197197
"expression": {
198198
"references": [
199-
"module.module_test_foo.test"
199+
"module.module_test_foo.test",
200+
"module.module_test_foo"
200201
]
201202
},
202203
"depends_on": [

internal/command/testdata/show-json/sensitive-values/output.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"test": {
8787
"expression": {
8888
"references": [
89+
"test_instance.test.ami",
8990
"test_instance.test"
9091
]
9192
},

0 commit comments

Comments
 (0)