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 }