github.com/hugorut/terraform@v1.1.3/src/terraform/transform_destroy_cbd.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hugorut/terraform/src/configs" 8 "github.com/hugorut/terraform/src/dag" 9 "github.com/hugorut/terraform/src/states" 10 ) 11 12 // GraphNodeDestroyerCBD must be implemented by nodes that might be 13 // create-before-destroy destroyers, or might plan a create-before-destroy 14 // action. 15 type GraphNodeDestroyerCBD interface { 16 // CreateBeforeDestroy returns true if this node represents a node 17 // that is doing a CBD. 18 CreateBeforeDestroy() bool 19 20 // ModifyCreateBeforeDestroy is called when the CBD state of a node 21 // is changed dynamically. This can return an error if this isn't 22 // allowed. 23 ModifyCreateBeforeDestroy(bool) error 24 } 25 26 // ForcedCBDTransformer detects when a particular CBD-able graph node has 27 // dependencies with another that has create_before_destroy set that require 28 // it to be forced on, and forces it on. 29 // 30 // This must be used in the plan graph builder to ensure that 31 // create_before_destroy settings are properly propagated before constructing 32 // the planned changes. This requires that the plannable resource nodes 33 // implement GraphNodeDestroyerCBD. 34 type ForcedCBDTransformer struct { 35 } 36 37 func (t *ForcedCBDTransformer) Transform(g *Graph) error { 38 for _, v := range g.Vertices() { 39 dn, ok := v.(GraphNodeDestroyerCBD) 40 if !ok { 41 continue 42 } 43 44 if !dn.CreateBeforeDestroy() { 45 // If there are no CBD decendent (dependent nodes), then we 46 // do nothing here. 47 if !t.hasCBDDescendent(g, v) { 48 log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD descendent, so skipping", dag.VertexName(v), v) 49 continue 50 } 51 52 // If this isn't naturally a CBD node, this means that an descendent is 53 // and we need to auto-upgrade this node to CBD. We do this because 54 // a CBD node depending on non-CBD will result in cycles. To avoid this, 55 // we always attempt to upgrade it. 56 log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v) 57 if err := dn.ModifyCreateBeforeDestroy(true); err != nil { 58 return fmt.Errorf( 59 "%s: must have create before destroy enabled because "+ 60 "a dependent resource has CBD enabled. However, when "+ 61 "attempting to automatically do this, an error occurred: %s", 62 dag.VertexName(v), err) 63 } 64 } else { 65 log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v) 66 } 67 } 68 return nil 69 } 70 71 // hasCBDDescendent returns true if any descendent (node that depends on this) 72 // has CBD set. 73 func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool { 74 s, _ := g.Descendents(v) 75 if s == nil { 76 return true 77 } 78 79 for _, ov := range s { 80 dn, ok := ov.(GraphNodeDestroyerCBD) 81 if !ok { 82 continue 83 } 84 85 if dn.CreateBeforeDestroy() { 86 // some descendent is CreateBeforeDestroy, so we need to follow suit 87 log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov)) 88 return true 89 } 90 } 91 92 return false 93 } 94 95 // CBDEdgeTransformer modifies the edges of CBD nodes that went through 96 // the DestroyEdgeTransformer to have the right dependencies. There are 97 // two real tasks here: 98 // 99 // 1. With CBD, the destroy edge is inverted: the destroy depends on 100 // the creation. 101 // 102 // 2. A_d must depend on resources that depend on A. This is to enable 103 // the destroy to only happen once nodes that depend on A successfully 104 // update to A. Example: adding a web server updates the load balancer 105 // before deleting the old web server. 106 // 107 // This transformer requires that a previous transformer has already forced 108 // create_before_destroy on for nodes that are depended on by explicit CBD 109 // nodes. This is the logic in ForcedCBDTransformer, though in practice we 110 // will get here by recording the CBD-ness of each change in the plan during 111 // the plan walk and then forcing the nodes into the appropriate setting during 112 // DiffTransformer when building the apply graph. 113 type CBDEdgeTransformer struct { 114 // Module and State are only needed to look up dependencies in 115 // any way possible. Either can be nil if not availabile. 116 Config *configs.Config 117 State *states.State 118 } 119 120 func (t *CBDEdgeTransformer) Transform(g *Graph) error { 121 // Go through and reverse any destroy edges 122 for _, v := range g.Vertices() { 123 dn, ok := v.(GraphNodeDestroyerCBD) 124 if !ok { 125 continue 126 } 127 if _, ok = v.(GraphNodeDestroyer); !ok { 128 continue 129 } 130 131 if !dn.CreateBeforeDestroy() { 132 continue 133 } 134 135 // Find the resource edges 136 for _, e := range g.EdgesTo(v) { 137 src := e.Source() 138 139 // If source is a create node, invert the edge. 140 // This covers both the node's own creator, as well as reversing 141 // any dependants' edges. 142 if _, ok := src.(GraphNodeCreator); ok { 143 log.Printf("[TRACE] CBDEdgeTransformer: reversing edge %s -> %s", dag.VertexName(src), dag.VertexName(v)) 144 g.RemoveEdge(e) 145 g.Connect(dag.BasicEdge(v, src)) 146 } 147 } 148 } 149 return nil 150 }