github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_module_expansion.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package tofu
     7  
     8  import (
     9  	"log"
    10  
    11  	"github.com/opentofu/opentofu/internal/addrs"
    12  	"github.com/opentofu/opentofu/internal/configs"
    13  	"github.com/opentofu/opentofu/internal/dag"
    14  )
    15  
    16  // ModuleExpansionTransformer is a GraphTransformer that adds graph nodes
    17  // representing the possible expansion of each module call in the configuration,
    18  // and ensures that any nodes representing objects declared within a module
    19  // are dependent on the expansion node so that they will be visited only
    20  // after the module expansion has been decided.
    21  //
    22  // This transform must be applied only after all nodes representing objects
    23  // that can be contained within modules have already been added.
    24  type ModuleExpansionTransformer struct {
    25  	Config *configs.Config
    26  
    27  	// Concrete allows injection of a wrapped module node by the graph builder
    28  	// to alter the evaluation behavior.
    29  	Concrete ConcreteModuleNodeFunc
    30  
    31  	closers map[string]*nodeCloseModule
    32  }
    33  
    34  func (t *ModuleExpansionTransformer) Transform(g *Graph) error {
    35  	t.closers = make(map[string]*nodeCloseModule)
    36  	// The root module is always a singleton and so does not need expansion
    37  	// processing, but any descendent modules do. We'll process them
    38  	// recursively using t.transform.
    39  	for _, cfg := range t.Config.Children {
    40  		err := t.transform(g, cfg, nil)
    41  		if err != nil {
    42  			return err
    43  		}
    44  	}
    45  
    46  	// Now go through and connect all nodes to their respective module closers.
    47  	// This is done all at once here, because orphaned modules were already
    48  	// handled by the RemovedModuleTransformer, and those module closers are in
    49  	// the graph already, and need to be connected to their parent closers.
    50  	for _, v := range g.Vertices() {
    51  		switch v.(type) {
    52  		case GraphNodeDestroyer:
    53  			// Destroy nodes can only be ordered relative to other resource
    54  			// instances.
    55  			continue
    56  		case *nodeCloseModule:
    57  			// a module closer cannot connect to itself
    58  			continue
    59  		}
    60  
    61  		// any node that executes within the scope of a module should be a
    62  		// GraphNodeModulePath
    63  		pather, ok := v.(GraphNodeModulePath)
    64  		if !ok {
    65  			continue
    66  		}
    67  		if closer, ok := t.closers[pather.ModulePath().String()]; ok {
    68  			// The module closer depends on each child resource instance, since
    69  			// during apply the module expansion will complete before the
    70  			// individual instances are applied.
    71  			g.Connect(dag.BasicEdge(closer, v))
    72  		}
    73  	}
    74  
    75  	// Modules implicitly depend on their child modules, so connect closers to
    76  	// other which contain their path.
    77  	for _, c := range t.closers {
    78  		for _, d := range t.closers {
    79  			if len(d.Addr) > len(c.Addr) && c.Addr.Equal(d.Addr[:len(c.Addr)]) {
    80  				g.Connect(dag.BasicEdge(c, d))
    81  			}
    82  		}
    83  	}
    84  
    85  	return nil
    86  }
    87  
    88  func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, parentNode dag.Vertex) error {
    89  	_, call := c.Path.Call()
    90  	modCall := c.Parent.Module.ModuleCalls[call.Name]
    91  
    92  	n := &nodeExpandModule{
    93  		Addr:       c.Path,
    94  		Config:     c.Module,
    95  		ModuleCall: modCall,
    96  	}
    97  	var expander dag.Vertex = n
    98  	if t.Concrete != nil {
    99  		expander = t.Concrete(n)
   100  	}
   101  
   102  	g.Add(expander)
   103  	log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", c.Path, expander)
   104  
   105  	if parentNode != nil {
   106  		log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(expander), dag.VertexName(parentNode))
   107  		g.Connect(dag.BasicEdge(expander, parentNode))
   108  	}
   109  
   110  	// Add the closer (which acts as the root module node) to provide a
   111  	// single exit point for the expanded module.
   112  	closer := &nodeCloseModule{
   113  		Addr: c.Path,
   114  	}
   115  	g.Add(closer)
   116  	g.Connect(dag.BasicEdge(closer, expander))
   117  	t.closers[c.Path.String()] = closer
   118  
   119  	for _, childV := range g.Vertices() {
   120  		// don't connect a node to itself
   121  		if childV == expander {
   122  			continue
   123  		}
   124  
   125  		var path addrs.Module
   126  		switch t := childV.(type) {
   127  		case GraphNodeDestroyer:
   128  			// skip destroyers, as they can only depend on other resources.
   129  			continue
   130  
   131  		case GraphNodeModulePath:
   132  			path = t.ModulePath()
   133  		default:
   134  			continue
   135  		}
   136  
   137  		if path.Equal(c.Path) {
   138  			log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(childV), c.Path)
   139  			g.Connect(dag.BasicEdge(childV, expander))
   140  		}
   141  	}
   142  
   143  	// Also visit child modules, recursively.
   144  	for _, cc := range c.Children {
   145  		if err := t.transform(g, cc, expander); err != nil {
   146  			return err
   147  		}
   148  	}
   149  
   150  	return nil
   151  }