github.com/eliastor/durgaform@v0.0.0-20220816172711-d0ab2d17673e/internal/durgaform/transform_module_expansion.go (about)

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