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  }