github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/terraform/transform_destroy_cbd.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/config/module" 8 "github.com/hashicorp/terraform/dag" 9 ) 10 11 // GraphNodeDestroyerCBD must be implemented by nodes that might be 12 // create-before-destroy destroyers. 13 type GraphNodeDestroyerCBD interface { 14 GraphNodeDestroyer 15 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 // CBDEdgeTransformer modifies the edges of CBD nodes that went through 27 // the DestroyEdgeTransformer to have the right dependencies. There are 28 // two real tasks here: 29 // 30 // 1. With CBD, the destroy edge is inverted: the destroy depends on 31 // the creation. 32 // 33 // 2. A_d must depend on resources that depend on A. This is to enable 34 // the destroy to only happen once nodes that depend on A successfully 35 // update to A. Example: adding a web server updates the load balancer 36 // before deleting the old web server. 37 // 38 type CBDEdgeTransformer struct { 39 // Module and State are only needed to look up dependencies in 40 // any way possible. Either can be nil if not availabile. 41 Module *module.Tree 42 State *State 43 } 44 45 func (t *CBDEdgeTransformer) Transform(g *Graph) error { 46 log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...") 47 48 // Go through and reverse any destroy edges 49 destroyMap := make(map[string][]dag.Vertex) 50 for _, v := range g.Vertices() { 51 dn, ok := v.(GraphNodeDestroyerCBD) 52 if !ok { 53 continue 54 } 55 56 if !dn.CreateBeforeDestroy() { 57 // If there are no CBD ancestors (dependent nodes), then we 58 // do nothing here. 59 if !t.hasCBDAncestor(g, v) { 60 continue 61 } 62 63 // If this isn't naturally a CBD node, this means that an ancestor is 64 // and we need to auto-upgrade this node to CBD. We do this because 65 // a CBD node depending on non-CBD will result in cycles. To avoid this, 66 // we always attempt to upgrade it. 67 if err := dn.ModifyCreateBeforeDestroy(true); err != nil { 68 return fmt.Errorf( 69 "%s: must have create before destroy enabled because "+ 70 "a dependent resource has CBD enabled. However, when "+ 71 "attempting to automatically do this, an error occurred: %s", 72 dag.VertexName(v), err) 73 } 74 } 75 76 // Find the destroy edge. There should only be one. 77 for _, e := range g.EdgesTo(v) { 78 // Not a destroy edge, ignore it 79 de, ok := e.(*DestroyEdge) 80 if !ok { 81 continue 82 } 83 84 log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", 85 dag.VertexName(de.Source()), dag.VertexName(de.Target())) 86 87 // Found it! Invert. 88 g.RemoveEdge(de) 89 g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()}) 90 } 91 92 // If the address has an index, we strip that. Our depMap creation 93 // graph doesn't expand counts so we don't currently get _exact_ 94 // dependencies. One day when we limit dependencies more exactly 95 // this will have to change. We have a test case covering this 96 // (depNonCBDCountBoth) so it'll be caught. 97 addr := dn.DestroyAddr() 98 if addr.Index >= 0 { 99 addr = addr.Copy() // Copy so that we don't modify any pointers 100 addr.Index = -1 101 } 102 103 // Add this to the list of nodes that we need to fix up 104 // the edges for (step 2 above in the docs). 105 key := addr.String() 106 destroyMap[key] = append(destroyMap[key], v) 107 } 108 109 // If we have no CBD nodes, then our work here is done 110 if len(destroyMap) == 0 { 111 return nil 112 } 113 114 // We have CBD nodes. We now have to move on to the much more difficult 115 // task of connecting dependencies of the creation side of the destroy 116 // to the destruction node. The easiest way to explain this is an example: 117 // 118 // Given a pre-destroy dependence of: A => B 119 // And A has CBD set. 120 // 121 // The resulting graph should be: A => B => A_d 122 // 123 // They key here is that B happens before A is destroyed. This is to 124 // facilitate the primary purpose for CBD: making sure that downstreams 125 // are properly updated to avoid downtime before the resource is destroyed. 126 // 127 // We can't trust that the resource being destroyed or anything that 128 // depends on it is actually in our current graph so we make a new 129 // graph in order to determine those dependencies and add them in. 130 log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") 131 depMap, err := t.depMap(destroyMap) 132 if err != nil { 133 return err 134 } 135 136 // We now have the mapping of resource addresses to the destroy 137 // nodes they need to depend on. We now go through our own vertices to 138 // find any matching these addresses and make the connection. 139 for _, v := range g.Vertices() { 140 // We're looking for creators 141 rn, ok := v.(GraphNodeCreator) 142 if !ok { 143 continue 144 } 145 146 // Get the address 147 addr := rn.CreateAddr() 148 149 // If the address has an index, we strip that. Our depMap creation 150 // graph doesn't expand counts so we don't currently get _exact_ 151 // dependencies. One day when we limit dependencies more exactly 152 // this will have to change. We have a test case covering this 153 // (depNonCBDCount) so it'll be caught. 154 if addr.Index >= 0 { 155 addr = addr.Copy() // Copy so that we don't modify any pointers 156 addr.Index = -1 157 } 158 159 // If there is nothing this resource should depend on, ignore it 160 key := addr.String() 161 dns, ok := depMap[key] 162 if !ok { 163 continue 164 } 165 166 // We have nodes! Make the connection 167 for _, dn := range dns { 168 log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", 169 dag.VertexName(dn), dag.VertexName(v)) 170 g.Connect(dag.BasicEdge(dn, v)) 171 } 172 } 173 174 return nil 175 } 176 177 func (t *CBDEdgeTransformer) depMap( 178 destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { 179 // Build the graph of our config, this ensures that all resources 180 // are present in the graph. 181 g, err := (&BasicGraphBuilder{ 182 Steps: []GraphTransformer{ 183 &FlatConfigTransformer{Module: t.Module}, 184 &AttachResourceConfigTransformer{Module: t.Module}, 185 &AttachStateTransformer{State: t.State}, 186 &ReferenceTransformer{}, 187 }, 188 Name: "CBDEdgeTransformer", 189 }).Build(nil) 190 if err != nil { 191 return nil, err 192 } 193 194 // Using this graph, build the list of destroy nodes that each resource 195 // address should depend on. For example, when we find B, we map the 196 // address of B to A_d in the "depMap" variable below. 197 depMap := make(map[string][]dag.Vertex) 198 for _, v := range g.Vertices() { 199 // We're looking for resources. 200 rn, ok := v.(GraphNodeResource) 201 if !ok { 202 continue 203 } 204 205 // Get the address 206 addr := rn.ResourceAddr() 207 key := addr.String() 208 209 // Get the destroy nodes that are destroying this resource. 210 // If there aren't any, then we don't need to worry about 211 // any connections. 212 dns, ok := destroyMap[key] 213 if !ok { 214 continue 215 } 216 217 // Get the nodes that depend on this on. In the example above: 218 // finding B in A => B. 219 for _, v := range g.UpEdges(v).List() { 220 // We're looking for resources. 221 rn, ok := v.(GraphNodeResource) 222 if !ok { 223 continue 224 } 225 226 // Keep track of the destroy nodes that this address 227 // needs to depend on. 228 key := rn.ResourceAddr().String() 229 depMap[key] = append(depMap[key], dns...) 230 } 231 } 232 233 return depMap, nil 234 } 235 236 // hasCBDAncestor returns true if any ancestor (node that depends on this) 237 // has CBD set. 238 func (t *CBDEdgeTransformer) hasCBDAncestor(g *Graph, v dag.Vertex) bool { 239 s, _ := g.Ancestors(v) 240 if s == nil { 241 return true 242 } 243 244 for _, v := range s.List() { 245 dn, ok := v.(GraphNodeDestroyerCBD) 246 if !ok { 247 continue 248 } 249 250 if dn.CreateBeforeDestroy() { 251 // some ancestor is CreateBeforeDestroy, so we need to follow suit 252 return true 253 } 254 } 255 256 return false 257 }