-
Notifications
You must be signed in to change notification settings - Fork 10
/
analyzer_contributing_resources.go
130 lines (114 loc) · 4.48 KB
/
analyzer_contributing_resources.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
package globalref
import (
"sort"
"github.com/snyk/policy-engine/pkg/internal/terraform/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
}
}