| 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 |
| } |
| } |