mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-21 10:00:09 -04:00
214 lines
6.1 KiB
Go
214 lines
6.1 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package addrs
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"github.com/hashicorp/terraform/internal/dag"
|
|
)
|
|
|
|
// DirectedGraph represents a directed graph whose nodes are addresses of
|
|
// type T.
|
|
//
|
|
// This graph type supports directed edges between pairs of addresses, and
|
|
// because Terraform most commonly uses graphs to represent dependency
|
|
// relationships it uses "dependency" and "dependent" as the names of the
|
|
// endpoints of an edge, even though technically this data structure could
|
|
// be used to represent other kinds of directed relationships if needed.
|
|
// When used as an operation dependency graph, the "dependency" must be visited
|
|
// before the "dependent".
|
|
//
|
|
// This data structure is not concurrency-safe for writes and so callers must
|
|
// supply suitable synchronization primitives if modifying a graph concurrently
|
|
// with readers or other writers. Concurrent reads of an already-constructed
|
|
// graph are safe.
|
|
type DirectedGraph[T UniqueKeyer] struct {
|
|
// Our dag.AcyclicGraph implementation is a little quirky but also
|
|
// well-tested and stable, so we'll use that for the underlying
|
|
// graph operations and just wrap a slightly nicer address-oriented
|
|
// API around it.
|
|
// Reusing this does mean that some of our operations end up allocating
|
|
// more than they would need to otherwise, so perhaps we'll revisit this
|
|
// in future if it seems to be causing performance problems.
|
|
g *dag.AcyclicGraph
|
|
|
|
// dag.AcyclicGraph can only support node types that are either
|
|
// comparable using == or that implement a special "hashable"
|
|
// interface, so we'll use our UniqueKeyer technique to produce
|
|
// suitable node values but we need a sidecar structure to remember
|
|
// which real address value belongs to each node value.
|
|
nodes map[UniqueKey]T
|
|
}
|
|
|
|
func NewDirectedGraph[T UniqueKeyer]() DirectedGraph[T] {
|
|
return DirectedGraph[T]{
|
|
g: &dag.AcyclicGraph{},
|
|
nodes: map[UniqueKey]T{},
|
|
}
|
|
}
|
|
|
|
func (g DirectedGraph[T]) Add(addr T) {
|
|
k := addr.UniqueKey()
|
|
g.nodes[k] = addr
|
|
g.g.Add(k)
|
|
}
|
|
|
|
func (g DirectedGraph[T]) Has(addr T) bool {
|
|
k := addr.UniqueKey()
|
|
_, ok := g.nodes[k]
|
|
return ok
|
|
}
|
|
|
|
func (g DirectedGraph[T]) Remove(addr T) {
|
|
k := addr.UniqueKey()
|
|
g.g.Remove(k)
|
|
delete(g.nodes, k)
|
|
}
|
|
|
|
func (g DirectedGraph[T]) AllNodes() Set[T] {
|
|
ret := make(Set[T], len(g.nodes))
|
|
for _, addr := range g.nodes {
|
|
ret.Add(addr)
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// AddDependency records that the first address depends on the second.
|
|
//
|
|
// If either address is not already in the graph then it will be implicitly
|
|
// added as part of this operation.
|
|
func (g DirectedGraph[T]) AddDependency(dependent, dependency T) {
|
|
g.Add(dependent)
|
|
g.Add(dependency)
|
|
g.g.Connect(dag.BasicEdge(dependent.UniqueKey(), dependency.UniqueKey()))
|
|
}
|
|
|
|
// DirectDependenciesOf returns only the direct dependencies of the given
|
|
// dependent address.
|
|
func (g DirectedGraph[T]) DirectDependenciesOf(addr T) Set[T] {
|
|
k := addr.UniqueKey()
|
|
ret := MakeSet[T]()
|
|
raw := g.g.DownEdges(k)
|
|
for otherKI := range raw {
|
|
ret.Add(g.nodes[otherKI.(UniqueKey)])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// TransitiveDependenciesOf returns both direct and indirect dependencies of the
|
|
// given dependent address.
|
|
//
|
|
// This operation is valid only for an acyclic graph, and will panic if
|
|
// the graph contains cycles.
|
|
func (g DirectedGraph[T]) TransitiveDependenciesOf(addr T) Set[T] {
|
|
k := addr.UniqueKey()
|
|
ret := MakeSet[T]()
|
|
for otherKI := range g.g.Ancestors(k) {
|
|
ret.Add(g.nodes[otherKI.(UniqueKey)])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// DirectDependentsOf returns only the direct dependents of the given
|
|
// dependency address.
|
|
func (g DirectedGraph[T]) DirectDependentsOf(addr T) Set[T] {
|
|
k := addr.UniqueKey()
|
|
ret := MakeSet[T]()
|
|
raw := g.g.UpEdges(k)
|
|
for otherKI := range raw {
|
|
ret.Add(g.nodes[otherKI.(UniqueKey)])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// TransitiveDependentsOf returns both direct and indirect dependents of the
|
|
// given dependency address.
|
|
//
|
|
// This operation is valid only for an acyclic graph, and will panic if
|
|
// the graph contains cycles.
|
|
func (g DirectedGraph[T]) TransitiveDependentsOf(addr T) Set[T] {
|
|
k := addr.UniqueKey()
|
|
ret := MakeSet[T]()
|
|
for otherKI := range g.g.Descendants(k) {
|
|
ret.Add(g.nodes[otherKI.(UniqueKey)])
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// TopologicalOrder returns one possible topological sort of the addresses
|
|
// in the graph.
|
|
//
|
|
// There are often multiple possible sort orders that preserve the required
|
|
// dependency ordering. The exact order returned by this function is undefined
|
|
// and may vary between calls against the same graph.
|
|
func (g DirectedGraph[T]) TopologicalOrder() []T {
|
|
raw := g.g.TopologicalOrder()
|
|
if len(raw) == 0 {
|
|
return nil
|
|
}
|
|
ret := make([]T, len(raw))
|
|
for i, k := range raw {
|
|
ret[i] = g.nodes[k.(UniqueKey)]
|
|
}
|
|
return ret
|
|
}
|
|
|
|
// StringForComparison outputs a string representing the topology of the
|
|
// graph in a form intended for convenient equality testing by string comparison.
|
|
//
|
|
// For best results all possible dynamic types of T should implement
|
|
// fmt.Stringer.
|
|
func (g DirectedGraph[T]) StringForComparison() string {
|
|
var buf bytes.Buffer
|
|
|
|
stringRepr := func(v any) string {
|
|
switch v := v.(type) {
|
|
case fmt.Stringer:
|
|
return v.String()
|
|
default:
|
|
return fmt.Sprintf("%#v", v)
|
|
}
|
|
}
|
|
|
|
// We want the addresses in a consistent order but it doesn't really
|
|
// matter what that order is, so we'll just do it lexically by each
|
|
// type's standard string representation.
|
|
nodeKeys := make([]UniqueKey, 0, len(g.nodes))
|
|
for k := range g.nodes {
|
|
nodeKeys = append(nodeKeys, k)
|
|
}
|
|
sort.Slice(nodeKeys, func(i, j int) bool {
|
|
iStr := stringRepr(g.nodes[nodeKeys[i]])
|
|
jStr := stringRepr(g.nodes[nodeKeys[j]])
|
|
return iStr < jStr
|
|
})
|
|
|
|
for _, k := range nodeKeys {
|
|
addr := g.nodes[k]
|
|
fmt.Fprintf(&buf, "%s\n", stringRepr(addr))
|
|
|
|
deps := g.DirectDependenciesOf(addr)
|
|
if len(deps) == 0 {
|
|
continue
|
|
}
|
|
|
|
depKeys := make([]UniqueKey, 0, len(deps))
|
|
for k := range deps {
|
|
depKeys = append(depKeys, k)
|
|
}
|
|
sort.Slice(depKeys, func(i, j int) bool {
|
|
iStr := stringRepr(g.nodes[depKeys[i]])
|
|
jStr := stringRepr(g.nodes[depKeys[j]])
|
|
return iStr < jStr
|
|
})
|
|
for _, k := range depKeys {
|
|
fmt.Fprintf(&buf, " %s\n", stringRepr(g.nodes[k]))
|
|
}
|
|
}
|
|
|
|
return buf.String()
|
|
}
|