203 lines
6.9 KiB
Go
203 lines
6.9 KiB
Go
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
|
|
// instance or resource instance where it was found.
|
|
//
|
|
// Because of the design of the Terraform language, our main model of
|
|
// references only captures the module-local part of the reference and assumes
|
|
// that it's always clear from context which module a reference belongs to.
|
|
// That's not true for globalref because our whole purpose is to work across
|
|
// module boundaries, and so this package in particular has its own
|
|
// representation of references.
|
|
type Reference struct {
|
|
// ContainerAddr is always either addrs.ModuleInstance or
|
|
// addrs.AbsResourceInstance. The latter is required if LocalRef's
|
|
// subject is either an addrs.CountAddr or addrs.ForEachAddr, so
|
|
// we can know which resource's repetition expression it's
|
|
// referring to.
|
|
ContainerAddr addrs.Targetable
|
|
|
|
// LocalRef is a reference that would be resolved in the context
|
|
// of the module instance or resource instance given in ContainerAddr.
|
|
LocalRef *addrs.Reference
|
|
}
|
|
|
|
func absoluteRef(containerAddr addrs.Targetable, localRef *addrs.Reference) Reference {
|
|
ret := Reference{
|
|
ContainerAddr: containerAddr,
|
|
LocalRef: localRef,
|
|
}
|
|
// For simplicity's sake, we always reduce the ContainerAddr to be
|
|
// just the module address unless it's a count.index, each.key, or
|
|
// each.value reference, because for anything else it's immaterial
|
|
// which resource it belongs to.
|
|
switch localRef.Subject.(type) {
|
|
case addrs.CountAttr, addrs.ForEachAttr:
|
|
// nothing to do
|
|
default:
|
|
ret.ContainerAddr = ret.ModuleAddr()
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func absoluteRefs(containerAddr addrs.Targetable, refs []*addrs.Reference) []Reference {
|
|
if len(refs) == 0 {
|
|
return nil
|
|
}
|
|
|
|
ret := make([]Reference, len(refs))
|
|
for i, ref := range refs {
|
|
ret[i] = absoluteRef(containerAddr, ref)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// ModuleAddr returns the address of the module where the reference would
|
|
// be resolved.
|
|
//
|
|
// This is either ContainerAddr directly if it's already just a module
|
|
// instance, or the module instance part of it if it's a resource instance.
|
|
func (r Reference) ModuleAddr() addrs.ModuleInstance {
|
|
switch addr := r.ContainerAddr.(type) {
|
|
case addrs.ModuleInstance:
|
|
return addr
|
|
case addrs.AbsResourceInstance:
|
|
return addr.Module
|
|
default:
|
|
// NOTE: We're intentionally using only a subset of possible
|
|
// addrs.Targetable implementations here, so anything else
|
|
// is invalid.
|
|
panic(fmt.Sprintf("reference has invalid container address type %T", addr))
|
|
}
|
|
}
|
|
|
|
// ResourceAddr returns the address of the resource where the reference
|
|
// would be resolved, if there is one.
|
|
//
|
|
// Because not all references belong to resources, the extra boolean return
|
|
// value indicates whether the returned address is valid.
|
|
func (r Reference) ResourceAddr() (addrs.AbsResource, bool) {
|
|
moduleInstance := addrs.RootModuleInstance
|
|
|
|
switch container := r.ContainerAddr.(type) {
|
|
case addrs.ModuleInstance:
|
|
moduleInstance = container
|
|
|
|
switch ref := r.LocalRef.Subject.(type) {
|
|
case addrs.Resource:
|
|
return ref.Absolute(moduleInstance), true
|
|
case addrs.ResourceInstance:
|
|
return ref.ContainingResource().Absolute(moduleInstance), true
|
|
}
|
|
|
|
return addrs.AbsResource{}, false
|
|
|
|
case addrs.AbsResourceInstance:
|
|
return container.ContainingResource(), true
|
|
default:
|
|
// NOTE: We're intentionally using only a subset of possible
|
|
// addrs.Targetable implementations here, so anything else
|
|
// is invalid.
|
|
panic(fmt.Sprintf("reference has invalid container address type %T", container))
|
|
}
|
|
}
|
|
|
|
// DebugString returns an internal (but still somewhat Terraform-language-like)
|
|
// compact string representation of the reciever, which isn't an address that
|
|
// any of our usual address parsers could accept but still captures the
|
|
// essence of what the reference represents.
|
|
//
|
|
// The DebugString result is not suitable for end-user-oriented messages.
|
|
//
|
|
// DebugString is also not suitable for use as a unique key for a reference,
|
|
// because it's ambiguous (between a no-key resource instance and a resource)
|
|
// and because it discards the source location information in the LocalRef.
|
|
func (r Reference) DebugString() string {
|
|
// As the doc comment insinuates, we don't have any real syntax for
|
|
// "absolute references": references are always local, and targets are
|
|
// always absolute but only include modules and resources.
|
|
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.
|
|
//
|
|
// See the referenceAddrKey doc comment for more information on what this
|
|
// is suitable for.
|
|
func (r Reference) addrKey() referenceAddrKey {
|
|
// This is a pretty arbitrary bunch of stuff. We include the type here
|
|
// just to differentiate between no-key resource instances and resources.
|
|
return referenceAddrKey(fmt.Sprintf("%s(%T)%s", r.ContainerAddr.String(), r.LocalRef.Subject, r.LocalRef.DisplayString()))
|
|
}
|
|
|
|
// referenceAddrKey is a special string type which conventionally contains
|
|
// a unique string representation of the object that a reference refers to,
|
|
// although not of the reference itself because it ignores the information
|
|
// that would differentiate two different references to the same object.
|
|
//
|
|
// The actual content of a referenceAddrKey is arbitrary, for internal use
|
|
// only. and subject to change in future. We use a named type here only to
|
|
// 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)
|
|
}
|