github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/graph/dependency_graph.go (about) 1 // Copyright 2016-2021, Pulumi Corporation. All rights reserved. 2 3 package graph 4 5 import ( 6 "github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers" 7 "github.com/pulumi/pulumi/sdk/v3/go/common/resource" 8 "github.com/pulumi/pulumi/sdk/v3/go/common/util/contract" 9 ) 10 11 // DependencyGraph represents a dependency graph encoded within a resource snapshot. 12 type DependencyGraph struct { 13 index map[*resource.State]int // A mapping of resource pointers to indexes within the snapshot 14 resources []*resource.State // The list of resources, obtained from the snapshot 15 childrenOf map[resource.URN][]int // Pre-computed map of transitive children for each resource 16 } 17 18 // DependingOn returns a slice containing all resources that directly or indirectly 19 // depend upon the given resource. The returned slice is guaranteed to be in topological 20 // order with respect to the snapshot dependency graph. 21 // 22 // The time complexity of DependingOn is linear with respect to the number of resources. 23 // 24 // includeChildren adds children as another type of (transitive) dependency. 25 func (dg *DependencyGraph) DependingOn(res *resource.State, 26 ignore map[resource.URN]bool, includeChildren bool) []*resource.State { 27 // This implementation relies on the detail that snapshots are stored in a valid 28 // topological order. 29 var dependents []*resource.State 30 dependentSet := make(map[resource.URN]bool) 31 32 cursorIndex, ok := dg.index[res] 33 contract.Assert(ok) 34 dependentSet[res.URN] = true 35 36 isDependent := func(candidate *resource.State) bool { 37 if ignore[candidate.URN] { 38 return false 39 } 40 if includeChildren && dependentSet[candidate.Parent] { 41 return true 42 } 43 for _, dependency := range candidate.Dependencies { 44 if dependentSet[dependency] { 45 return true 46 } 47 } 48 if candidate.Provider != "" { 49 ref, err := providers.ParseReference(candidate.Provider) 50 contract.Assert(err == nil) 51 if dependentSet[ref.URN()] { 52 return true 53 } 54 } 55 return false 56 } 57 58 // The dependency graph encoded directly within the snapshot is the reverse of 59 // the graph that we actually want to operate upon. Edges in the snapshot graph 60 // originate in a resource and go to that resource's dependencies. 61 // 62 // The `DependingOn` is simpler when operating on the reverse of the snapshot graph, 63 // where edges originate in a resource and go to resources that depend on that resource. 64 // In this graph, `DependingOn` for a resource is the set of resources that are reachable from the 65 // given resource. 66 // 67 // To accomplish this without building up an entire graph data structure, we'll do a linear 68 // scan of the resource list starting at the requested resource and ending at the end of 69 // the list. All resources that depend directly or indirectly on `res` are prepended 70 // onto `dependents`. 71 for i := cursorIndex + 1; i < len(dg.resources); i++ { 72 candidate := dg.resources[i] 73 if isDependent(candidate) { 74 dependents = append(dependents, candidate) 75 dependentSet[candidate.URN] = true 76 } 77 } 78 79 return dependents 80 } 81 82 // DependenciesOf returns a ResourceSet of resources upon which the given resource depends. The resource's parent is 83 // included in the returned set. 84 func (dg *DependencyGraph) DependenciesOf(res *resource.State) ResourceSet { 85 set := make(ResourceSet) 86 87 dependentUrns := make(map[resource.URN]bool) 88 for _, dep := range res.Dependencies { 89 dependentUrns[dep] = true 90 } 91 92 if res.Provider != "" { 93 ref, err := providers.ParseReference(res.Provider) 94 contract.Assert(err == nil) 95 dependentUrns[ref.URN()] = true 96 } 97 98 cursorIndex, ok := dg.index[res] 99 contract.Assert(ok) 100 for i := cursorIndex - 1; i >= 0; i-- { 101 candidate := dg.resources[i] 102 // Include all resources that are dependencies of the resource 103 if dependentUrns[candidate.URN] { 104 set[candidate] = true 105 // If the dependency is a component, all transitive children of the dependency that are before this 106 // resource in the topological sort are also implicitly dependencies. This is necessary because for remote 107 // components, the dependencies will not include the transitive set of children directly, but will include 108 // the parent component. We must walk that component's children here to ensure they are treated as 109 // dependencies. Transitive children of the dependency that are after the resource in the topological sort 110 // are not included as this could lead to cycles in the dependency order. 111 if !candidate.Custom { 112 for _, transitiveCandidateIndex := range dg.childrenOf[candidate.URN] { 113 if transitiveCandidateIndex < cursorIndex { 114 set[dg.resources[transitiveCandidateIndex]] = true 115 } 116 } 117 } 118 } 119 // Include the resource's parent, as the resource depends on it's parent existing. 120 if candidate.URN == res.Parent { 121 set[candidate] = true 122 } 123 } 124 125 return set 126 } 127 128 // `TransitiveDependenciesOf` calculates the set of resources that `r` depends 129 // on, directly or indirectly. This includes as a `Parent`, a member of r's 130 // `Dependencies` list or as a provider. 131 // 132 // This function is linear in the number of resources in the `DependencyGraph`. 133 func (dg *DependencyGraph) TransitiveDependenciesOf(r *resource.State) ResourceSet { 134 dependentProviders := make(map[resource.URN]struct{}) 135 136 urns := make(map[resource.URN]*node, len(dg.resources)) 137 for _, r := range dg.resources { 138 urns[r.URN] = &node{resource: r} 139 } 140 141 // Linearity is due to short circuiting in the traversal. 142 markAsDependency(r.URN, urns, dependentProviders) 143 144 // This will only trigger if (urn, node) is a provider. The check is implicit 145 // in the set lookup. 146 for urn := range urns { 147 if _, ok := dependentProviders[urn]; ok { 148 markAsDependency(urn, urns, dependentProviders) 149 } 150 } 151 152 dependencies := ResourceSet{} 153 for _, r := range urns { 154 if r.marked { 155 dependencies[r.resource] = true 156 } 157 } 158 // We don't want to include `r` as it's own dependency. 159 delete(dependencies, r) 160 return dependencies 161 162 } 163 164 // Mark a resource and its parents as a dependency. This is a helper function for `TransitiveDependenciesOf`. 165 func markAsDependency(urn resource.URN, urns map[resource.URN]*node, dependedProviders map[resource.URN]struct{}) { 166 r := urns[urn] 167 for { 168 r.marked = true 169 if r.resource.Provider != "" { 170 ref, err := providers.ParseReference(r.resource.Provider) 171 contract.AssertNoError(err) 172 dependedProviders[ref.URN()] = struct{}{} 173 } 174 for _, dep := range r.resource.Dependencies { 175 markAsDependency(dep, urns, dependedProviders) 176 } 177 178 // If p is already marked, we don't need to continue to traverse. All 179 // nodes above p will have already been marked. This is a property of 180 // `resources` being topologically sorted. 181 if p, ok := urns[r.resource.Parent]; ok && !p.marked { 182 r = p 183 } else { 184 break 185 } 186 } 187 } 188 189 // NewDependencyGraph creates a new DependencyGraph from a list of resources. 190 // The resources should be in topological order with respect to their dependencies, including 191 // parents appearing before children. 192 func NewDependencyGraph(resources []*resource.State) *DependencyGraph { 193 index := make(map[*resource.State]int) 194 childrenOf := make(map[resource.URN][]int) 195 196 urnIndex := make(map[resource.URN]int) 197 for idx, res := range resources { 198 index[res] = idx 199 urnIndex[res.URN] = idx 200 parent := res.Parent 201 for parent != "" { 202 childrenOf[parent] = append(childrenOf[parent], idx) 203 parent = resources[urnIndex[parent]].Parent 204 } 205 } 206 207 return &DependencyGraph{index, resources, childrenOf} 208 } 209 210 // A node in a graph. 211 type node struct { 212 marked bool 213 resource *resource.State 214 }