github.com/opentofu/opentofu@v1.7.1/internal/tofu/transform_destroy_edge.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/dag" 13 "github.com/opentofu/opentofu/internal/plans" 14 ) 15 16 // GraphNodeDestroyer must be implemented by nodes that destroy resources. 17 type GraphNodeDestroyer interface { 18 dag.Vertex 19 20 // DestroyAddr is the address of the resource that is being 21 // destroyed by this node. If this returns nil, then this node 22 // is not destroying anything. 23 DestroyAddr() *addrs.AbsResourceInstance 24 } 25 26 // GraphNodeCreator must be implemented by nodes that create OR update resources. 27 type GraphNodeCreator interface { 28 // CreateAddr is the address of the resource being created or updated 29 CreateAddr() *addrs.AbsResourceInstance 30 } 31 32 // DestroyEdgeTransformer is a GraphTransformer that creates the proper 33 // references for destroy resources. Destroy resources are more complex 34 // in that they must be depend on the destruction of resources that 35 // in turn depend on the CREATION of the node being destroy. 36 // 37 // That is complicated. Visually: 38 // 39 // B_d -> A_d -> A -> B 40 // 41 // Notice that A destroy depends on B destroy, while B create depends on 42 // A create. They're inverted. This must be done for example because often 43 // dependent resources will block parent resources from deleting. Concrete 44 // example: VPC with subnets, the VPC can't be deleted while there are 45 // still subnets. 46 type DestroyEdgeTransformer struct { 47 // FIXME: GraphNodeCreators are not always applying changes, and should not 48 // participate in the destroy graph if there are no operations which could 49 // interract with destroy nodes. We need Changes for now to detect the 50 // action type, but perhaps this should be indicated somehow by the 51 // DiffTransformer which was intended to be the only transformer operating 52 // from the change set. 53 Changes *plans.Changes 54 55 // FIXME: Operation will not be needed here one we can better track 56 // inter-provider dependencies and remove the cycle checks in 57 // tryInterProviderDestroyEdge. 58 Operation walkOperation 59 } 60 61 // tryInterProviderDestroyEdge checks if we're inserting a destroy edge 62 // across a provider boundary, and only adds the edge if it results in no cycles. 63 // 64 // FIXME: The cycles can arise in valid configurations when a provider depends 65 // on resources from another provider. In the future we may want to inspect 66 // the dependencies of the providers themselves, to avoid needing to use the 67 // blunt hammer of checking for cycles. 68 // 69 // A reduced example of this dependency problem looks something like: 70 /* 71 72 createA <- createB 73 | \ / | 74 | providerB <- | 75 v \ v 76 destroyA -------------> destroyB 77 78 */ 79 // 80 // The edge from destroyA to destroyB would be skipped in this case, but there 81 // are still other combinations of changes which could connect the A and B 82 // groups around providerB in various ways. 83 // 84 // The most difficult problem here happens during a full destroy operation. 85 // That creates a special case where resources on which a provider depends must 86 // exist for evaluation before they are destroyed. This means that any provider 87 // dependencies must wait until all that provider's resources have first been 88 // destroyed. This is where these cross-provider edges are still required to 89 // ensure the correct order. 90 func (t *DestroyEdgeTransformer) tryInterProviderDestroyEdge(g *Graph, from, to dag.Vertex) { 91 e := dag.BasicEdge(from, to) 92 g.Connect(e) 93 94 // If this is a complete destroy operation, then there are no create/update 95 // nodes to worry about and we can accept the edge without deeper inspection. 96 if t.Operation == walkDestroy || t.Operation == walkPlanDestroy { 97 return 98 } 99 100 // getComparableProvider inspects the node to try and get the most precise 101 // description of the provider being used to help determine if 2 nodes are 102 // from the same provider instance. 103 getComparableProvider := func(pc GraphNodeProviderConsumer) string { 104 ps := pc.Provider().String() 105 106 // we don't care about `exact` here, since we're only looking for any 107 // clue that the providers may differ. 108 p, _ := pc.ProvidedBy() 109 switch p := p.(type) { 110 case addrs.AbsProviderConfig: 111 ps = p.String() 112 case addrs.LocalProviderConfig: 113 ps = p.String() 114 } 115 116 return ps 117 } 118 119 pc, ok := from.(GraphNodeProviderConsumer) 120 if !ok { 121 return 122 } 123 fromProvider := getComparableProvider(pc) 124 125 pc, ok = to.(GraphNodeProviderConsumer) 126 if !ok { 127 return 128 } 129 toProvider := getComparableProvider(pc) 130 131 // Check for cycles, and back out the edge if there are any. 132 // The cycles we are looking for only appears between providers, so don't 133 // waste time checking for cycles if both nodes use the same provider. 134 if fromProvider != toProvider && len(g.Cycles()) > 0 { 135 log.Printf("[DEBUG] DestroyEdgeTransformer: skipping inter-provider edge %s->%s which creates a cycle", 136 dag.VertexName(from), dag.VertexName(to)) 137 g.RemoveEdge(e) 138 } 139 } 140 141 func (t *DestroyEdgeTransformer) Transform(g *Graph) error { 142 // Build a map of what is being destroyed (by address string) to 143 // the list of destroyers. 144 destroyers := make(map[string][]GraphNodeDestroyer) 145 146 // Record the creators, which will need to depend on the destroyers if they 147 // are only being updated. 148 creators := make(map[string][]GraphNodeCreator) 149 150 // destroyersByResource records each destroyer by the ConfigResource 151 // address. We use this because dependencies are only referenced as 152 // resources and have no index or module instance information, but we will 153 // want to connect all the individual instances for correct ordering. 154 destroyersByResource := make(map[string][]GraphNodeDestroyer) 155 for _, v := range g.Vertices() { 156 switch n := v.(type) { 157 case GraphNodeDestroyer: 158 addrP := n.DestroyAddr() 159 if addrP == nil { 160 log.Printf("[WARN] DestroyEdgeTransformer: %q (%T) has no destroy address", dag.VertexName(n), v) 161 continue 162 } 163 addr := *addrP 164 165 key := addr.String() 166 log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(n), v, key) 167 destroyers[key] = append(destroyers[key], n) 168 169 resAddr := addr.ContainingResource().Config().String() 170 destroyersByResource[resAddr] = append(destroyersByResource[resAddr], n) 171 case GraphNodeCreator: 172 addr := n.CreateAddr() 173 cfgAddr := addr.ContainingResource().Config().String() 174 175 if t.Changes == nil { 176 // unit tests may not have changes 177 creators[cfgAddr] = append(creators[cfgAddr], n) 178 break 179 } 180 181 // NoOp changes should not participate in the destroy dependencies. 182 rc := t.Changes.ResourceInstance(*addr) 183 if rc != nil && rc.Action != plans.NoOp { 184 creators[cfgAddr] = append(creators[cfgAddr], n) 185 } 186 } 187 } 188 189 // If we aren't destroying anything, there will be no edges to make 190 // so just exit early and avoid future work. 191 if len(destroyers) == 0 { 192 return nil 193 } 194 195 // Go through and connect creators to destroyers. Going along with 196 // our example, this makes: A_d => A 197 for _, v := range g.Vertices() { 198 cn, ok := v.(GraphNodeCreator) 199 if !ok { 200 continue 201 } 202 203 addr := cn.CreateAddr() 204 if addr == nil { 205 continue 206 } 207 208 for _, d := range destroyers[addr.String()] { 209 // For illustrating our example 210 a_d := d.(dag.Vertex) 211 a := v 212 213 log.Printf( 214 "[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q", 215 dag.VertexName(a), dag.VertexName(a_d)) 216 217 g.Connect(dag.BasicEdge(a, a_d)) 218 } 219 } 220 221 // connect creators to any destroyers on which they may depend 222 for _, cs := range creators { 223 for _, c := range cs { 224 ri, ok := c.(GraphNodeResourceInstance) 225 if !ok { 226 continue 227 } 228 229 for _, resAddr := range ri.StateDependencies() { 230 for _, desDep := range destroyersByResource[resAddr.String()] { 231 if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(c, desDep) { 232 log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(c), dag.VertexName(desDep)) 233 g.Connect(dag.BasicEdge(c, desDep)) 234 } else { 235 log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(c), dag.VertexName(desDep)) 236 } 237 } 238 } 239 } 240 } 241 242 // Connect destroy dependencies as stored in the state 243 for _, ds := range destroyers { 244 for _, des := range ds { 245 ri, ok := des.(GraphNodeResourceInstance) 246 if !ok { 247 continue 248 } 249 250 for _, resAddr := range ri.StateDependencies() { 251 for _, desDep := range destroyersByResource[resAddr.String()] { 252 if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(desDep, des) { 253 log.Printf("[TRACE] DestroyEdgeTransformer: %s has stored dependency of %s\n", dag.VertexName(desDep), dag.VertexName(des)) 254 t.tryInterProviderDestroyEdge(g, desDep, des) 255 } else { 256 log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(desDep), dag.VertexName(des)) 257 } 258 } 259 260 // We can have some create or update nodes which were 261 // dependents of the destroy node. If they have no destroyer 262 // themselves, make the connection directly from the creator. 263 for _, createDep := range creators[resAddr.String()] { 264 if !graphNodesAreResourceInstancesInDifferentInstancesOfSameModule(createDep, des) { 265 log.Printf("[DEBUG] DestroyEdgeTransformer2: %s has stored dependency of %s\n", dag.VertexName(createDep), dag.VertexName(des)) 266 t.tryInterProviderDestroyEdge(g, createDep, des) 267 } else { 268 log.Printf("[TRACE] DestroyEdgeTransformer2: skipping %s => %s inter-module-instance dependency\n", dag.VertexName(createDep), dag.VertexName(des)) 269 } 270 } 271 } 272 } 273 } 274 275 return nil 276 } 277 278 // Remove any nodes that aren't needed when destroying modules. 279 // Variables, outputs, locals, and expanders may not be able to evaluate 280 // correctly, so we can remove these if nothing depends on them. The module 281 // closers also need to disable their use of expansion if the module itself is 282 // no longer present. 283 type pruneUnusedNodesTransformer struct { 284 // The plan graph builder will skip this transformer except during a full 285 // destroy. Planing normally involves all nodes, but during a destroy plan 286 // we may need to prune things which are in the configuration but do not 287 // exist in state to evaluate. 288 skip bool 289 } 290 291 func (t *pruneUnusedNodesTransformer) Transform(g *Graph) error { 292 if t.skip { 293 return nil 294 } 295 296 // We need a reverse depth first walk of modules, processing them in order 297 // from the leaf modules to the root. This allows us to remove unneeded 298 // dependencies from child modules, freeing up nodes in the parent module 299 // to also be removed. 300 301 nodes := g.Vertices() 302 303 for removed := true; removed; { 304 removed = false 305 306 for i := 0; i < len(nodes); i++ { 307 // run this in a closure, so we can return early rather than 308 // dealing with complex looping and labels 309 func() { 310 n := nodes[i] 311 switch n := n.(type) { 312 case graphNodeTemporaryValue: 313 // root module outputs indicate they are not temporary by 314 // returning false here. 315 if !n.temporaryValue() { 316 return 317 } 318 319 // temporary values, which consist of variables, locals, 320 // and outputs, must be kept if anything refers to them. 321 for _, v := range g.UpEdges(n) { 322 // keep any value which is connected through a 323 // reference 324 if _, ok := v.(GraphNodeReferencer); ok { 325 return 326 } 327 } 328 329 case graphNodeExpandsInstances: 330 // Any nodes that expand instances are kept when their 331 // instances may need to be evaluated. 332 for _, v := range g.UpEdges(n) { 333 switch v.(type) { 334 case graphNodeExpandsInstances: 335 // Root module output values (which the following 336 // condition matches) are exempt because we know 337 // there is only ever exactly one instance of the 338 // root module, and so it's not actually important 339 // to expand it and so this lets us do a bit more 340 // pruning than we'd be able to do otherwise. 341 if tmp, ok := v.(graphNodeTemporaryValue); ok && !tmp.temporaryValue() { 342 continue 343 } 344 345 // expanders can always depend on module expansion 346 // themselves 347 return 348 case GraphNodeResourceInstance: 349 // resource instances always depend on their 350 // resource node, which is an expander 351 return 352 } 353 } 354 355 case GraphNodeProvider: 356 // Only keep providers for evaluation if they have 357 // resources to handle. 358 // The provider transformers removed most unused providers 359 // earlier, however there may be more to prune now based on 360 // targeting or a destroy with no related instances in the 361 // state. 362 des, _ := g.Descendents(n) 363 for _, v := range des { 364 switch v.(type) { 365 case GraphNodeProviderConsumer: 366 return 367 } 368 } 369 370 default: 371 return 372 } 373 374 log.Printf("[DEBUG] pruneUnusedNodes: %s is no longer needed, removing", dag.VertexName(n)) 375 g.Remove(n) 376 removed = true 377 378 // remove the node from our iteration as well 379 last := len(nodes) - 1 380 nodes[i], nodes[last] = nodes[last], nodes[i] 381 nodes = nodes[:last] 382 }() 383 } 384 } 385 386 return nil 387 }