-
Notifications
You must be signed in to change notification settings - Fork 1.1k
/
dependency_graph.go
214 lines (189 loc) 路 7.36 KB
/
dependency_graph.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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
// Copyright 2016-2021, Pulumi Corporation. All rights reserved.
package graph
import (
"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)
// DependencyGraph represents a dependency graph encoded within a resource snapshot.
type DependencyGraph struct {
index map[*resource.State]int // A mapping of resource pointers to indexes within the snapshot
resources []*resource.State // The list of resources, obtained from the snapshot
childrenOf map[resource.URN][]int // Pre-computed map of transitive children for each resource
}
// DependingOn returns a slice containing all resources that directly or indirectly
// depend upon the given resource. The returned slice is guaranteed to be in topological
// order with respect to the snapshot dependency graph.
//
// The time complexity of DependingOn is linear with respect to the number of resources.
//
// includeChildren adds children as another type of (transitive) dependency.
func (dg *DependencyGraph) DependingOn(res *resource.State,
ignore map[resource.URN]bool, includeChildren bool) []*resource.State {
// This implementation relies on the detail that snapshots are stored in a valid
// topological order.
var dependents []*resource.State
dependentSet := make(map[resource.URN]bool)
cursorIndex, ok := dg.index[res]
contract.Assert(ok)
dependentSet[res.URN] = true
isDependent := func(candidate *resource.State) bool {
if ignore[candidate.URN] {
return false
}
if includeChildren && dependentSet[candidate.Parent] {
return true
}
for _, dependency := range candidate.Dependencies {
if dependentSet[dependency] {
return true
}
}
if candidate.Provider != "" {
ref, err := providers.ParseReference(candidate.Provider)
contract.Assert(err == nil)
if dependentSet[ref.URN()] {
return true
}
}
return false
}
// The dependency graph encoded directly within the snapshot is the reverse of
// the graph that we actually want to operate upon. Edges in the snapshot graph
// originate in a resource and go to that resource's dependencies.
//
// The `DependingOn` is simpler when operating on the reverse of the snapshot graph,
// where edges originate in a resource and go to resources that depend on that resource.
// In this graph, `DependingOn` for a resource is the set of resources that are reachable from the
// given resource.
//
// To accomplish this without building up an entire graph data structure, we'll do a linear
// scan of the resource list starting at the requested resource and ending at the end of
// the list. All resources that depend directly or indirectly on `res` are prepended
// onto `dependents`.
for i := cursorIndex + 1; i < len(dg.resources); i++ {
candidate := dg.resources[i]
if isDependent(candidate) {
dependents = append(dependents, candidate)
dependentSet[candidate.URN] = true
}
}
return dependents
}
// DependenciesOf returns a ResourceSet of resources upon which the given resource depends. The resource's parent is
// included in the returned set.
func (dg *DependencyGraph) DependenciesOf(res *resource.State) ResourceSet {
set := make(ResourceSet)
dependentUrns := make(map[resource.URN]bool)
for _, dep := range res.Dependencies {
dependentUrns[dep] = true
}
if res.Provider != "" {
ref, err := providers.ParseReference(res.Provider)
contract.Assert(err == nil)
dependentUrns[ref.URN()] = true
}
cursorIndex, ok := dg.index[res]
contract.Assert(ok)
for i := cursorIndex - 1; i >= 0; i-- {
candidate := dg.resources[i]
// Include all resources that are dependencies of the resource
if dependentUrns[candidate.URN] {
set[candidate] = true
// If the dependency is a component, all transitive children of the dependency that are before this
// resource in the topological sort are also implicitly dependencies. This is necessary because for remote
// components, the dependencies will not include the transitive set of children directly, but will include
// the parent component. We must walk that component's children here to ensure they are treated as
// dependencies. Transitive children of the dependency that are after the resource in the topological sort
// are not included as this could lead to cycles in the dependency order.
if !candidate.Custom {
for _, transitiveCandidateIndex := range dg.childrenOf[candidate.URN] {
if transitiveCandidateIndex < cursorIndex {
set[dg.resources[transitiveCandidateIndex]] = true
}
}
}
}
// Include the resource's parent, as the resource depends on it's parent existing.
if candidate.URN == res.Parent {
set[candidate] = true
}
}
return set
}
// `TransitiveDependenciesOf` calculates the set of resources that `r` depends
// on, directly or indirectly. This includes as a `Parent`, a member of r's
// `Dependencies` list or as a provider.
//
// This function is linear in the number of resources in the `DependencyGraph`.
func (dg *DependencyGraph) TransitiveDependenciesOf(r *resource.State) ResourceSet {
dependentProviders := make(map[resource.URN]struct{})
urns := make(map[resource.URN]*node, len(dg.resources))
for _, r := range dg.resources {
urns[r.URN] = &node{resource: r}
}
// Linearity is due to short circuiting in the traversal.
markAsDependency(r.URN, urns, dependentProviders)
// This will only trigger if (urn, node) is a provider. The check is implicit
// in the set lookup.
for urn := range urns {
if _, ok := dependentProviders[urn]; ok {
markAsDependency(urn, urns, dependentProviders)
}
}
dependencies := ResourceSet{}
for _, r := range urns {
if r.marked {
dependencies[r.resource] = true
}
}
// We don't want to include `r` as it's own dependency.
delete(dependencies, r)
return dependencies
}
// Mark a resource and its parents as a dependency. This is a helper function for `TransitiveDependenciesOf`.
func markAsDependency(urn resource.URN, urns map[resource.URN]*node, dependedProviders map[resource.URN]struct{}) {
r := urns[urn]
for {
r.marked = true
if r.resource.Provider != "" {
ref, err := providers.ParseReference(r.resource.Provider)
contract.AssertNoError(err)
dependedProviders[ref.URN()] = struct{}{}
}
for _, dep := range r.resource.Dependencies {
markAsDependency(dep, urns, dependedProviders)
}
// If p is already marked, we don't need to continue to traverse. All
// nodes above p will have already been marked. This is a property of
// `resources` being topologically sorted.
if p, ok := urns[r.resource.Parent]; ok && !p.marked {
r = p
} else {
break
}
}
}
// NewDependencyGraph creates a new DependencyGraph from a list of resources.
// The resources should be in topological order with respect to their dependencies, including
// parents appearing before children.
func NewDependencyGraph(resources []*resource.State) *DependencyGraph {
index := make(map[*resource.State]int)
childrenOf := make(map[resource.URN][]int)
urnIndex := make(map[resource.URN]int)
for idx, res := range resources {
index[res] = idx
urnIndex[res.URN] = idx
parent := res.Parent
for parent != "" {
childrenOf[parent] = append(childrenOf[parent], idx)
parent = resources[urnIndex[parent]].Parent
}
}
return &DependencyGraph{index, resources, childrenOf}
}
// A node in a graph.
type node struct {
marked bool
resource *resource.State
}