package terraform import ( "log" "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" "github.com/hashicorp/terraform-plugin-sdk/internal/states" "github.com/hashicorp/terraform-plugin-sdk/internal/configs" "github.com/hashicorp/terraform-plugin-sdk/internal/dag" ) // GraphNodeDestroyer must be implemented by nodes that destroy resources. type GraphNodeDestroyer interface { dag.Vertex // DestroyAddr is the address of the resource that is being // destroyed by this node. If this returns nil, then this node // is not destroying anything. DestroyAddr() *addrs.AbsResourceInstance } // GraphNodeCreator must be implemented by nodes that create OR update resources. type GraphNodeCreator interface { // CreateAddr is the address of the resource being created or updated CreateAddr() *addrs.AbsResourceInstance } // DestroyEdgeTransformer is a GraphTransformer that creates the proper // references for destroy resources. Destroy resources are more complex // in that they must be depend on the destruction of resources that // in turn depend on the CREATION of the node being destroy. // // That is complicated. Visually: // // B_d -> A_d -> A -> B // // Notice that A destroy depends on B destroy, while B create depends on // A create. They're inverted. This must be done for example because often // dependent resources will block parent resources from deleting. Concrete // example: VPC with subnets, the VPC can't be deleted while there are // still subnets. type DestroyEdgeTransformer struct { // These are needed to properly build the graph of dependencies // to determine what a destroy node depends on. Any of these can be nil. Config *configs.Config State *states.State // If configuration is present then Schemas is required in order to // obtain schema information from providers and provisioners in order // to properly resolve implicit dependencies. Schemas *Schemas } func (t *DestroyEdgeTransformer) Transform(g *Graph) error { // Build a map of what is being destroyed (by address string) to // the list of destroyers. Usually there will be at most one destroyer // per node, but we allow multiple if present for completeness. destroyers := make(map[string][]GraphNodeDestroyer) destroyerAddrs := make(map[string]addrs.AbsResourceInstance) for _, v := range g.Vertices() { dn, ok := v.(GraphNodeDestroyer) if !ok { continue } addrP := dn.DestroyAddr() if addrP == nil { continue } addr := *addrP key := addr.String() log.Printf("[TRACE] DestroyEdgeTransformer: %q (%T) destroys %s", dag.VertexName(dn), v, key) destroyers[key] = append(destroyers[key], dn) destroyerAddrs[key] = addr } // If we aren't destroying anything, there will be no edges to make // so just exit early and avoid future work. if len(destroyers) == 0 { return nil } // Go through and connect creators to destroyers. Going along with // our example, this makes: A_d => A for _, v := range g.Vertices() { cn, ok := v.(GraphNodeCreator) if !ok { continue } addr := cn.CreateAddr() if addr == nil { continue } key := addr.String() ds := destroyers[key] if len(ds) == 0 { continue } for _, d := range ds { // For illustrating our example a_d := d.(dag.Vertex) a := v log.Printf( "[TRACE] DestroyEdgeTransformer: connecting creator %q with destroyer %q", dag.VertexName(a), dag.VertexName(a_d)) g.Connect(&DestroyEdge{S: a, T: a_d}) // Attach the destroy node to the creator // There really shouldn't be more than one destroyer, but even if // there are, any of them will represent the correct // CreateBeforeDestroy status. if n, ok := cn.(GraphNodeAttachDestroyer); ok { if d, ok := d.(GraphNodeDestroyerCBD); ok { n.AttachDestroyNode(d) } } } } // This is strange but is the easiest way to get the dependencies // of a node that is being destroyed. We use another graph to make sure // the resource is in the graph and ask for references. We have to do this // because the node that is being destroyed may NOT be in the graph. // // Example: resource A is force new, then destroy A AND create A are // in the graph. BUT if resource A is just pure destroy, then only // destroy A is in the graph, and create A is not. providerFn := func(a *NodeAbstractProvider) dag.Vertex { return &NodeApplyableProvider{NodeAbstractProvider: a} } steps := []GraphTransformer{ // Add the local values &LocalTransformer{Config: t.Config}, // Add outputs and metadata &OutputTransformer{Config: t.Config}, &AttachResourceConfigTransformer{Config: t.Config}, &AttachStateTransformer{State: t.State}, // Add all the variables. We can depend on resources through // variables due to module parameters, and we need to properly // determine that. &RootVariableTransformer{Config: t.Config}, &ModuleVariableTransformer{Config: t.Config}, TransformProviders(nil, providerFn, t.Config), // Must attach schemas before ReferenceTransformer so that we can // analyze the configuration to find references. &AttachSchemaTransformer{Schemas: t.Schemas}, &ReferenceTransformer{}, } // Go through all the nodes being destroyed and create a graph. // The resulting graph is only of things being CREATED. For example, // following our example, the resulting graph would be: // // A, B (with no edges) // var tempG Graph var tempDestroyed []dag.Vertex for d := range destroyers { // d is the string key for the resource being destroyed. We actually // want the address value, which we stashed earlier. addr := destroyerAddrs[d] // This part is a little bit weird but is the best way to // find the dependencies we need to: build a graph and use the // attach config and state transformers then ask for references. abstract := NewNodeAbstractResourceInstance(addr) tempG.Add(abstract) tempDestroyed = append(tempDestroyed, abstract) // We also add the destroy version here since the destroy can // depend on things that the creation doesn't (destroy provisioners). destroy := &NodeDestroyResourceInstance{NodeAbstractResourceInstance: abstract} tempG.Add(destroy) tempDestroyed = append(tempDestroyed, destroy) } // Run the graph transforms so we have the information we need to // build references. log.Printf("[TRACE] DestroyEdgeTransformer: constructing temporary graph for analysis of references, starting from:\n%s", tempG.StringWithNodeTypes()) for _, s := range steps { log.Printf("[TRACE] DestroyEdgeTransformer: running %T on temporary graph", s) if err := s.Transform(&tempG); err != nil { log.Printf("[TRACE] DestroyEdgeTransformer: %T failed: %s", s, err) return err } } log.Printf("[TRACE] DestroyEdgeTransformer: temporary reference graph:\n%s", tempG.String()) // Go through all the nodes in the graph and determine what they // depend on. for _, v := range tempDestroyed { // Find all ancestors of this to determine the edges we'll depend on vs, err := tempG.Ancestors(v) if err != nil { return err } refs := make([]dag.Vertex, 0, vs.Len()) for _, raw := range vs.List() { refs = append(refs, raw.(dag.Vertex)) } refNames := make([]string, len(refs)) for i, ref := range refs { refNames[i] = dag.VertexName(ref) } log.Printf( "[TRACE] DestroyEdgeTransformer: creation node %q references %s", dag.VertexName(v), refNames) // If we have no references, then we won't need to do anything if len(refs) == 0 { continue } // Get the destroy node for this. In the example of our struct, // we are currently at B and we're looking for B_d. rn, ok := v.(GraphNodeResourceInstance) if !ok { log.Printf("[TRACE] DestroyEdgeTransformer: skipping %s, since it's not a resource", dag.VertexName(v)) continue } addr := rn.ResourceInstanceAddr() dns := destroyers[addr.String()] // We have dependencies, check if any are being destroyed // to build the list of things that we must depend on! // // In the example of the struct, if we have: // // B_d => A_d => A => B // // Then at this point in the algorithm we started with B_d, // we built B (to get dependencies), and we found A. We're now looking // to see if A_d exists. var depDestroyers []dag.Vertex for _, v := range refs { rn, ok := v.(GraphNodeResourceInstance) if !ok { continue } addr := rn.ResourceInstanceAddr() key := addr.String() if ds, ok := destroyers[key]; ok { for _, d := range ds { depDestroyers = append(depDestroyers, d.(dag.Vertex)) log.Printf( "[TRACE] DestroyEdgeTransformer: destruction of %q depends on %s", key, dag.VertexName(d)) } } } // Go through and make the connections. Use the variable // names "a_d" and "b_d" to reference our example. for _, a_d := range dns { for _, b_d := range depDestroyers { if b_d != a_d { log.Printf("[TRACE] DestroyEdgeTransformer: %q depends on %q", dag.VertexName(b_d), dag.VertexName(a_d)) g.Connect(dag.BasicEdge(b_d, a_d)) } } } } return nil }