github.com/jaredpalmer/terraform@v1.1.0-alpha20210908.0.20210911170307-88705c943a03/internal/refactoring/move_execute.go (about) 1 package refactoring 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/internal/addrs" 8 "github.com/hashicorp/terraform/internal/dag" 9 "github.com/hashicorp/terraform/internal/logging" 10 "github.com/hashicorp/terraform/internal/states" 11 ) 12 13 type MoveResult struct { 14 From, To addrs.AbsResourceInstance 15 } 16 17 // ApplyMoves modifies in-place the given state object so that any existing 18 // objects that are matched by a "from" argument of one of the move statements 19 // will be moved to instead appear at the "to" argument of that statement. 20 // 21 // The result is a map from the unique key of each absolute address that was 22 // either the source or destination of a move to a MoveResult describing 23 // what happened at that address. 24 // 25 // ApplyMoves does not have any error situations itself, and will instead just 26 // ignore any unresolvable move statements. Validation of a set of moves is 27 // a separate concern applied to the configuration, because validity of 28 // moves is always dependent only on the configuration, not on the state. 29 // 30 // ApplyMoves expects exclusive access to the given state while it's running. 31 // Don't read or write any part of the state structure until ApplyMoves returns. 32 func ApplyMoves(stmts []MoveStatement, state *states.State) map[addrs.UniqueKey]MoveResult { 33 results := make(map[addrs.UniqueKey]MoveResult) 34 35 // The methodology here is to construct a small graph of all of the move 36 // statements where the edges represent where a particular statement 37 // is either chained from or nested inside the effect of another statement. 38 // That then means we can traverse the graph in topological sort order 39 // to gradually move objects through potentially multiple moves each. 40 41 g := buildMoveStatementGraph(stmts) 42 43 // If there are any cycles in the graph then we'll not take any action 44 // at all. The separate validation step should detect this and return 45 // an error. 46 if len(g.Cycles()) != 0 { 47 return results 48 } 49 50 // The starting nodes are the ones that don't depend on any other nodes. 51 startNodes := make(dag.Set, len(stmts)) 52 for _, v := range g.Vertices() { 53 if len(g.DownEdges(v)) == 0 { 54 startNodes.Add(v) 55 } 56 } 57 58 if startNodes.Len() == 0 { 59 log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration") 60 return results 61 } 62 63 log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String())) 64 65 g.ReverseDepthFirstWalk(startNodes, func(v dag.Vertex, depth int) error { 66 stmt := v.(*MoveStatement) 67 68 for _, ms := range state.Modules { 69 modAddr := ms.Addr 70 if !stmt.From.SelectsModule(modAddr) { 71 continue 72 } 73 74 // We now know that the current module is relevant but what 75 // we'll do with it depends on the object kind. 76 switch kind := stmt.ObjectKind(); kind { 77 case addrs.MoveEndpointModule: 78 // For a module endpoint we just try the module address 79 // directly. 80 if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches { 81 log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr) 82 // We need to visit all of the resource instances in the 83 // module and record them individually as results. 84 for _, rs := range ms.Resources { 85 relAddr := rs.Addr.Resource 86 for key := range rs.Instances { 87 oldInst := relAddr.Instance(key).Absolute(modAddr) 88 newInst := relAddr.Instance(key).Absolute(newAddr) 89 result := MoveResult{ 90 From: oldInst, 91 To: newInst, 92 } 93 results[oldInst.UniqueKey()] = result 94 results[newInst.UniqueKey()] = result 95 } 96 } 97 98 state.MoveModuleInstance(modAddr, newAddr) 99 continue 100 } 101 case addrs.MoveEndpointResource: 102 // For a resource endpoint we need to search each of the 103 // resources and resource instances in the module. 104 for _, rs := range ms.Resources { 105 rAddr := rs.Addr 106 if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches { 107 log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr) 108 for key := range rs.Instances { 109 oldInst := rAddr.Instance(key) 110 newInst := newAddr.Instance(key) 111 result := MoveResult{ 112 From: oldInst, 113 To: newInst, 114 } 115 results[oldInst.UniqueKey()] = result 116 results[newInst.UniqueKey()] = result 117 } 118 state.MoveAbsResource(rAddr, newAddr) 119 continue 120 } 121 for key := range rs.Instances { 122 iAddr := rAddr.Instance(key) 123 if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches { 124 log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr) 125 result := MoveResult{From: iAddr, To: newAddr} 126 results[iAddr.UniqueKey()] = result 127 results[newAddr.UniqueKey()] = result 128 129 state.MoveAbsResourceInstance(iAddr, newAddr) 130 continue 131 } 132 } 133 } 134 default: 135 panic(fmt.Sprintf("unhandled move object kind %s", kind)) 136 } 137 } 138 139 return nil 140 }) 141 142 // FIXME: In the case of either chained or nested moves, "results" will 143 // be left in a pretty interesting shape where the "old" address will 144 // refer to a result that describes only the first step, while the "new" 145 // address will refer to a result that describes only the last step. 146 // To make that actually useful we'll need a different strategy where 147 // the result describes the _effective_ source and destination, skipping 148 // over any intermediate steps we took to get there, so that ultimately 149 // we'll have enough information to annotate items in the plan with the 150 // addresses the originally moved from. 151 152 return results 153 } 154 155 // buildMoveStatementGraph constructs a dependency graph of the given move 156 // statements, where the nodes are all pointers to statements in the given 157 // slice and the edges represent either chaining or nesting relationships. 158 // 159 // buildMoveStatementGraph doesn't do any validation of the graph, so it 160 // may contain cycles and other sorts of invalidity. 161 func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph { 162 g := &dag.AcyclicGraph{} 163 for i := range stmts { 164 // The graph nodes are pointers to the actual statements directly. 165 g.Add(&stmts[i]) 166 } 167 168 // Now we'll add the edges representing chaining and nesting relationships. 169 // We assume that a reasonable configuration will have at most tens of 170 // move statements and thus this N*M algorithm is acceptable. 171 for dependerI := range stmts { 172 depender := &stmts[dependerI] 173 for dependeeI := range stmts { 174 dependee := &stmts[dependeeI] 175 dependeeTo := dependee.To 176 dependerFrom := depender.From 177 if dependerFrom.CanChainFrom(dependeeTo) || dependerFrom.NestedWithin(dependeeTo) { 178 g.Connect(dag.BasicEdge(depender, dependee)) 179 } 180 } 181 } 182 183 return g 184 }