github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/transform_destroy_edge.go (about)

     1  package terraform
     2  
     3  import (
     4  	"log"
     5  
     6  	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
     7  	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
     8  
     9  	"github.com/hashicorp/terraform-plugin-sdk/internal/configs"
    10  	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
    11  )
    12  
    13  // GraphNodeDestroyer must be implemented by nodes that destroy resources.
    14  type GraphNodeDestroyer interface {
    15  	dag.Vertex
    16  
    17  	// DestroyAddr is the address of the resource that is being
    18  	// destroyed by this node. If this returns nil, then this node
    19  	// is not destroying anything.
    20  	DestroyAddr() *addrs.AbsResourceInstance
    21  }
    22  
    23  // GraphNodeCreator must be implemented by nodes that create OR update resources.
    24  type GraphNodeCreator interface {
    25  	// CreateAddr is the address of the resource being created or updated
    26  	CreateAddr() *addrs.AbsResourceInstance
    27  }
    28  
    29  // DestroyEdgeTransformer is a GraphTransformer that creates the proper
    30  // references for destroy resources. Destroy resources are more complex
    31  // in that they must be depend on the destruction of resources that
    32  // in turn depend on the CREATION of the node being destroy.
    33  //
    34  // That is complicated. Visually:
    35  //
    36  //   B_d -> A_d -> A -> B
    37  //
    38  // Notice that A destroy depends on B destroy, while B create depends on
    39  // A create. They're inverted. This must be done for example because often
    40  // dependent resources will block parent resources from deleting. Concrete
    41  // example: VPC with subnets, the VPC can't be deleted while there are
    42  // still subnets.
    43  type DestroyEdgeTransformer struct {
    44  	// These are needed to properly build the graph of dependencies
    45  	// to determine what a destroy node depends on. Any of these can be nil.
    46  	Config *configs.Config
    47  	State  *states.State
    48  
    49  	// If configuration is present then Schemas is required in order to
    50  	// obtain schema information from providers and provisioners in order
    51  	// to properly resolve implicit dependencies.
    52  	Schemas *Schemas
    53  }
    54  
    55  func (t *DestroyEdgeTransformer) Transform(g *Graph) error {
    56  	// Build a map of what is being destroyed (by address string) to
    57  	// the list of destroyers. Usually there will be at most one destroyer
    58  	// per node, but we allow multiple if present for completeness.
    59  	destroyers := make(map[string][]GraphNodeDestroyer)
    60  	destroyerAddrs := make(map[string]addrs.AbsResourceInstance)
    61  	for _, v := range g.Vertices() {
    62  		dn, ok := v.(GraphNodeDestroyer)
    63  		if !ok {
    64  			continue
    65  		}
    66  
    67  		addrP := dn.DestroyAddr()
    68  		if addrP == nil {
    69  			continue
    70  		}
    71  		addr := *addrP
    72  
    73  		key := addr.String()
    74  		log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key)
    75  		destroyers[key] = append(destroyers[key], dn)
    76  		destroyerAddrs[key] = addr
    77  	}
    78  
    79  	// If we aren't destroying anything, there will be no edges to make
    80  	// so just exit early and avoid future work.
    81  	if len(destroyers) == 0 {
    82  		return nil
    83  	}
    84  
    85  	// Go through and connect creators to destroyers. Going along with
    86  	// our example, this makes: A_d => A
    87  	for _, v := range g.Vertices() {
    88  		cn, ok := v.(GraphNodeCreator)
    89  		if !ok {
    90  			continue
    91  		}
    92  
    93  		addr := cn.CreateAddr()
    94  		if addr == nil {
    95  			continue
    96  		}
    97  
    98  		key := addr.String()
    99  		ds := destroyers[key]
   100  		if len(ds) == 0 {
   101  			continue
   102  		}
   103  
   104  		for _, d := range ds {
   105  			// For illustrating our example
   106  			a_d := d.(dag.Vertex)
   107  			a := v
   108  
   109  			log.Printf(
   110  				"[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q",
   111  				dag.VertexName(a), dag.VertexName(a_d))
   112  
   113  			g.Connect(&DestroyEdge{S: a, T: a_d})
   114  
   115  			// Attach the destroy node to the creator
   116  			// There really shouldn't be more than one destroyer, but even if
   117  			// there are, any of them will represent the correct
   118  			// CreateBeforeDestroy status.
   119  			if n, ok := cn.(GraphNodeAttachDestroyer); ok {
   120  				if d, ok := d.(GraphNodeDestroyerCBD); ok {
   121  					n.AttachDestroyNode(d)
   122  				}
   123  			}
   124  		}
   125  	}
   126  
   127  	// This is strange but is the easiest way to get the dependencies
   128  	// of a node that is being destroyed. We use another graph to make sure
   129  	// the resource is in the graph and ask for references. We have to do this
   130  	// because the node that is being destroyed may NOT be in the graph.
   131  	//
   132  	// Example: resource A is force new, then destroy A AND create A are
   133  	// in the graph. BUT if resource A is just pure destroy, then only
   134  	// destroy A is in the graph, and create A is not.
   135  	providerFn := func(a *NodeAbstractProvider) dag.Vertex {
   136  		return &NodeApplyableProvider{NodeAbstractProvider: a}
   137  	}
   138  	steps := []GraphTransformer{
   139  		// Add the local values
   140  		&LocalTransformer{Config: t.Config},
   141  
   142  		// Add outputs and metadata
   143  		&OutputTransformer{Config: t.Config},
   144  		&AttachResourceConfigTransformer{Config: t.Config},
   145  		&AttachStateTransformer{State: t.State},
   146  
   147  		// Add all the variables. We can depend on resources through
   148  		// variables due to module parameters, and we need to properly
   149  		// determine that.
   150  		&RootVariableTransformer{Config: t.Config},
   151  		&ModuleVariableTransformer{Config: t.Config},
   152  
   153  		TransformProviders(nil, providerFn, t.Config),
   154  
   155  		// Must attach schemas before ReferenceTransformer so that we can
   156  		// analyze the configuration to find references.
   157  		&AttachSchemaTransformer{Schemas: t.Schemas},
   158  
   159  		&ReferenceTransformer{},
   160  	}
   161  
   162  	// Go through all the nodes being destroyed and create a graph.
   163  	// The resulting graph is only of things being CREATED. For example,
   164  	// following our example, the resulting graph would be:
   165  	//
   166  	//   A, B (with no edges)
   167  	//
   168  	var tempG Graph
   169  	var tempDestroyed []dag.Vertex
   170  	for d := range destroyers {
   171  		// d is the string key for the resource being destroyed. We actually
   172  		// want the address value, which we stashed earlier.
   173  		addr := destroyerAddrs[d]
   174  
   175  		// This part is a little bit weird but is the best way to
   176  		// find the dependencies we need to: build a graph and use the
   177  		// attach config and state transformers then ask for references.
   178  		abstract := NewNodeAbstractResourceInstance(addr)
   179  		tempG.Add(abstract)
   180  		tempDestroyed = append(tempDestroyed, abstract)
   181  
   182  		// We also add the destroy version here since the destroy can
   183  		// depend on things that the creation doesn't (destroy provisioners).
   184  		destroy := &NodeDestroyResourceInstance{NodeAbstractResourceInstance: abstract}
   185  		tempG.Add(destroy)
   186  		tempDestroyed = append(tempDestroyed, destroy)
   187  	}
   188  
   189  	// Run the graph transforms so we have the information we need to
   190  	// build references.
   191  	log.Printf("[TRACE] DestroyEdgeTransformer: constructing temporary graph for analysis of references, starting from:\n%s", tempG.StringWithNodeTypes())
   192  	for _, s := range steps {
   193  		log.Printf("[TRACE] DestroyEdgeTransformer: running %T on temporary graph", s)
   194  		if err := s.Transform(&tempG); err != nil {
   195  			log.Printf("[TRACE] DestroyEdgeTransformer: %T failed: %s", s, err)
   196  			return err
   197  		}
   198  	}
   199  	log.Printf("[TRACE] DestroyEdgeTransformer: temporary reference graph:\n%s", tempG.String())
   200  
   201  	// Go through all the nodes in the graph and determine what they
   202  	// depend on.
   203  	for _, v := range tempDestroyed {
   204  		// Find all ancestors of this to determine the edges we'll depend on
   205  		vs, err := tempG.Ancestors(v)
   206  		if err != nil {
   207  			return err
   208  		}
   209  
   210  		refs := make([]dag.Vertex, 0, vs.Len())
   211  		for _, raw := range vs.List() {
   212  			refs = append(refs, raw.(dag.Vertex))
   213  		}
   214  
   215  		refNames := make([]string, len(refs))
   216  		for i, ref := range refs {
   217  			refNames[i] = dag.VertexName(ref)
   218  		}
   219  		log.Printf(
   220  			"[TRACE] DestroyEdgeTransformer: creation node %q references %s",
   221  			dag.VertexName(v), refNames)
   222  
   223  		// If we have no references, then we won't need to do anything
   224  		if len(refs) == 0 {
   225  			continue
   226  		}
   227  
   228  		// Get the destroy node for this. In the example of our struct,
   229  		// we are currently at B and we're looking for B_d.
   230  		rn, ok := v.(GraphNodeResourceInstance)
   231  		if !ok {
   232  			log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s, since it's not a resource", dag.VertexName(v))
   233  			continue
   234  		}
   235  
   236  		addr := rn.ResourceInstanceAddr()
   237  		dns := destroyers[addr.String()]
   238  
   239  		// We have dependencies, check if any are being destroyed
   240  		// to build the list of things that we must depend on!
   241  		//
   242  		// In the example of the struct, if we have:
   243  		//
   244  		//   B_d => A_d => A => B
   245  		//
   246  		// Then at this point in the algorithm we started with B_d,
   247  		// we built B (to get dependencies), and we found A. We're now looking
   248  		// to see if A_d exists.
   249  		var depDestroyers []dag.Vertex
   250  		for _, v := range refs {
   251  			rn, ok := v.(GraphNodeResourceInstance)
   252  			if !ok {
   253  				continue
   254  			}
   255  
   256  			addr := rn.ResourceInstanceAddr()
   257  			key := addr.String()
   258  			if ds, ok := destroyers[key]; ok {
   259  				for _, d := range ds {
   260  					depDestroyers = append(depDestroyers, d.(dag.Vertex))
   261  					log.Printf(
   262  						"[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s",
   263  						key, dag.VertexName(d))
   264  				}
   265  			}
   266  		}
   267  
   268  		// Go through and make the connections. Use the variable
   269  		// names "a_d" and "b_d" to reference our example.
   270  		for _, a_d := range dns {
   271  			for _, b_d := range depDestroyers {
   272  				if b_d != a_d {
   273  					log.Printf("[TRACE] DestroyEdgeTransformer: %q depends on %q", dag.VertexName(b_d), dag.VertexName(a_d))
   274  					g.Connect(dag.BasicEdge(b_d, a_d))
   275  				}
   276  			}
   277  		}
   278  	}
   279  
   280  	return t.pruneResources(g)
   281  }
   282  
   283  // If there are only destroy instances for a particular resource, there's no
   284  // reason for the resource node to prepare the state. Remove Resource nodes so
   285  // that they don't fail by trying to evaluate a resource that is only being
   286  // destroyed along with its dependencies.
   287  func (t *DestroyEdgeTransformer) pruneResources(g *Graph) error {
   288  	for _, v := range g.Vertices() {
   289  		n, ok := v.(*NodeApplyableResource)
   290  		if !ok {
   291  			continue
   292  		}
   293  
   294  		// if there are only destroy dependencies, we don't need this node
   295  		des, err := g.Descendents(n)
   296  		if err != nil {
   297  			return err
   298  		}
   299  
   300  		descendents := des.List()
   301  		nonDestroyInstanceFound := false
   302  		for _, v := range descendents {
   303  			if _, ok := v.(*NodeApplyableResourceInstance); ok {
   304  				nonDestroyInstanceFound = true
   305  				break
   306  			}
   307  		}
   308  
   309  		if nonDestroyInstanceFound {
   310  			continue
   311  		}
   312  
   313  		// connect all the through-edges, then delete the node
   314  		for _, d := range g.DownEdges(n).List() {
   315  			for _, u := range g.UpEdges(n).List() {
   316  				g.Connect(dag.BasicEdge(u, d))
   317  			}
   318  		}
   319  		log.Printf("DestroyEdgeTransformer: pruning unused resource node %s", dag.VertexName(n))
   320  		g.Remove(n)
   321  	}
   322  	return nil
   323  }