github.com/hugorut/terraform@v1.1.3/src/refactoring/move_execute.go (about) 1 package refactoring 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hugorut/terraform/src/addrs" 8 "github.com/hugorut/terraform/src/dag" 9 "github.com/hugorut/terraform/src/logging" 10 "github.com/hugorut/terraform/src/states" 11 ) 12 13 // ApplyMoves modifies in-place the given state object so that any existing 14 // objects that are matched by a "from" argument of one of the move statements 15 // will be moved to instead appear at the "to" argument of that statement. 16 // 17 // The result is a map from the unique key of each absolute address that was 18 // either the source or destination of a move to a MoveResult describing 19 // what happened at that address. 20 // 21 // ApplyMoves does not have any error situations itself, and will instead just 22 // ignore any unresolvable move statements. Validation of a set of moves is 23 // a separate concern applied to the configuration, because validity of 24 // moves is always dependent only on the configuration, not on the state. 25 // 26 // ApplyMoves expects exclusive access to the given state while it's running. 27 // Don't read or write any part of the state structure until ApplyMoves returns. 28 func ApplyMoves(stmts []MoveStatement, state *states.State) MoveResults { 29 ret := MoveResults{ 30 Changes: make(map[addrs.UniqueKey]MoveSuccess), 31 Blocked: make(map[addrs.UniqueKey]MoveBlocked), 32 } 33 34 // The methodology here is to construct a small graph of all of the move 35 // statements where the edges represent where a particular statement 36 // is either chained from or nested inside the effect of another statement. 37 // That then means we can traverse the graph in topological sort order 38 // to gradually move objects through potentially multiple moves each. 39 40 g := buildMoveStatementGraph(stmts) 41 42 // If there are any cycles in the graph then we'll not take any action 43 // at all. The separate validation step should detect this and return 44 // an error. 45 if len(g.Cycles()) != 0 { 46 return ret 47 } 48 49 // The starting nodes are the ones that don't depend on any other nodes. 50 startNodes := make(dag.Set, len(stmts)) 51 for _, v := range g.Vertices() { 52 if len(g.DownEdges(v)) == 0 { 53 startNodes.Add(v) 54 } 55 } 56 57 if startNodes.Len() == 0 { 58 log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration") 59 return ret 60 } 61 62 log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String())) 63 64 recordOldAddr := func(oldAddr, newAddr addrs.AbsResourceInstance) { 65 oldAddrKey := oldAddr.UniqueKey() 66 newAddrKey := newAddr.UniqueKey() 67 if prevMove, exists := ret.Changes[oldAddrKey]; exists { 68 // If the old address was _already_ the result of a move then 69 // we'll replace that entry so that our results summarize a chain 70 // of moves into a single entry. 71 delete(ret.Changes, oldAddrKey) 72 oldAddr = prevMove.From 73 } 74 ret.Changes[newAddrKey] = MoveSuccess{ 75 From: oldAddr, 76 To: newAddr, 77 } 78 } 79 recordBlockage := func(newAddr, wantedAddr addrs.AbsMoveable) { 80 ret.Blocked[newAddr.UniqueKey()] = MoveBlocked{ 81 Wanted: wantedAddr, 82 Actual: newAddr, 83 } 84 } 85 86 g.ReverseDepthFirstWalk(startNodes, func(v dag.Vertex, depth int) error { 87 stmt := v.(*MoveStatement) 88 89 for _, ms := range state.Modules { 90 modAddr := ms.Addr 91 if !stmt.From.SelectsModule(modAddr) { 92 continue 93 } 94 95 // We now know that the current module is relevant but what 96 // we'll do with it depends on the object kind. 97 switch kind := stmt.ObjectKind(); kind { 98 case addrs.MoveEndpointModule: 99 // For a module endpoint we just try the module address 100 // directly. 101 if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches { 102 log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr) 103 104 // If we already have a module at the new address then 105 // we'll skip this move and let the existing object take 106 // priority. 107 if ms := state.Module(newAddr); ms != nil { 108 log.Printf("[WARN] Skipped moving %s to %s, because there's already another module instance at the destination", modAddr, newAddr) 109 recordBlockage(modAddr, newAddr) 110 continue 111 } 112 113 // We need to visit all of the resource instances in the 114 // module and record them individually as results. 115 for _, rs := range ms.Resources { 116 relAddr := rs.Addr.Resource 117 for key := range rs.Instances { 118 oldInst := relAddr.Instance(key).Absolute(modAddr) 119 newInst := relAddr.Instance(key).Absolute(newAddr) 120 recordOldAddr(oldInst, newInst) 121 } 122 } 123 124 state.MoveModuleInstance(modAddr, newAddr) 125 continue 126 } 127 case addrs.MoveEndpointResource: 128 // For a resource endpoint we need to search each of the 129 // resources and resource instances in the module. 130 for _, rs := range ms.Resources { 131 rAddr := rs.Addr 132 if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches { 133 log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr) 134 135 // If we already have a resource at the new address then 136 // we'll skip this move and let the existing object take 137 // priority. 138 if rs := state.Resource(newAddr); rs != nil { 139 log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource at the destination", rAddr, newAddr) 140 recordBlockage(rAddr, newAddr) 141 continue 142 } 143 144 for key := range rs.Instances { 145 oldInst := rAddr.Instance(key) 146 newInst := newAddr.Instance(key) 147 recordOldAddr(oldInst, newInst) 148 } 149 state.MoveAbsResource(rAddr, newAddr) 150 continue 151 } 152 for key := range rs.Instances { 153 iAddr := rAddr.Instance(key) 154 if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches { 155 log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr) 156 157 // If we already have a resource instance at the new 158 // address then we'll skip this move and let the existing 159 // object take priority. 160 if is := state.ResourceInstance(newAddr); is != nil { 161 log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource instance at the destination", iAddr, newAddr) 162 recordBlockage(iAddr, newAddr) 163 continue 164 } 165 166 recordOldAddr(iAddr, newAddr) 167 168 state.MoveAbsResourceInstance(iAddr, newAddr) 169 continue 170 } 171 } 172 } 173 default: 174 panic(fmt.Sprintf("unhandled move object kind %s", kind)) 175 } 176 } 177 178 return nil 179 }) 180 181 return ret 182 } 183 184 // buildMoveStatementGraph constructs a dependency graph of the given move 185 // statements, where the nodes are all pointers to statements in the given 186 // slice and the edges represent either chaining or nesting relationships. 187 // 188 // buildMoveStatementGraph doesn't do any validation of the graph, so it 189 // may contain cycles and other sorts of invalidity. 190 func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph { 191 g := &dag.AcyclicGraph{} 192 for i := range stmts { 193 // The graph nodes are pointers to the actual statements directly. 194 g.Add(&stmts[i]) 195 } 196 197 // Now we'll add the edges representing chaining and nesting relationships. 198 // We assume that a reasonable configuration will have at most tens of 199 // move statements and thus this N*M algorithm is acceptable. 200 for dependerI := range stmts { 201 depender := &stmts[dependerI] 202 for dependeeI := range stmts { 203 if dependerI == dependeeI { 204 // skip comparing the statement to itself 205 continue 206 } 207 dependee := &stmts[dependeeI] 208 209 if statementDependsOn(depender, dependee) { 210 g.Connect(dag.BasicEdge(depender, dependee)) 211 } 212 } 213 } 214 215 return g 216 } 217 218 // statementDependsOn returns true if statement a depends on statement b; 219 // i.e. statement b must be executed before statement a. 220 func statementDependsOn(a, b *MoveStatement) bool { 221 // chain-able moves are simple, as on the destination of one move could be 222 // equal to the source of another. 223 if a.From.CanChainFrom(b.To) { 224 return true 225 } 226 227 // Statement nesting in more complex, as we have 8 possible combinations to 228 // assess. Here we list all combinations, along with the statement which 229 // must be executed first when one address is nested within another. 230 // A.From IsNestedWithin B.From => A 231 // A.From IsNestedWithin B.To => B 232 // A.To IsNestedWithin B.From => A 233 // A.To IsNestedWithin B.To => B 234 // B.From IsNestedWithin A.From => B 235 // B.From IsNestedWithin A.To => A 236 // B.To IsNestedWithin A.From => B 237 // B.To IsNestedWithin A.To => A 238 // 239 // Since we are only interested in checking if A depends on B, we only need 240 // to check the 4 possibilities above which result in B being executed 241 // first. 242 return a.From.NestedWithin(b.To) || 243 a.To.NestedWithin(b.To) || 244 b.From.NestedWithin(a.From) || 245 b.To.NestedWithin(a.From) 246 } 247 248 // MoveResults describes the outcome of an ApplyMoves call. 249 type MoveResults struct { 250 // Changes is a map from the unique keys of the final new resource 251 // instance addresses to an object describing what changed. 252 // 253 // This includes one entry for each resource instance address that was 254 // the destination of a move statement. It doesn't include resource 255 // instances that were not affected by moves at all, but it does include 256 // resource instance addresses that were "blocked" (also recorded in 257 // BlockedAddrs) if and only if they were able to move at least 258 // partially along a chain before being blocked. 259 // 260 // In the return value from ApplyMoves, all of the keys are guaranteed to 261 // be unique keys derived from addrs.AbsResourceInstance values. 262 Changes map[addrs.UniqueKey]MoveSuccess 263 264 // Blocked is a map from the unique keys of the final new 265 // resource instances addresses to information about where they "wanted" 266 // to move, but were blocked by a pre-existing object at the same address. 267 // 268 // "Blocking" can arise in unusual situations where multiple points along 269 // a move chain were already bound to objects, and thus only one of them 270 // can actually adopt the final position in the chain. It can also 271 // occur in other similar situations, such as if a configuration contains 272 // a move of an entire module and a move of an individual resource into 273 // that module, such that the individual resource would collide with a 274 // resource in the whole module that was moved. 275 // 276 // In the return value from ApplyMoves, all of the keys are guaranteed to 277 // be unique keys derived from values of addrs.AbsMoveable types. 278 Blocked map[addrs.UniqueKey]MoveBlocked 279 } 280 281 type MoveSuccess struct { 282 From addrs.AbsResourceInstance 283 To addrs.AbsResourceInstance 284 } 285 286 type MoveBlocked struct { 287 Wanted addrs.AbsMoveable 288 Actual addrs.AbsMoveable 289 } 290 291 // AddrMoved returns true if and only if the given resource instance moved to 292 // a new address in the ApplyMoves call that the receiver is describing. 293 // 294 // If AddrMoved returns true, you can pass the same address to method OldAddr 295 // to find its original address prior to moving. 296 func (rs MoveResults) AddrMoved(newAddr addrs.AbsResourceInstance) bool { 297 _, ok := rs.Changes[newAddr.UniqueKey()] 298 return ok 299 } 300 301 // OldAddr returns the old address of the given resource instance address, or 302 // just returns back the same address if the given instance wasn't affected by 303 // any move statements. 304 func (rs MoveResults) OldAddr(newAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance { 305 change, ok := rs.Changes[newAddr.UniqueKey()] 306 if !ok { 307 return newAddr 308 } 309 return change.From 310 }