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.
This commit is contained in:
parent
ac03d35997
commit
329585d07d
|
@ -1,10 +1,13 @@
|
|||
package jsonconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hcldec"
|
||||
"github.com/hashicorp/terraform/internal/addrs"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
"github.com/hashicorp/terraform/internal/lang"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
|
@ -30,22 +33,49 @@ type expression struct {
|
|||
|
||||
func marshalExpression(ex hcl.Expression) expression {
|
||||
var ret expression
|
||||
if ex != nil {
|
||||
val, _ := ex.Value(nil)
|
||||
if val != cty.NilVal {
|
||||
valJSON, _ := ctyjson.Marshal(val, val.Type())
|
||||
ret.ConstantValue = valJSON
|
||||
}
|
||||
vars, _ := lang.ReferencesInExpr(ex)
|
||||
var varString []string
|
||||
if len(vars) > 0 {
|
||||
for _, v := range vars {
|
||||
varString = append(varString, v.Subject.String())
|
||||
}
|
||||
ret.References = varString
|
||||
}
|
||||
if ex == nil {
|
||||
return ret
|
||||
}
|
||||
|
||||
val, _ := ex.Value(nil)
|
||||
if val != cty.NilVal {
|
||||
valJSON, _ := ctyjson.Marshal(val, val.Type())
|
||||
ret.ConstantValue = valJSON
|
||||
}
|
||||
|
||||
refs, _ := lang.ReferencesInExpr(ex)
|
||||
if len(refs) > 0 {
|
||||
var varString []string
|
||||
for _, ref := range refs {
|
||||
// We work backwards here, starting with the full reference +
|
||||
// reamining traversal, and then unwrapping the remaining traversals
|
||||
// into parts until we end up at the smallest referencable address.
|
||||
remains := ref.Remaining
|
||||
for len(remains) > 0 {
|
||||
varString = append(varString, fmt.Sprintf("%s%s", ref.Subject, traversalStr(remains)))
|
||||
remains = remains[:(len(remains) - 1)]
|
||||
}
|
||||
varString = append(varString, ref.Subject.String())
|
||||
|
||||
switch ref.Subject.(type) {
|
||||
case addrs.ModuleCallInstance:
|
||||
if ref.Subject.(addrs.ModuleCallInstance).Key != addrs.NoKey {
|
||||
// Include the module call, without the key
|
||||
varString = append(varString, ref.Subject.(addrs.ModuleCallInstance).Call.String())
|
||||
}
|
||||
case addrs.ResourceInstance:
|
||||
if ref.Subject.(addrs.ResourceInstance).Key != addrs.NoKey {
|
||||
// Include the resource, without the key
|
||||
varString = append(varString, ref.Subject.(addrs.ResourceInstance).Resource.String())
|
||||
}
|
||||
case addrs.AbsModuleCallOutput:
|
||||
// Include the module name, without the output name
|
||||
varString = append(varString, ref.Subject.(addrs.AbsModuleCallOutput).Call.String())
|
||||
}
|
||||
}
|
||||
ret.References = varString
|
||||
}
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
|
@ -117,3 +147,31 @@ func marshalExpressions(body hcl.Body, schema *configschema.Block) expressions {
|
|||
|
||||
return ret
|
||||
}
|
||||
|
||||
// traversalStr produces a representation of an HCL traversal that is compact,
|
||||
// resembles HCL native syntax, and is suitable for display in the UI.
|
||||
//
|
||||
// This was copied (and simplified) from internal/command/views/json/diagnostic.go.
|
||||
func traversalStr(traversal hcl.Traversal) string {
|
||||
var buf bytes.Buffer
|
||||
for _, step := range traversal {
|
||||
switch tStep := step.(type) {
|
||||
case hcl.TraverseRoot:
|
||||
buf.WriteString(tStep.Name)
|
||||
case hcl.TraverseAttr:
|
||||
buf.WriteByte('.')
|
||||
buf.WriteString(tStep.Name)
|
||||
case hcl.TraverseIndex:
|
||||
buf.WriteByte('[')
|
||||
switch tStep.Key.Type() {
|
||||
case cty.String:
|
||||
buf.WriteString(fmt.Sprintf("%q", tStep.Key.AsString()))
|
||||
case cty.Number:
|
||||
bf := tStep.Key.AsBigFloat()
|
||||
buf.WriteString(bf.Text('g', 10))
|
||||
}
|
||||
buf.WriteByte(']')
|
||||
}
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
@ -9,14 +9,14 @@ import (
|
|||
|
||||
"github.com/hashicorp/hcl/v2"
|
||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||
"github.com/hashicorp/hcl/v2/hcltest"
|
||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||
)
|
||||
|
||||
func TestMarshalExpressions(t *testing.T) {
|
||||
tests := []struct {
|
||||
Input hcl.Body
|
||||
Schema *configschema.Block
|
||||
Want expressions
|
||||
Input hcl.Body
|
||||
Want expressions
|
||||
}{
|
||||
{
|
||||
&hclsyntax.Body{
|
||||
|
@ -28,14 +28,6 @@ func TestMarshalExpressions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
&configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
expressions{
|
||||
"foo": expression{
|
||||
ConstantValue: json.RawMessage([]byte(`"bar"`)),
|
||||
|
@ -43,12 +35,66 @@ func TestMarshalExpressions(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Expr: hcltest.MockExprTraversalSrc(`var.list[1]`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
expressions{
|
||||
"foo": expression{
|
||||
References: []string{"var.list[1]", "var.list"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Expr: hcltest.MockExprTraversalSrc(`data.template_file.foo[1].vars["baz"]`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
expressions{
|
||||
"foo": expression{
|
||||
References: []string{"data.template_file.foo[1].vars[\"baz\"]", "data.template_file.foo[1].vars", "data.template_file.foo[1]", "data.template_file.foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
hcltest.MockBody(&hcl.BodyContent{
|
||||
Attributes: hcl.Attributes{
|
||||
"foo": {
|
||||
Name: "foo",
|
||||
Expr: hcltest.MockExprTraversalSrc(`module.foo.bar`),
|
||||
},
|
||||
},
|
||||
}),
|
||||
expressions{
|
||||
"foo": expression{
|
||||
References: []string{"module.foo.bar", "module.foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
got := marshalExpressions(test.Input, test.Schema)
|
||||
schema := &configschema.Block{
|
||||
Attributes: map[string]*configschema.Attribute{
|
||||
"foo": {
|
||||
Type: cty.String,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
got := marshalExpressions(test.Input, schema)
|
||||
if !reflect.DeepEqual(got, test.Want) {
|
||||
t.Fatalf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
|
||||
t.Errorf("wrong result:\nGot: %#v\nWant: %#v\n", got, test.Want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -334,7 +334,7 @@ func TestShow_json_output(t *testing.T) {
|
|||
expectError := strings.Contains(entry.Name(), "error")
|
||||
|
||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||
"test": []string{"1.2.3"},
|
||||
"test": {"1.2.3"},
|
||||
})
|
||||
defer close()
|
||||
|
||||
|
|
|
@ -196,7 +196,8 @@
|
|||
"test": {
|
||||
"expression": {
|
||||
"references": [
|
||||
"module.module_test_foo.test"
|
||||
"module.module_test_foo.test",
|
||||
"module.module_test_foo"
|
||||
]
|
||||
},
|
||||
"depends_on": [
|
||||
|
|
|
@ -86,6 +86,7 @@
|
|||
"test": {
|
||||
"expression": {
|
||||
"references": [
|
||||
"test_instance.test.ami",
|
||||
"test_instance.test"
|
||||
]
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue