github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/graph.go (about)

     1  package terraform
     2  
     3  import (
     4  	"fmt"
     5  	"log"
     6  
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
    10  
    11  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
    12  )
    13  
    14  // Graph represents the graph that Terraform uses to represent resources
    15  // and their dependencies.
    16  type Graph struct {
    17  	// Graph is the actual DAG. This is embedded so you can call the DAG
    18  	// methods directly.
    19  	dag.AcyclicGraph
    20  
    21  	// Path is the path in the module tree that this Graph represents.
    22  	Path addrs.ModuleInstance
    23  
    24  	// debugName is a name for reference in the debug output. This is usually
    25  	// to indicate what topmost builder was, and if this graph is a shadow or
    26  	// not.
    27  	debugName string
    28  }
    29  
    30  func (g *Graph) DirectedGraph() dag.Grapher {
    31  	return &g.AcyclicGraph
    32  }
    33  
    34  // Walk walks the graph with the given walker for callbacks. The graph
    35  // will be walked with full parallelism, so the walker should expect
    36  // to be called in concurrently.
    37  func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics {
    38  	return g.walk(walker)
    39  }
    40  
    41  func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
    42  	// The callbacks for enter/exiting a graph
    43  	ctx := walker.EnterPath(g.Path)
    44  	defer walker.ExitPath(g.Path)
    45  
    46  	// Get the path for logs
    47  	path := ctx.Path().String()
    48  
    49  	debugName := "walk-graph.json"
    50  	if g.debugName != "" {
    51  		debugName = g.debugName + "-" + debugName
    52  	}
    53  
    54  	// Walk the graph.
    55  	var walkFn dag.WalkFunc
    56  	walkFn = func(v dag.Vertex) (diags tfdiags.Diagnostics) {
    57  		log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v)
    58  		g.DebugVisitInfo(v, g.debugName)
    59  
    60  		defer func() {
    61  			log.Printf("[TRACE] vertex %q: visit complete", dag.VertexName(v))
    62  		}()
    63  
    64  		walker.EnterVertex(v)
    65  		defer walker.ExitVertex(v, diags)
    66  
    67  		// vertexCtx is the context that we use when evaluating. This
    68  		// is normally the context of our graph but can be overridden
    69  		// with a GraphNodeSubPath impl.
    70  		vertexCtx := ctx
    71  		if pn, ok := v.(GraphNodeSubPath); ok && len(pn.Path()) > 0 {
    72  			vertexCtx = walker.EnterPath(pn.Path())
    73  			defer walker.ExitPath(pn.Path())
    74  		}
    75  
    76  		// If the node is eval-able, then evaluate it.
    77  		if ev, ok := v.(GraphNodeEvalable); ok {
    78  			tree := ev.EvalTree()
    79  			if tree == nil {
    80  				panic(fmt.Sprintf("%q (%T): nil eval tree", dag.VertexName(v), v))
    81  			}
    82  
    83  			// Allow the walker to change our tree if needed. Eval,
    84  			// then callback with the output.
    85  			log.Printf("[TRACE] vertex %q: evaluating", dag.VertexName(v))
    86  
    87  			g.DebugVertexInfo(v, fmt.Sprintf("evaluating %T(%s)", v, path))
    88  
    89  			tree = walker.EnterEvalTree(v, tree)
    90  			output, err := Eval(tree, vertexCtx)
    91  			diags = diags.Append(walker.ExitEvalTree(v, output, err))
    92  			if diags.HasErrors() {
    93  				return
    94  			}
    95  		}
    96  
    97  		// If the node is dynamically expanded, then expand it
    98  		if ev, ok := v.(GraphNodeDynamicExpandable); ok {
    99  			log.Printf("[TRACE] vertex %q: expanding dynamic subgraph", dag.VertexName(v))
   100  
   101  			g.DebugVertexInfo(v, fmt.Sprintf("expanding %T(%s)", v, path))
   102  
   103  			g, err := ev.DynamicExpand(vertexCtx)
   104  			if err != nil {
   105  				diags = diags.Append(err)
   106  				return
   107  			}
   108  			if g != nil {
   109  				// Walk the subgraph
   110  				log.Printf("[TRACE] vertex %q: entering dynamic subgraph", dag.VertexName(v))
   111  				subDiags := g.walk(walker)
   112  				diags = diags.Append(subDiags)
   113  				if subDiags.HasErrors() {
   114  					log.Printf("[TRACE] vertex %q: dynamic subgraph encountered errors", dag.VertexName(v))
   115  					return
   116  				}
   117  				log.Printf("[TRACE] vertex %q: dynamic subgraph completed successfully", dag.VertexName(v))
   118  			} else {
   119  				log.Printf("[TRACE] vertex %q: produced no dynamic subgraph", dag.VertexName(v))
   120  			}
   121  		}
   122  
   123  		// If the node has a subgraph, then walk the subgraph
   124  		if sn, ok := v.(GraphNodeSubgraph); ok {
   125  			log.Printf("[TRACE] vertex %q: entering static subgraph", dag.VertexName(v))
   126  
   127  			g.DebugVertexInfo(v, fmt.Sprintf("subgraph: %T(%s)", v, path))
   128  
   129  			subDiags := sn.Subgraph().(*Graph).walk(walker)
   130  			if subDiags.HasErrors() {
   131  				log.Printf("[TRACE] vertex %q: static subgraph encountered errors", dag.VertexName(v))
   132  				return
   133  			}
   134  			log.Printf("[TRACE] vertex %q: static subgraph completed successfully", dag.VertexName(v))
   135  		}
   136  
   137  		return
   138  	}
   139  
   140  	return g.AcyclicGraph.Walk(walkFn)
   141  }