github.com/helmwave/helmwave@v0.36.4-0.20240509190856-b35563eba4c6/pkg/release/dependency/graph.go (about)

     1  package dependency
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"golang.org/x/exp/maps"
     7  )
     8  
     9  // Graph is dependencies graph. K stands for map keys type (e.g. string names), N for data type.
    10  type Graph[K comparable, N any] struct {
    11  	Nodes        map[K]*Node[N]
    12  	dependencies []GraphDependency[K]
    13  }
    14  
    15  // NewGraph returns empty graph.
    16  func NewGraph[K comparable, N any]() *Graph[K, N] {
    17  	return &Graph[K, N]{
    18  		Nodes:        make(map[K]*Node[N]),
    19  		dependencies: make([]GraphDependency[K], 0),
    20  	}
    21  }
    22  
    23  func (graph *Graph[K, N]) NewNode(key K, data N) error {
    24  	if _, ok := graph.Nodes[key]; ok {
    25  		return fmt.Errorf("key %v already exists", key)
    26  	}
    27  
    28  	node := newNode(data)
    29  	graph.Nodes[key] = node
    30  
    31  	return nil
    32  }
    33  
    34  // AddDependency adds lazy dependency. It will be evaluated only in `Build` method.
    35  func (graph *Graph[K, N]) AddDependency(dependant, dependency K) {
    36  	graph.dependencies = append(graph.dependencies, newDependency(dependant, dependency))
    37  }
    38  
    39  func (graph *Graph[K, N]) Reverse() (*Graph[K, N], error) {
    40  	newDependenciesGraph := NewGraph[K, N]()
    41  
    42  	for key, node := range graph.Nodes {
    43  		err := newDependenciesGraph.NewNode(key, node.Data)
    44  		if err != nil {
    45  			return nil, err
    46  		}
    47  	}
    48  
    49  	for _, dep := range graph.dependencies {
    50  		newDependenciesGraph.AddDependency(dep.Dependency(), dep.Dependant())
    51  	}
    52  
    53  	err := newDependenciesGraph.Build()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  
    58  	return newDependenciesGraph, nil
    59  }
    60  
    61  func (graph *Graph[K, N]) Build() error {
    62  	for _, dep := range graph.dependencies {
    63  		dependant, ok := graph.Nodes[dep.Dependant()]
    64  		if !ok {
    65  			return fmt.Errorf("dependant key %v does not exist", dep.Dependant())
    66  		}
    67  		dependency, ok := graph.Nodes[dep.Dependency()]
    68  		if !ok {
    69  			return fmt.Errorf("dependency key %v does not exist", dep.Dependency())
    70  		}
    71  
    72  		dependant.addDependency(dependency)
    73  	}
    74  
    75  	if err := graph.detectCycle(); err != nil {
    76  		return err
    77  	}
    78  
    79  	return nil
    80  }
    81  
    82  func (graph *Graph[K, N]) detectCycle() error {
    83  	visited := make(map[*Node[N]]int)
    84  
    85  	for _, node := range graph.Nodes {
    86  		err := graph.dfs(node, visited)
    87  		if err != nil {
    88  			return err
    89  		}
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  // dfs is Depth First Search.
    96  func (graph *Graph[K, N]) dfs(node *Node[N], visited map[*Node[N]]int) error {
    97  	// This means that during recursion we hit node that is already being dfs'd
    98  	if visited[node] == -1 {
    99  		return fmt.Errorf("graph loop detected (starts with %v)", node)
   100  	}
   101  
   102  	if visited[node] == 1 {
   103  		return nil
   104  	}
   105  
   106  	visited[node] = -1
   107  
   108  	for _, dep := range node.dependencies {
   109  		err := graph.dfs(dep, visited)
   110  		if err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	visited[node] = 1
   116  
   117  	return nil
   118  }
   119  
   120  func (graph *Graph[K, N]) runChan(ch chan<- *Node[N]) {
   121  	nodes := maps.Clone(graph.Nodes)
   122  
   123  	for len(nodes) > 0 {
   124  		for key, node := range nodes {
   125  			switch {
   126  			// In case some release failed because it's dependency failed
   127  			case node.IsDone():
   128  				delete(nodes, key)
   129  			case node.IsReady():
   130  				ch <- node
   131  				delete(nodes, key)
   132  			}
   133  		}
   134  	}
   135  
   136  	close(ch)
   137  }
   138  
   139  // Run returns channel for data and runs goroutine that handles dependency graph
   140  // and populates channel with ready to install releases.
   141  func (graph *Graph[K, N]) Run() <-chan *Node[N] {
   142  	ch := make(chan *Node[N], len(graph.Nodes))
   143  	go graph.runChan(ch)
   144  
   145  	return ch
   146  }
   147  
   148  type GraphDependency[K comparable] [2]K
   149  
   150  func newDependency[K comparable](dependant, dependency K) GraphDependency[K] {
   151  	return GraphDependency[K]{dependant, dependency}
   152  }
   153  
   154  func (dep GraphDependency[K]) Dependant() K {
   155  	return dep[0]
   156  }
   157  
   158  func (dep GraphDependency[K]) Dependency() K {
   159  	return dep[1]
   160  }