]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/transform_destroy_cbd.go
Upgrade to 0.12
[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/configs"
8 "github.com/hashicorp/terraform/dag"
9 "github.com/hashicorp/terraform/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 // GraphNodeAttachDestroyer is implemented by applyable nodes that have a
27 // companion destroy node. This allows the creation node to look up the status
28 // of the destroy node and determine if it needs to depose the existing state,
29 // or replace it.
30 // If a node is not marked as create-before-destroy in the configuration, but a
31 // dependency forces that status, only the destroy node will be aware of that
32 // status.
33 type GraphNodeAttachDestroyer interface {
34 // AttachDestroyNode takes a destroy node and saves a reference to that
35 // node in the receiver, so it can later check the status of
36 // CreateBeforeDestroy().
37 AttachDestroyNode(n GraphNodeDestroyerCBD)
38 }
39
40 // ForcedCBDTransformer detects when a particular CBD-able graph node has
41 // dependencies with another that has create_before_destroy set that require
42 // it to be forced on, and forces it on.
43 //
44 // This must be used in the plan graph builder to ensure that
45 // create_before_destroy settings are properly propagated before constructing
46 // the planned changes. This requires that the plannable resource nodes
47 // implement GraphNodeDestroyerCBD.
48 type ForcedCBDTransformer struct {
49 }
50
51 func (t *ForcedCBDTransformer) Transform(g *Graph) error {
52 for _, v := range g.Vertices() {
53 dn, ok := v.(GraphNodeDestroyerCBD)
54 if !ok {
55 continue
56 }
57
58 if !dn.CreateBeforeDestroy() {
59 // If there are no CBD decendent (dependent nodes), then we
60 // do nothing here.
61 if !t.hasCBDDescendent(g, v) {
62 log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) has no CBD descendent, so skipping", dag.VertexName(v), v)
63 continue
64 }
65
66 // If this isn't naturally a CBD node, this means that an descendent is
67 // and we need to auto-upgrade this node to CBD. We do this because
68 // a CBD node depending on non-CBD will result in cycles. To avoid this,
69 // we always attempt to upgrade it.
70 log.Printf("[TRACE] ForcedCBDTransformer: forcing create_before_destroy on for %q (%T)", dag.VertexName(v), v)
71 if err := dn.ModifyCreateBeforeDestroy(true); err != nil {
72 return fmt.Errorf(
73 "%s: must have create before destroy enabled because "+
74 "a dependent resource has CBD enabled. However, when "+
75 "attempting to automatically do this, an error occurred: %s",
76 dag.VertexName(v), err)
77 }
78 } else {
79 log.Printf("[TRACE] ForcedCBDTransformer: %q (%T) already has create_before_destroy set", dag.VertexName(v), v)
80 }
81 }
82 return nil
83 }
84
85 // hasCBDDescendent returns true if any descendent (node that depends on this)
86 // has CBD set.
87 func (t *ForcedCBDTransformer) hasCBDDescendent(g *Graph, v dag.Vertex) bool {
88 s, _ := g.Descendents(v)
89 if s == nil {
90 return true
91 }
92
93 for _, ov := range s.List() {
94 dn, ok := ov.(GraphNodeDestroyerCBD)
95 if !ok {
96 continue
97 }
98
99 if dn.CreateBeforeDestroy() {
100 // some descendent is CreateBeforeDestroy, so we need to follow suit
101 log.Printf("[TRACE] ForcedCBDTransformer: %q has CBD descendent %q", dag.VertexName(v), dag.VertexName(ov))
102 return true
103 }
104 }
105
106 return false
107 }
108
109 // CBDEdgeTransformer modifies the edges of CBD nodes that went through
110 // the DestroyEdgeTransformer to have the right dependencies. There are
111 // two real tasks here:
112 //
113 // 1. With CBD, the destroy edge is inverted: the destroy depends on
114 // the creation.
115 //
116 // 2. A_d must depend on resources that depend on A. This is to enable
117 // the destroy to only happen once nodes that depend on A successfully
118 // update to A. Example: adding a web server updates the load balancer
119 // before deleting the old web server.
120 //
121 // This transformer requires that a previous transformer has already forced
122 // create_before_destroy on for nodes that are depended on by explicit CBD
123 // nodes. This is the logic in ForcedCBDTransformer, though in practice we
124 // will get here by recording the CBD-ness of each change in the plan during
125 // the plan walk and then forcing the nodes into the appropriate setting during
126 // DiffTransformer when building the apply graph.
127 type CBDEdgeTransformer struct {
128 // Module and State are only needed to look up dependencies in
129 // any way possible. Either can be nil if not availabile.
130 Config *configs.Config
131 State *states.State
132
133 // If configuration is present then Schemas is required in order to
134 // obtain schema information from providers and provisioners so we can
135 // properly resolve implicit dependencies.
136 Schemas *Schemas
137 }
138
139 func (t *CBDEdgeTransformer) Transform(g *Graph) error {
140 // Go through and reverse any destroy edges
141 destroyMap := make(map[string][]dag.Vertex)
142 for _, v := range g.Vertices() {
143 dn, ok := v.(GraphNodeDestroyerCBD)
144 if !ok {
145 continue
146 }
147 dern, ok := v.(GraphNodeDestroyer)
148 if !ok {
149 continue
150 }
151
152 if !dn.CreateBeforeDestroy() {
153 continue
154 }
155
156 // Find the destroy edge. There should only be one.
157 for _, e := range g.EdgesTo(v) {
158 // Not a destroy edge, ignore it
159 de, ok := e.(*DestroyEdge)
160 if !ok {
161 continue
162 }
163
164 log.Printf("[TRACE] CBDEdgeTransformer: inverting edge: %s => %s",
165 dag.VertexName(de.Source()), dag.VertexName(de.Target()))
166
167 // Found it! Invert.
168 g.RemoveEdge(de)
169 applyNode := de.Source()
170 destroyNode := de.Target()
171 g.Connect(&DestroyEdge{S: destroyNode, T: applyNode})
172 }
173
174 // If the address has an index, we strip that. Our depMap creation
175 // graph doesn't expand counts so we don't currently get _exact_
176 // dependencies. One day when we limit dependencies more exactly
177 // this will have to change. We have a test case covering this
178 // (depNonCBDCountBoth) so it'll be caught.
179 addr := dern.DestroyAddr()
180 key := addr.ContainingResource().String()
181
182 // Add this to the list of nodes that we need to fix up
183 // the edges for (step 2 above in the docs).
184 destroyMap[key] = append(destroyMap[key], v)
185 }
186
187 // If we have no CBD nodes, then our work here is done
188 if len(destroyMap) == 0 {
189 return nil
190 }
191
192 // We have CBD nodes. We now have to move on to the much more difficult
193 // task of connecting dependencies of the creation side of the destroy
194 // to the destruction node. The easiest way to explain this is an example:
195 //
196 // Given a pre-destroy dependence of: A => B
197 // And A has CBD set.
198 //
199 // The resulting graph should be: A => B => A_d
200 //
201 // They key here is that B happens before A is destroyed. This is to
202 // facilitate the primary purpose for CBD: making sure that downstreams
203 // are properly updated to avoid downtime before the resource is destroyed.
204 //
205 // We can't trust that the resource being destroyed or anything that
206 // depends on it is actually in our current graph so we make a new
207 // graph in order to determine those dependencies and add them in.
208 log.Printf("[TRACE] CBDEdgeTransformer: building graph to find dependencies...")
209 depMap, err := t.depMap(destroyMap)
210 if err != nil {
211 return err
212 }
213
214 // We now have the mapping of resource addresses to the destroy
215 // nodes they need to depend on. We now go through our own vertices to
216 // find any matching these addresses and make the connection.
217 for _, v := range g.Vertices() {
218 // We're looking for creators
219 rn, ok := v.(GraphNodeCreator)
220 if !ok {
221 continue
222 }
223
224 // Get the address
225 addr := rn.CreateAddr()
226
227 // If the address has an index, we strip that. Our depMap creation
228 // graph doesn't expand counts so we don't currently get _exact_
229 // dependencies. One day when we limit dependencies more exactly
230 // this will have to change. We have a test case covering this
231 // (depNonCBDCount) so it'll be caught.
232 key := addr.ContainingResource().String()
233
234 // If there is nothing this resource should depend on, ignore it
235 dns, ok := depMap[key]
236 if !ok {
237 continue
238 }
239
240 // We have nodes! Make the connection
241 for _, dn := range dns {
242 log.Printf("[TRACE] CBDEdgeTransformer: destroy depends on dependence: %s => %s",
243 dag.VertexName(dn), dag.VertexName(v))
244 g.Connect(dag.BasicEdge(dn, v))
245 }
246 }
247
248 return nil
249 }
250
251 func (t *CBDEdgeTransformer) depMap(destroyMap map[string][]dag.Vertex) (map[string][]dag.Vertex, error) {
252 // Build the graph of our config, this ensures that all resources
253 // are present in the graph.
254 g, diags := (&BasicGraphBuilder{
255 Steps: []GraphTransformer{
256 &FlatConfigTransformer{Config: t.Config},
257 &AttachResourceConfigTransformer{Config: t.Config},
258 &AttachStateTransformer{State: t.State},
259 &AttachSchemaTransformer{Schemas: t.Schemas},
260 &ReferenceTransformer{},
261 },
262 Name: "CBDEdgeTransformer",
263 }).Build(nil)
264 if diags.HasErrors() {
265 return nil, diags.Err()
266 }
267
268 // Using this graph, build the list of destroy nodes that each resource
269 // address should depend on. For example, when we find B, we map the
270 // address of B to A_d in the "depMap" variable below.
271 depMap := make(map[string][]dag.Vertex)
272 for _, v := range g.Vertices() {
273 // We're looking for resources.
274 rn, ok := v.(GraphNodeResource)
275 if !ok {
276 continue
277 }
278
279 // Get the address
280 addr := rn.ResourceAddr()
281 key := addr.String()
282
283 // Get the destroy nodes that are destroying this resource.
284 // If there aren't any, then we don't need to worry about
285 // any connections.
286 dns, ok := destroyMap[key]
287 if !ok {
288 continue
289 }
290
291 // Get the nodes that depend on this on. In the example above:
292 // finding B in A => B.
293 for _, v := range g.UpEdges(v).List() {
294 // We're looking for resources.
295 rn, ok := v.(GraphNodeResource)
296 if !ok {
297 continue
298 }
299
300 // Keep track of the destroy nodes that this address
301 // needs to depend on.
302 key := rn.ResourceAddr().String()
303 depMap[key] = append(depMap[key], dns...)
304 }
305 }
306
307 return depMap, nil
308 }