package terraform import ( "fmt" "log" "github.com/hashicorp/terraform/configs" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/states" ) // GraphNodeDestroyerCBD must be implemented by nodes that might be // create-before-destroy destroyers, or might plan a create-before-destroy // action. type GraphNodeDestroyerCBD interface { // CreateBeforeDestroy returns true if this node represents a node // that is doing a CBD. CreateBeforeDestroy() bool // ModifyCreateBeforeDestroy is called when the CBD state of a node // is changed dynamically. This can return an error if this isn't // allowed. ModifyCreateBeforeDestroy(bool) error } // GraphNodeAttachDestroyer is implemented by applyable nodes that have a // companion destroy node. This allows the creation node to look up the status // of the destroy node and determine if it needs to depose the existing state, // or replace it. // If a node is not marked as create-before-destroy in the configuration, but a // dependency forces that status, only the destroy node will be aware of that // status. type GraphNodeAttachDestroyer interface { // AttachDestroyNode takes a destroy node and saves a reference to that // node in the receiver, so it can later check the status of // CreateBeforeDestroy(). AttachDestroyNode(n GraphNodeDestroyerCBD) } // ForcedCBDTransformer detects when a particular CBD-able graph node has // dependencies with another that has create_before_destroy set that require // it to be forced on, and forces it on. // // This must be used in the plan graph builder to ensure that // create_before_destroy settings are properly propagated before constructing // the planned changes. This requires that the plannable resource nodes // implement GraphNodeDestroyerCBD. type ForcedCBDTransformer struct { } func (t *ForcedCBDTransformer) Transform(g *Graph) error { for _, v := range g.Vertices() { dn, ok := v.(GraphNodeDestroyerCBD) if !ok { continue } if !dn.CreateBeforeDestroy() { // If there are no CBD decendent (dependent nodes), then we // do nothing here. if !t.hasCBDDescendent(g, v) { log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD descendent, so skipping", dag.VertexName(v), v) continue } // If this isn't naturally a CBD node, this means that an descendent is // and we need to auto-upgrade this node to CBD. We do this because // a CBD node depending on non-CBD will result in cycles. To avoid this, // we always attempt to upgrade it. log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v) if err := dn.ModifyCreateBeforeDestroy(true); err != nil { return fmt.Errorf( "%s: must have create before destroy enabled because "+ "a dependent resource has CBD enabled. However, when "+ "attempting to automatically do this, an error occurred: %s", dag.VertexName(v), err) } } else { log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v) } } return nil } // hasCBDDescendent returns true if any descendent (node that depends on this) // has CBD set. func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool { s, _ := g.Descendents(v) if s == nil { return true } for _, ov := range s.List() { dn, ok := ov.(GraphNodeDestroyerCBD) if !ok { continue } if dn.CreateBeforeDestroy() { // some descendent is CreateBeforeDestroy, so we need to follow suit log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov)) return true } } return false } // CBDEdgeTransformer modifies the edges of CBD nodes that went through // the DestroyEdgeTransformer to have the right dependencies. There are // two real tasks here: // // 1. With CBD, the destroy edge is inverted: the destroy depends on // the creation. // // 2. A_d must depend on resources that depend on A. This is to enable // the destroy to only happen once nodes that depend on A successfully // update to A. Example: adding a web server updates the load balancer // before deleting the old web server. // // This transformer requires that a previous transformer has already forced // create_before_destroy on for nodes that are depended on by explicit CBD // nodes. This is the logic in ForcedCBDTransformer, though in practice we // will get here by recording the CBD-ness of each change in the plan during // the plan walk and then forcing the nodes into the appropriate setting during // DiffTransformer when building the apply graph. type CBDEdgeTransformer struct { // Module and State are only needed to look up dependencies in // any way possible. Either can be nil if not availabile. Config *configs.Config State *states.State // If configuration is present then Schemas is required in order to // obtain schema information from providers and provisioners so we can // properly resolve implicit dependencies. Schemas *Schemas } func (t *CBDEdgeTransformer) Transform(g *Graph) error { // Go through and reverse any destroy edges destroyMap := make(map[string][]dag.Vertex) for _, v := range g.Vertices() { dn, ok := v.(GraphNodeDestroyerCBD) if !ok { continue } dern, ok := v.(GraphNodeDestroyer) if !ok { continue } if !dn.CreateBeforeDestroy() { continue } // Find the destroy edge. There should only be one. for _, e := range g.EdgesTo(v) { // Not a destroy edge, ignore it de, ok := e.(*DestroyEdge) if !ok { continue } log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", dag.VertexName(de.Source()), dag.VertexName(de.Target())) // Found it! Invert. g.RemoveEdge(de) applyNode := de.Source() destroyNode := de.Target() g.Connect(&DestroyEdge{S: destroyNode, T: applyNode}) } // If the address has an index, we strip that. Our depMap creation // graph doesn't expand counts so we don't currently get _exact_ // dependencies. One day when we limit dependencies more exactly // this will have to change. We have a test case covering this // (depNonCBDCountBoth) so it'll be caught. addr := dern.DestroyAddr() key := addr.ContainingResource().String() // Add this to the list of nodes that we need to fix up // the edges for (step 2 above in the docs). destroyMap[key] = append(destroyMap[key], v) } // If we have no CBD nodes, then our work here is done if len(destroyMap) == 0 { return nil } // We have CBD nodes. We now have to move on to the much more difficult // task of connecting dependencies of the creation side of the destroy // to the destruction node. The easiest way to explain this is an example: // // Given a pre-destroy dependence of: A => B // And A has CBD set. // // The resulting graph should be: A => B => A_d // // They key here is that B happens before A is destroyed. This is to // facilitate the primary purpose for CBD: making sure that downstreams // are properly updated to avoid downtime before the resource is destroyed. // // We can't trust that the resource being destroyed or anything that // depends on it is actually in our current graph so we make a new // graph in order to determine those dependencies and add them in. log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") depMap, err := t.depMap(destroyMap) if err != nil { return err } // We now have the mapping of resource addresses to the destroy // nodes they need to depend on. We now go through our own vertices to // find any matching these addresses and make the connection. for _, v := range g.Vertices() { // We're looking for creators rn, ok := v.(GraphNodeCreator) if !ok { continue } // Get the address addr := rn.CreateAddr() // If the address has an index, we strip that. Our depMap creation // graph doesn't expand counts so we don't currently get _exact_ // dependencies. One day when we limit dependencies more exactly // this will have to change. We have a test case covering this // (depNonCBDCount) so it'll be caught. key := addr.ContainingResource().String() // If there is nothing this resource should depend on, ignore it dns, ok := depMap[key] if !ok { continue } // We have nodes! Make the connection for _, dn := range dns { log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", dag.VertexName(dn), dag.VertexName(v)) g.Connect(dag.BasicEdge(dn, v)) } } return nil } func (t *CBDEdgeTransformer) depMap(destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { // Build the graph of our config, this ensures that all resources // are present in the graph. g, diags := (&BasicGraphBuilder{ Steps: []GraphTransformer{ &FlatConfigTransformer{Config: t.Config}, &AttachResourceConfigTransformer{Config: t.Config}, &AttachStateTransformer{State: t.State}, &AttachSchemaTransformer{Schemas: t.Schemas}, &ReferenceTransformer{}, }, Name: "CBDEdgeTransformer", }).Build(nil) if diags.HasErrors() { return nil, diags.Err() } // Using this graph, build the list of destroy nodes that each resource // address should depend on. For example, when we find B, we map the // address of B to A_d in the "depMap" variable below. depMap := make(map[string][]dag.Vertex) for _, v := range g.Vertices() { // We're looking for resources. rn, ok := v.(GraphNodeResource) if !ok { continue } // Get the address addr := rn.ResourceAddr() key := addr.String() // Get the destroy nodes that are destroying this resource. // If there aren't any, then we don't need to worry about // any connections. dns, ok := destroyMap[key] if !ok { continue } // Get the nodes that depend on this on. In the example above: // finding B in A => B. for _, v := range g.UpEdges(v).List() { // We're looking for resources. rn, ok := v.(GraphNodeResource) if !ok { continue } // Keep track of the destroy nodes that this address // needs to depend on. key := rn.ResourceAddr().String() depMap[key] = append(depMap[key], dns...) } } return depMap, nil }