131 lines
4.5 KiB
Go
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
|
||
|
}
|
||
|
}
|