github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/lang/globalref/analyzer_contributing_resources.go (about)

     1  package globalref
     2  
     3  import (
     4  	"sort"
     5  
     6  	"github.com/hashicorp/terraform/internal/addrs"
     7  )
     8  
     9  // ContributingResources analyzes all of the given references and
    10  // for each one tries to walk backwards through any named values to find all
    11  // resources whose values contributed either directly or indirectly to any of
    12  // them.
    13  //
    14  // This is a wrapper around ContributingResourceReferences which simplifies
    15  // the result to only include distinct resource addresses, not full references.
    16  // If the configuration includes several different references to different
    17  // parts of a resource, ContributingResources will not preserve that detail.
    18  func (a *Analyzer) ContributingResources(refs ...Reference) []addrs.AbsResource {
    19  	retRefs := a.ContributingResourceReferences(refs...)
    20  	if len(retRefs) == 0 {
    21  		return nil
    22  	}
    23  
    24  	uniq := make(map[string]addrs.AbsResource, len(refs))
    25  	for _, ref := range retRefs {
    26  		if addr, ok := resourceForAddr(ref.LocalRef.Subject); ok {
    27  			moduleAddr := ref.ModuleAddr()
    28  			absAddr := addr.Absolute(moduleAddr)
    29  			uniq[absAddr.String()] = absAddr
    30  		}
    31  	}
    32  	ret := make([]addrs.AbsResource, 0, len(uniq))
    33  	for _, addr := range uniq {
    34  		ret = append(ret, addr)
    35  	}
    36  	sort.Slice(ret, func(i, j int) bool {
    37  		// We only have a sorting function for resource _instances_, but
    38  		// it'll do well enough if we just pretend we have no-key instances.
    39  		return ret[i].Instance(addrs.NoKey).Less(ret[j].Instance(addrs.NoKey))
    40  	})
    41  	return ret
    42  }
    43  
    44  // ContributingResourceReferences analyzes all of the given references and
    45  // for each one tries to walk backwards through any named values to find all
    46  // references to resource attributes that contributed either directly or
    47  // indirectly to any of them.
    48  //
    49  // This is a global operation that can be potentially quite expensive for
    50  // complex configurations.
    51  func (a *Analyzer) ContributingResourceReferences(refs ...Reference) []Reference {
    52  	// Our methodology here is to keep digging through MetaReferences
    53  	// until we've visited everything we encounter directly or indirectly,
    54  	// and keep track of any resources we find along the way.
    55  
    56  	// We'll aggregate our result here, using the string representations of
    57  	// the resources as keys to avoid returning the same one more than once.
    58  	found := make(map[referenceAddrKey]Reference)
    59  
    60  	// We might encounter the same object multiple times as we walk,
    61  	// but we won't learn anything more by traversing them again and so we'll
    62  	// just skip them instead.
    63  	visitedObjects := make(map[referenceAddrKey]struct{})
    64  
    65  	// A queue of objects we still need to visit.
    66  	// Note that if we find multiple references to the same object then we'll
    67  	// just arbitrary choose any one of them, because for our purposes here
    68  	// it's immaterial which reference we actually followed.
    69  	pendingObjects := make(map[referenceAddrKey]Reference)
    70  
    71  	// Initial state: identify any directly-mentioned resources and
    72  	// queue up any named values we refer to.
    73  	for _, ref := range refs {
    74  		if _, ok := resourceForAddr(ref.LocalRef.Subject); ok {
    75  			found[ref.addrKey()] = ref
    76  		}
    77  		pendingObjects[ref.addrKey()] = ref
    78  	}
    79  
    80  	for len(pendingObjects) > 0 {
    81  		// Note: we modify this map while we're iterating over it, which means
    82  		// that anything we add might be either visited within a later
    83  		// iteration of the inner loop or in a later iteration of the outer
    84  		// loop, but we get the correct result either way because we keep
    85  		// working until we've fully depleted the queue.
    86  		for key, ref := range pendingObjects {
    87  			delete(pendingObjects, key)
    88  
    89  			// We do this _before_ the visit below just in case this is an
    90  			// invalid config with a self-referential local value, in which
    91  			// case we'll just silently ignore the self reference for our
    92  			// purposes here, and thus still eventually converge (albeit
    93  			// with an incomplete answer).
    94  			visitedObjects[key] = struct{}{}
    95  
    96  			moreRefs := a.MetaReferences(ref)
    97  			for _, newRef := range moreRefs {
    98  				if _, ok := resourceForAddr(newRef.LocalRef.Subject); ok {
    99  					found[newRef.addrKey()] = newRef
   100  				}
   101  
   102  				newKey := newRef.addrKey()
   103  				if _, visited := visitedObjects[newKey]; !visited {
   104  					pendingObjects[newKey] = newRef
   105  				}
   106  			}
   107  		}
   108  	}
   109  
   110  	if len(found) == 0 {
   111  		return nil
   112  	}
   113  
   114  	ret := make([]Reference, 0, len(found))
   115  	for _, ref := range found {
   116  		ret = append(ret, ref)
   117  	}
   118  	return ret
   119  }
   120  
   121  func resourceForAddr(addr addrs.Referenceable) (addrs.Resource, bool) {
   122  	switch addr := addr.(type) {
   123  	case addrs.Resource:
   124  		return addr, true
   125  	case addrs.ResourceInstance:
   126  		return addr.Resource, true
   127  	default:
   128  		return addrs.Resource{}, false
   129  	}
   130  }