github.com/mehmetalisavas/terraform@v0.7.10/terraform/transform_destroy_cbd.go (about) 1 package terraform 2 3 import ( 4 "log" 5 6 "github.com/hashicorp/terraform/config/module" 7 "github.com/hashicorp/terraform/dag" 8 ) 9 10 // GraphNodeDestroyerCBD must be implemented by nodes that might be 11 // create-before-destroy destroyers. 12 type GraphNodeDestroyerCBD interface { 13 GraphNodeDestroyer 14 15 // CreateBeforeDestroy returns true if this node represents a node 16 // that is doing a CBD. 17 CreateBeforeDestroy() bool 18 } 19 20 // CBDEdgeTransformer modifies the edges of CBD nodes that went through 21 // the DestroyEdgeTransformer to have the right dependencies. There are 22 // two real tasks here: 23 // 24 // 1. With CBD, the destroy edge is inverted: the destroy depends on 25 // the creation. 26 // 27 // 2. A_d must depend on resources that depend on A. This is to enable 28 // the destroy to only happen once nodes that depend on A successfully 29 // update to A. Example: adding a web server updates the load balancer 30 // before deleting the old web server. 31 // 32 type CBDEdgeTransformer struct { 33 // Module and State are only needed to look up dependencies in 34 // any way possible. Either can be nil if not availabile. 35 Module *module.Tree 36 State *State 37 } 38 39 func (t *CBDEdgeTransformer) Transform(g *Graph) error { 40 log.Printf("[TRACE] CBDEdgeTransformer: Beginning CBD transformation...") 41 42 // Go through and reverse any destroy edges 43 destroyMap := make(map[string][]dag.Vertex) 44 for _, v := range g.Vertices() { 45 dn, ok := v.(GraphNodeDestroyerCBD) 46 if !ok { 47 continue 48 } 49 50 if !dn.CreateBeforeDestroy() { 51 continue 52 } 53 54 // Find the destroy edge. There should only be one. 55 for _, e := range g.EdgesTo(v) { 56 // Not a destroy edge, ignore it 57 de, ok := e.(*DestroyEdge) 58 if !ok { 59 continue 60 } 61 62 log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s", 63 dag.VertexName(de.Source()), dag.VertexName(de.Target())) 64 65 // Found it! Invert. 66 g.RemoveEdge(de) 67 g.Connect(&DestroyEdge{S: de.Target(), T: de.Source()}) 68 } 69 70 // Add this to the list of nodes that we need to fix up 71 // the edges for (step 2 above in the docs). 72 key := dn.DestroyAddr().String() 73 destroyMap[key] = append(destroyMap[key], v) 74 } 75 76 // If we have no CBD nodes, then our work here is done 77 if len(destroyMap) == 0 { 78 return nil 79 } 80 81 // We have CBD nodes. We now have to move on to the much more difficult 82 // task of connecting dependencies of the creation side of the destroy 83 // to the destruction node. The easiest way to explain this is an example: 84 // 85 // Given a pre-destroy dependence of: A => B 86 // And A has CBD set. 87 // 88 // The resulting graph should be: A => B => A_d 89 // 90 // They key here is that B happens before A is destroyed. This is to 91 // facilitate the primary purpose for CBD: making sure that downstreams 92 // are properly updated to avoid downtime before the resource is destroyed. 93 // 94 // We can't trust that the resource being destroyed or anything that 95 // depends on it is actually in our current graph so we make a new 96 // graph in order to determine those dependencies and add them in. 97 log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...") 98 depMap, err := t.depMap(destroyMap) 99 if err != nil { 100 return err 101 } 102 103 // We now have the mapping of resource addresses to the destroy 104 // nodes they need to depend on. We now go through our own vertices to 105 // find any matching these addresses and make the connection. 106 for _, v := range g.Vertices() { 107 // We're looking for creators 108 rn, ok := v.(GraphNodeCreator) 109 if !ok { 110 continue 111 } 112 113 // Get the address 114 addr := rn.CreateAddr() 115 key := addr.String() 116 117 // If there is nothing this resource should depend on, ignore it 118 dns, ok := depMap[key] 119 if !ok { 120 continue 121 } 122 123 // We have nodes! Make the connection 124 for _, dn := range dns { 125 log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s", 126 dag.VertexName(dn), dag.VertexName(v)) 127 g.Connect(dag.BasicEdge(dn, v)) 128 } 129 } 130 131 return nil 132 } 133 134 func (t *CBDEdgeTransformer) depMap( 135 destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) { 136 // Build the graph of our config, this ensures that all resources 137 // are present in the graph. 138 g, err := (&BasicGraphBuilder{ 139 Steps: []GraphTransformer{ 140 &FlatConfigTransformer{Module: t.Module}, 141 &AttachResourceConfigTransformer{Module: t.Module}, 142 &AttachStateTransformer{State: t.State}, 143 &ReferenceTransformer{}, 144 }, 145 }).Build(nil) 146 if err != nil { 147 return nil, err 148 } 149 150 // Using this graph, build the list of destroy nodes that each resource 151 // address should depend on. For example, when we find B, we map the 152 // address of B to A_d in the "depMap" variable below. 153 depMap := make(map[string][]dag.Vertex) 154 for _, v := range g.Vertices() { 155 // We're looking for resources. 156 rn, ok := v.(GraphNodeResource) 157 if !ok { 158 continue 159 } 160 161 // Get the address 162 addr := rn.ResourceAddr() 163 key := addr.String() 164 165 // Get the destroy nodes that are destroying this resource. 166 // If there aren't any, then we don't need to worry about 167 // any connections. 168 dns, ok := destroyMap[key] 169 if !ok { 170 continue 171 } 172 173 // Get the nodes that depend on this on. In the example above: 174 // finding B in A => B. 175 for _, v := range g.UpEdges(v).List() { 176 // We're looking for resources. 177 rn, ok := v.(GraphNodeResource) 178 if !ok { 179 continue 180 } 181 182 // Keep track of the destroy nodes that this address 183 // needs to depend on. 184 key := rn.ResourceAddr().String() 185 depMap[key] = append(depMap[key], dns...) 186 } 187 } 188 189 return depMap, nil 190 }