github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/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 // 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 := makeMoveResults() 30 31 if len(stmts) == 0 { 32 return ret 33 } 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 the graph is not valid the we will not take any action at all. The 44 // separate validation step should detect this and return an error. 45 if diags := validateMoveStatementGraph(g); diags.HasErrors() { 46 log.Printf("[ERROR] ApplyMoves: %s", diags.ErrWithWarnings()) 47 return ret 48 } 49 50 // The graph must be reduced in order for ReverseDepthFirstWalk to work 51 // correctly, since it is built from following edges and can skip over 52 // dependencies if there is a direct edge to a transitive dependency. 53 g.TransitiveReduction() 54 55 // The starting nodes are the ones that don't depend on any other nodes. 56 startNodes := make(dag.Set, len(stmts)) 57 for _, v := range g.Vertices() { 58 if len(g.DownEdges(v)) == 0 { 59 startNodes.Add(v) 60 } 61 } 62 63 if startNodes.Len() == 0 { 64 log.Println("[TRACE] refactoring.ApplyMoves: No 'moved' statements to consider in this configuration") 65 return ret 66 } 67 68 log.Printf("[TRACE] refactoring.ApplyMoves: Processing 'moved' statements in the configuration\n%s", logging.Indent(g.String())) 69 70 recordOldAddr := func(oldAddr, newAddr addrs.AbsResourceInstance) { 71 if prevMove, exists := ret.Changes.GetOk(oldAddr); exists { 72 // If the old address was _already_ the result of a move then 73 // we'll replace that entry so that our results summarize a chain 74 // of moves into a single entry. 75 ret.Changes.Remove(oldAddr) 76 oldAddr = prevMove.From 77 } 78 ret.Changes.Put(newAddr, MoveSuccess{ 79 From: oldAddr, 80 To: newAddr, 81 }) 82 } 83 recordBlockage := func(newAddr, wantedAddr addrs.AbsMoveable) { 84 ret.Blocked.Put(newAddr, MoveBlocked{ 85 Wanted: wantedAddr, 86 Actual: newAddr, 87 }) 88 } 89 90 for _, v := range g.ReverseTopologicalOrder() { 91 stmt := v.(*MoveStatement) 92 93 for _, ms := range state.Modules { 94 modAddr := ms.Addr 95 96 // We don't yet know that the current module is relevant, and 97 // we determine that differently for each the object kind. 98 switch kind := stmt.ObjectKind(); kind { 99 case addrs.MoveEndpointModule: 100 // For a module endpoint we just try the module address 101 // directly, and execute the moves if it matches. 102 if newAddr, matches := modAddr.MoveDestination(stmt.From, stmt.To); matches { 103 log.Printf("[TRACE] refactoring.ApplyMoves: %s has moved to %s", modAddr, newAddr) 104 105 // If we already have a module at the new address then 106 // we'll skip this move and let the existing object take 107 // priority. 108 if ms := state.Module(newAddr); ms != nil { 109 log.Printf("[WARN] Skipped moving %s to %s, because there's already another module instance at the destination", modAddr, newAddr) 110 recordBlockage(modAddr, newAddr) 111 continue 112 } 113 114 // We need to visit all of the resource instances in the 115 // module and record them individually as results. 116 for _, rs := range ms.Resources { 117 relAddr := rs.Addr.Resource 118 for key := range rs.Instances { 119 oldInst := relAddr.Instance(key).Absolute(modAddr) 120 newInst := relAddr.Instance(key).Absolute(newAddr) 121 recordOldAddr(oldInst, newInst) 122 } 123 } 124 125 state.MoveModuleInstance(modAddr, newAddr) 126 continue 127 } 128 case addrs.MoveEndpointResource: 129 // For a resource endpoint we require an exact containing 130 // module match, because by definition a matching resource 131 // cannot be nested any deeper than that. 132 if !stmt.From.SelectsModule(modAddr) { 133 continue 134 } 135 136 // We then need to search each of the resources and resource 137 // instances in the module. 138 for _, rs := range ms.Resources { 139 rAddr := rs.Addr 140 if newAddr, matches := rAddr.MoveDestination(stmt.From, stmt.To); matches { 141 log.Printf("[TRACE] refactoring.ApplyMoves: resource %s has moved to %s", rAddr, newAddr) 142 143 // If we already have a resource at the new address then 144 // we'll skip this move and let the existing object take 145 // priority. 146 if rs := state.Resource(newAddr); rs != nil { 147 log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource at the destination", rAddr, newAddr) 148 recordBlockage(rAddr, newAddr) 149 continue 150 } 151 152 for key := range rs.Instances { 153 oldInst := rAddr.Instance(key) 154 newInst := newAddr.Instance(key) 155 recordOldAddr(oldInst, newInst) 156 } 157 state.MoveAbsResource(rAddr, newAddr) 158 continue 159 } 160 for key := range rs.Instances { 161 iAddr := rAddr.Instance(key) 162 if newAddr, matches := iAddr.MoveDestination(stmt.From, stmt.To); matches { 163 log.Printf("[TRACE] refactoring.ApplyMoves: resource instance %s has moved to %s", iAddr, newAddr) 164 165 // If we already have a resource instance at the new 166 // address then we'll skip this move and let the existing 167 // object take priority. 168 if is := state.ResourceInstance(newAddr); is != nil { 169 log.Printf("[WARN] Skipped moving %s to %s, because there's already another resource instance at the destination", iAddr, newAddr) 170 recordBlockage(iAddr, newAddr) 171 continue 172 } 173 174 recordOldAddr(iAddr, newAddr) 175 176 state.MoveAbsResourceInstance(iAddr, newAddr) 177 continue 178 } 179 } 180 } 181 default: 182 panic(fmt.Sprintf("unhandled move object kind %s", kind)) 183 } 184 } 185 } 186 187 return ret 188 } 189 190 // buildMoveStatementGraph constructs a dependency graph of the given move 191 // statements, where the nodes are all pointers to statements in the given 192 // slice and the edges represent either chaining or nesting relationships. 193 // 194 // buildMoveStatementGraph doesn't do any validation of the graph, so it 195 // may contain cycles and other sorts of invalidity. 196 func buildMoveStatementGraph(stmts []MoveStatement) *dag.AcyclicGraph { 197 g := &dag.AcyclicGraph{} 198 for i := range stmts { 199 // The graph nodes are pointers to the actual statements directly. 200 g.Add(&stmts[i]) 201 } 202 203 // Now we'll add the edges representing chaining and nesting relationships. 204 // We assume that a reasonable configuration will have at most tens of 205 // move statements and thus this N*M algorithm is acceptable. 206 for dependerI := range stmts { 207 depender := &stmts[dependerI] 208 for dependeeI := range stmts { 209 if dependerI == dependeeI { 210 // skip comparing the statement to itself 211 continue 212 } 213 dependee := &stmts[dependeeI] 214 215 if statementDependsOn(depender, dependee) { 216 g.Connect(dag.BasicEdge(depender, dependee)) 217 } 218 } 219 } 220 221 return g 222 } 223 224 // statementDependsOn returns true if statement a depends on statement b; 225 // i.e. statement b must be executed before statement a. 226 func statementDependsOn(a, b *MoveStatement) bool { 227 // chain-able moves are simple, as on the destination of one move could be 228 // equal to the source of another. 229 if a.From.CanChainFrom(b.To) { 230 return true 231 } 232 233 // Statement nesting in more complex, as we have 8 possible combinations to 234 // assess. Here we list all combinations, along with the statement which 235 // must be executed first when one address is nested within another. 236 // A.From IsNestedWithin B.From => A 237 // A.From IsNestedWithin B.To => B 238 // A.To IsNestedWithin B.From => A 239 // A.To IsNestedWithin B.To => B 240 // B.From IsNestedWithin A.From => B 241 // B.From IsNestedWithin A.To => A 242 // B.To IsNestedWithin A.From => B 243 // B.To IsNestedWithin A.To => A 244 // 245 // Since we are only interested in checking if A depends on B, we only need 246 // to check the 4 possibilities above which result in B being executed 247 // first. If we're there's no dependency at all we can return immediately. 248 if !(a.From.NestedWithin(b.To) || a.To.NestedWithin(b.To) || 249 b.From.NestedWithin(a.From) || b.To.NestedWithin(a.From)) { 250 return false 251 } 252 253 // If a nested move has a dependency, we need to rule out the possibility 254 // that this is a move inside a module only changing indexes. If an 255 // ancestor module is only changing the index of a nested module, any 256 // nested move statements are going to match both the From and To address 257 // when the base name is not changing, causing a cycle in the order of 258 // operations. 259 260 // if A is not declared in an ancestor module, then we can't be nested 261 // within a module index change. 262 if len(a.To.Module()) >= len(b.To.Module()) { 263 return true 264 } 265 // We only want the nested move statement to depend on the outer module 266 // move, so we only test this in the reverse direction. 267 if a.From.IsModuleReIndex(a.To) { 268 return false 269 } 270 271 return true 272 } 273 274 // MoveResults describes the outcome of an ApplyMoves call. 275 type MoveResults struct { 276 // Changes is a map from the unique keys of the final new resource 277 // instance addresses to an object describing what changed. 278 // 279 // This includes one entry for each resource instance address that was 280 // the destination of a move statement. It doesn't include resource 281 // instances that were not affected by moves at all, but it does include 282 // resource instance addresses that were "blocked" (also recorded in 283 // BlockedAddrs) if and only if they were able to move at least 284 // partially along a chain before being blocked. 285 // 286 // In the return value from ApplyMoves, all of the keys are guaranteed to 287 // be unique keys derived from addrs.AbsResourceInstance values. 288 Changes addrs.Map[addrs.AbsResourceInstance, MoveSuccess] 289 290 // Blocked is a map from the unique keys of the final new 291 // resource instances addresses to information about where they "wanted" 292 // to move, but were blocked by a pre-existing object at the same address. 293 // 294 // "Blocking" can arise in unusual situations where multiple points along 295 // a move chain were already bound to objects, and thus only one of them 296 // can actually adopt the final position in the chain. It can also 297 // occur in other similar situations, such as if a configuration contains 298 // a move of an entire module and a move of an individual resource into 299 // that module, such that the individual resource would collide with a 300 // resource in the whole module that was moved. 301 // 302 // In the return value from ApplyMoves, all of the keys are guaranteed to 303 // be unique keys derived from values of addrs.AbsMoveable types. 304 Blocked addrs.Map[addrs.AbsMoveable, MoveBlocked] 305 } 306 307 func makeMoveResults() MoveResults { 308 return MoveResults{ 309 Changes: addrs.MakeMap[addrs.AbsResourceInstance, MoveSuccess](), 310 Blocked: addrs.MakeMap[addrs.AbsMoveable, MoveBlocked](), 311 } 312 } 313 314 type MoveSuccess struct { 315 From addrs.AbsResourceInstance 316 To addrs.AbsResourceInstance 317 } 318 319 type MoveBlocked struct { 320 Wanted addrs.AbsMoveable 321 Actual addrs.AbsMoveable 322 } 323 324 // AddrMoved returns true if and only if the given resource instance moved to 325 // a new address in the ApplyMoves call that the receiver is describing. 326 // 327 // If AddrMoved returns true, you can pass the same address to method OldAddr 328 // to find its original address prior to moving. 329 func (rs MoveResults) AddrMoved(newAddr addrs.AbsResourceInstance) bool { 330 return rs.Changes.Has(newAddr) 331 } 332 333 // OldAddr returns the old address of the given resource instance address, or 334 // just returns back the same address if the given instance wasn't affected by 335 // any move statements. 336 func (rs MoveResults) OldAddr(newAddr addrs.AbsResourceInstance) addrs.AbsResourceInstance { 337 change, ok := rs.Changes.GetOk(newAddr) 338 if !ok { 339 return newAddr 340 } 341 return change.From 342 }