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  }