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