terraform/internal/lang/globalref/analyzer_contributing_resou...

131 lines
4.5 KiB
Go

package globalref
import (
"sort"
"github.com/hashicorp/terraform/internal/addrs"
)
// ContributingResources analyzes all of the given references and
// for each one tries to walk backwards through any named values to find all
// resources whose values contributed either directly or indirectly to any of
// them.
//
// This is a wrapper around ContributingResourceReferences which simplifies
// the result to only include distinct resource addresses, not full references.
// If the configuration includes several different references to different
// parts of a resource, ContributingResources will not preserve that detail.
func (a *Analyzer) ContributingResources(refs ...Reference) []addrs.AbsResource {
retRefs := a.ContributingResourceReferences(refs...)
if len(retRefs) == 0 {
return nil
}
uniq := make(map[string]addrs.AbsResource, len(refs))
for _, ref := range retRefs {
if addr, ok := resourceForAddr(ref.LocalRef.Subject); ok {
moduleAddr := ref.ModuleAddr()
absAddr := addr.Absolute(moduleAddr)
uniq[absAddr.String()] = absAddr
}
}
ret := make([]addrs.AbsResource, 0, len(uniq))
for _, addr := range uniq {
ret = append(ret, addr)
}
sort.Slice(ret, func(i, j int) bool {
// We only have a sorting function for resource _instances_, but
// it'll do well enough if we just pretend we have no-key instances.
return ret[i].Instance(addrs.NoKey).Less(ret[j].Instance(addrs.NoKey))
})
return ret
}
// ContributingResourceReferences analyzes all of the given references and
// for each one tries to walk backwards through any named values to find all
// references to resource attributes that contributed either directly or
// indirectly to any of them.
//
// This is a global operation that can be potentially quite expensive for
// complex configurations.
func (a *Analyzer) ContributingResourceReferences(refs ...Reference) []Reference {
// Our methodology here is to keep digging through MetaReferences
// until we've visited everything we encounter directly or indirectly,
// and keep track of any resources we find along the way.
// We'll aggregate our result here, using the string representations of
// the resources as keys to avoid returning the same one more than once.
found := make(map[referenceAddrKey]Reference)
// We might encounter the same object multiple times as we walk,
// but we won't learn anything more by traversing them again and so we'll
// just skip them instead.
visitedObjects := make(map[referenceAddrKey]struct{})
// A queue of objects we still need to visit.
// Note that if we find multiple references to the same object then we'll
// just arbitrary choose any one of them, because for our purposes here
// it's immaterial which reference we actually followed.
pendingObjects := make(map[referenceAddrKey]Reference)
// Initial state: identify any directly-mentioned resources and
// queue up any named values we refer to.
for _, ref := range refs {
if _, ok := resourceForAddr(ref.LocalRef.Subject); ok {
found[ref.addrKey()] = ref
}
pendingObjects[ref.addrKey()] = ref
}
for len(pendingObjects) > 0 {
// Note: we modify this map while we're iterating over it, which means
// that anything we add might be either visited within a later
// iteration of the inner loop or in a later iteration of the outer
// loop, but we get the correct result either way because we keep
// working until we've fully depleted the queue.
for key, ref := range pendingObjects {
delete(pendingObjects, key)
// We do this _before_ the visit below just in case this is an
// invalid config with a self-referential local value, in which
// case we'll just silently ignore the self reference for our
// purposes here, and thus still eventually converge (albeit
// with an incomplete answer).
visitedObjects[key] = struct{}{}
moreRefs := a.MetaReferences(ref)
for _, newRef := range moreRefs {
if _, ok := resourceForAddr(newRef.LocalRef.Subject); ok {
found[newRef.addrKey()] = newRef
}
newKey := newRef.addrKey()
if _, visited := visitedObjects[newKey]; !visited {
pendingObjects[newKey] = newRef
}
}
}
}
if len(found) == 0 {
return nil
}
ret := make([]Reference, 0, len(found))
for _, ref := range found {
ret = append(ret, ref)
}
return ret
}
func resourceForAddr(addr addrs.Referenceable) (addrs.Resource, bool) {
switch addr := addr.(type) {
case addrs.Resource:
return addr, true
case addrs.ResourceInstance:
return addr.Resource, true
default:
return addrs.Resource{}, false
}
}