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