]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/transform_destroy_cbd.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / transform_destroy_cbd.go
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 }