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