globalref.Reference.ResourceAttr

Convert a global reference to a specific AbsResource and attribute pair.
The hcl.Traversal is converted to a cty.Path at this point because plan
rendering is based on cty values.
This commit is contained in:
James Bardin 2022-02-04 12:58:07 -05:00
parent dc393cc6e0
commit 620caa983c
2 changed files with 148 additions and 0 deletions

View File

@ -1,6 +1,7 @@
package globalref
import (
"sort"
"testing"
"github.com/google/go-cmp/cmp"
@ -94,3 +95,96 @@ func TestAnalyzerContributingResources(t *testing.T) {
})
}
}
func TestAnalyzerContributingResourceAttrs(t *testing.T) {
azr := testAnalyzer(t, "contributing-resources")
tests := map[string]struct {
StartRefs func() []Reference
WantAttrs []string
}{
"root output 'network'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "network"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"root output 'c10s_url'": {
func() []Reference {
return azr.ReferencesFromOutputValue(
addrs.OutputValue{Name: "c10s_url"}.Absolute(addrs.RootModuleInstance),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.compute.test_thing.load_balancer.string`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"module.compute.test_thing.load_balancer": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "test_thing",
Name: "load_balancer",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance.Child("compute", addrs.NoKey)),
)
},
[]string{
`data.test_thing.environment.any.base_cidr_block`,
`data.test_thing.environment.any.subnet_count`,
`module.compute.test_thing.controller`,
`module.network.test_thing.subnet`,
`module.network.test_thing.vpc.string`,
},
},
"data.test_thing.environment": {
func() []Reference {
return azr.ReferencesFromResourceInstance(
addrs.Resource{
Mode: addrs.DataResourceMode,
Type: "test_thing",
Name: "environment",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
)
},
[]string{
// Nothing! This one only refers to an input variable.
},
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
startRefs := test.StartRefs()
refs := azr.ContributingResourceReferences(startRefs...)
want := test.WantAttrs
got := make([]string, len(refs))
for i, ref := range refs {
resAttr, ok := ref.ResourceAttr()
if !ok {
t.Errorf("%s is not a resource attr reference", resAttr.DebugString())
continue
}
got[i] = resAttr.DebugString()
}
sort.Strings(got)
if diff := cmp.Diff(want, got); diff != "" {
t.Errorf("wrong addresses\n%s", diff)
}
})
}
}

View File

@ -3,7 +3,10 @@ package globalref
import (
"fmt"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// Reference combines an addrs.Reference with the address of the module
@ -125,6 +128,45 @@ func (r Reference) DebugString() string {
return r.ContainerAddr.String() + "::" + r.LocalRef.DisplayString()
}
// ResourceAttr converts the Reference value to a more specific ResourceAttr
// value.
//
// Because not all references belong to resources, the extra boolean return
// value indicates whether the returned address is valid.
func (r Reference) ResourceAttr() (ResourceAttr, bool) {
res, ok := r.ResourceAddr()
if !ok {
return ResourceAttr{}, ok
}
traversal := r.LocalRef.Remaining
path := make(cty.Path, len(traversal))
for si, step := range traversal {
switch ts := step.(type) {
case hcl.TraverseRoot:
path[si] = cty.GetAttrStep{
Name: ts.Name,
}
case hcl.TraverseAttr:
path[si] = cty.GetAttrStep{
Name: ts.Name,
}
case hcl.TraverseIndex:
path[si] = cty.IndexStep{
Key: ts.Key,
}
default:
panic(fmt.Sprintf("unsupported traversal step %#v", step))
}
}
return ResourceAttr{
Resource: res,
Attr: path,
}, true
}
// addrKey returns the referenceAddrKey value for the item that
// this reference refers to, discarding any source location information.
//
@ -146,3 +188,15 @@ func (r Reference) addrKey() referenceAddrKey {
// make it easier to see when we're intentionally using strings to uniquely
// identify absolute reference addresses.
type referenceAddrKey string
// ResourceAttr represents a global resource and attribute reference.
// This is a more specific form of the Reference type since it can only refer
// to a specific AbsResource and one of its attributes.
type ResourceAttr struct {
Resource addrs.AbsResource
Attr cty.Path
}
func (r ResourceAttr) DebugString() string {
return r.Resource.String() + tfdiags.FormatCtyPath(r.Attr)
}