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
|
package jsonconfig
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hcldec"
|
"github.com/hashicorp/hcl/v2/hcldec"
|
||||||
|
"github.com/hashicorp/terraform/internal/addrs"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
"github.com/hashicorp/terraform/internal/lang"
|
"github.com/hashicorp/terraform/internal/lang"
|
||||||
"github.com/zclconf/go-cty/cty"
|
"github.com/zclconf/go-cty/cty"
|
||||||
|
@ -30,22 +33,49 @@ type expression struct {
|
||||||
|
|
||||||
func marshalExpression(ex hcl.Expression) expression {
|
func marshalExpression(ex hcl.Expression) expression {
|
||||||
var ret expression
|
var ret expression
|
||||||
if ex != nil {
|
if ex == nil {
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
val, _ := ex.Value(nil)
|
val, _ := ex.Value(nil)
|
||||||
if val != cty.NilVal {
|
if val != cty.NilVal {
|
||||||
valJSON, _ := ctyjson.Marshal(val, val.Type())
|
valJSON, _ := ctyjson.Marshal(val, val.Type())
|
||||||
ret.ConstantValue = valJSON
|
ret.ConstantValue = valJSON
|
||||||
}
|
}
|
||||||
vars, _ := lang.ReferencesInExpr(ex)
|
|
||||||
|
refs, _ := lang.ReferencesInExpr(ex)
|
||||||
|
if len(refs) > 0 {
|
||||||
var varString []string
|
var varString []string
|
||||||
if len(vars) > 0 {
|
for _, ref := range refs {
|
||||||
for _, v := range vars {
|
// We work backwards here, starting with the full reference +
|
||||||
varString = append(varString, v.Subject.String())
|
// 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
|
ret.References = varString
|
||||||
}
|
}
|
||||||
return ret
|
|
||||||
}
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,3 +147,31 @@ func marshalExpressions(body hcl.Body, schema *configschema.Block) expressions {
|
||||||
|
|
||||||
return ret
|
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,13 +9,13 @@ import (
|
||||||
|
|
||||||
"github.com/hashicorp/hcl/v2"
|
"github.com/hashicorp/hcl/v2"
|
||||||
"github.com/hashicorp/hcl/v2/hclsyntax"
|
"github.com/hashicorp/hcl/v2/hclsyntax"
|
||||||
|
"github.com/hashicorp/hcl/v2/hcltest"
|
||||||
"github.com/hashicorp/terraform/internal/configs/configschema"
|
"github.com/hashicorp/terraform/internal/configs/configschema"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalExpressions(t *testing.T) {
|
func TestMarshalExpressions(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
Input hcl.Body
|
Input hcl.Body
|
||||||
Schema *configschema.Block
|
|
||||||
Want expressions
|
Want expressions
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
@ -28,14 +28,6 @@ func TestMarshalExpressions(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
&configschema.Block{
|
|
||||||
Attributes: map[string]*configschema.Attribute{
|
|
||||||
"foo": {
|
|
||||||
Type: cty.String,
|
|
||||||
Optional: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
expressions{
|
expressions{
|
||||||
"foo": expression{
|
"foo": expression{
|
||||||
ConstantValue: json.RawMessage([]byte(`"bar"`)),
|
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 {
|
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) {
|
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")
|
expectError := strings.Contains(entry.Name(), "error")
|
||||||
|
|
||||||
providerSource, close := newMockProviderSource(t, map[string][]string{
|
providerSource, close := newMockProviderSource(t, map[string][]string{
|
||||||
"test": []string{"1.2.3"},
|
"test": {"1.2.3"},
|
||||||
})
|
})
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
|
|
|
@ -196,7 +196,8 @@
|
||||||
"test": {
|
"test": {
|
||||||
"expression": {
|
"expression": {
|
||||||
"references": [
|
"references": [
|
||||||
"module.module_test_foo.test"
|
"module.module_test_foo.test",
|
||||||
|
"module.module_test_foo"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"depends_on": [
|
"depends_on": [
|
||||||
|
|
|
@ -86,6 +86,7 @@
|
||||||
"test": {
|
"test": {
|
||||||
"expression": {
|
"expression": {
|
||||||
"references": [
|
"references": [
|
||||||
|
"test_instance.test.ami",
|
||||||
"test_instance.test"
|
"test_instance.test"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue