]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | ||
6 | "github.com/hashicorp/terraform/config" | |
7 | ) | |
8 | ||
9 | // NodeApplyableResource represents a resource that is "applyable": | |
10 | // it is ready to be applied and is represented by a diff. | |
11 | type NodeApplyableResource struct { | |
12 | *NodeAbstractResource | |
13 | } | |
14 | ||
15 | // GraphNodeCreator | |
16 | func (n *NodeApplyableResource) CreateAddr() *ResourceAddress { | |
17 | return n.NodeAbstractResource.Addr | |
18 | } | |
19 | ||
20 | // GraphNodeReferencer, overriding NodeAbstractResource | |
21 | func (n *NodeApplyableResource) References() []string { | |
22 | result := n.NodeAbstractResource.References() | |
23 | ||
24 | // The "apply" side of a resource generally also depends on the | |
25 | // destruction of its dependencies as well. For example, if a LB | |
26 | // references a set of VMs with ${vm.foo.*.id}, then we must wait for | |
27 | // the destruction so we get the newly updated list of VMs. | |
28 | // | |
29 | // The exception here is CBD. When CBD is set, we don't do this since | |
30 | // it would create a cycle. By not creating a cycle, we require two | |
31 | // applies since the first apply the creation step will use the OLD | |
32 | // values (pre-destroy) and the second step will update. | |
33 | // | |
34 | // This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x). | |
35 | // We mimic that behavior here now and can improve upon it in the future. | |
36 | // | |
37 | // This behavior is tested in graph_build_apply_test.go to test ordering. | |
38 | cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy | |
39 | if !cbd { | |
40 | // The "apply" side of a resource always depends on the destruction | |
41 | // of all its dependencies in addition to the creation. | |
42 | for _, v := range result { | |
43 | result = append(result, v+".destroy") | |
44 | } | |
45 | } | |
46 | ||
47 | return result | |
48 | } | |
49 | ||
50 | // GraphNodeEvalable | |
51 | func (n *NodeApplyableResource) EvalTree() EvalNode { | |
52 | addr := n.NodeAbstractResource.Addr | |
53 | ||
54 | // stateId is the ID to put into the state | |
55 | stateId := addr.stateId() | |
56 | ||
57 | // Build the instance info. More of this will be populated during eval | |
58 | info := &InstanceInfo{ | |
59 | Id: stateId, | |
60 | Type: addr.Type, | |
61 | } | |
62 | ||
63 | // Build the resource for eval | |
64 | resource := &Resource{ | |
65 | Name: addr.Name, | |
66 | Type: addr.Type, | |
67 | CountIndex: addr.Index, | |
68 | } | |
69 | if resource.CountIndex < 0 { | |
70 | resource.CountIndex = 0 | |
71 | } | |
72 | ||
73 | // Determine the dependencies for the state. | |
74 | stateDeps := n.StateReferences() | |
75 | ||
76 | // Eval info is different depending on what kind of resource this is | |
77 | switch n.Config.Mode { | |
78 | case config.ManagedResourceMode: | |
79 | return n.evalTreeManagedResource( | |
80 | stateId, info, resource, stateDeps, | |
81 | ) | |
82 | case config.DataResourceMode: | |
83 | return n.evalTreeDataResource( | |
84 | stateId, info, resource, stateDeps) | |
85 | default: | |
86 | panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) | |
87 | } | |
88 | } | |
89 | ||
90 | func (n *NodeApplyableResource) evalTreeDataResource( | |
91 | stateId string, info *InstanceInfo, | |
92 | resource *Resource, stateDeps []string) EvalNode { | |
93 | var provider ResourceProvider | |
94 | var config *ResourceConfig | |
95 | var diff *InstanceDiff | |
96 | var state *InstanceState | |
97 | ||
98 | return &EvalSequence{ | |
99 | Nodes: []EvalNode{ | |
100 | // Build the instance info | |
101 | &EvalInstanceInfo{ | |
102 | Info: info, | |
103 | }, | |
104 | ||
105 | // Get the saved diff for apply | |
106 | &EvalReadDiff{ | |
107 | Name: stateId, | |
108 | Diff: &diff, | |
109 | }, | |
110 | ||
111 | // Stop here if we don't actually have a diff | |
112 | &EvalIf{ | |
113 | If: func(ctx EvalContext) (bool, error) { | |
114 | if diff == nil { | |
115 | return true, EvalEarlyExitError{} | |
116 | } | |
117 | ||
118 | if diff.GetAttributesLen() == 0 { | |
119 | return true, EvalEarlyExitError{} | |
120 | } | |
121 | ||
122 | return true, nil | |
123 | }, | |
124 | Then: EvalNoop{}, | |
125 | }, | |
126 | ||
15c0b25d AP |
127 | // Normally we interpolate count as a preparation step before |
128 | // a DynamicExpand, but an apply graph has pre-expanded nodes | |
129 | // and so the count would otherwise never be interpolated. | |
130 | // | |
131 | // This is redundant when there are multiple instances created | |
132 | // from the same config (count > 1) but harmless since the | |
133 | // underlying structures have mutexes to make this concurrency-safe. | |
134 | // | |
135 | // In most cases this isn't actually needed because we dealt with | |
136 | // all of the counts during the plan walk, but we do it here | |
137 | // for completeness because other code assumes that the | |
138 | // final count is always available during interpolation. | |
139 | // | |
140 | // Here we are just populating the interpolated value in-place | |
141 | // inside this RawConfig object, like we would in | |
142 | // NodeAbstractCountResource. | |
143 | &EvalInterpolate{ | |
144 | Config: n.Config.RawCount, | |
145 | ContinueOnErr: true, | |
146 | }, | |
147 | ||
bae9f6d2 JC |
148 | // We need to re-interpolate the config here, rather than |
149 | // just using the diff's values directly, because we've | |
150 | // potentially learned more variable values during the | |
151 | // apply pass that weren't known when the diff was produced. | |
152 | &EvalInterpolate{ | |
153 | Config: n.Config.RawConfig.Copy(), | |
154 | Resource: resource, | |
155 | Output: &config, | |
156 | }, | |
157 | ||
158 | &EvalGetProvider{ | |
15c0b25d | 159 | Name: n.ResolvedProvider, |
bae9f6d2 JC |
160 | Output: &provider, |
161 | }, | |
162 | ||
163 | // Make a new diff with our newly-interpolated config. | |
164 | &EvalReadDataDiff{ | |
165 | Info: info, | |
166 | Config: &config, | |
167 | Previous: &diff, | |
168 | Provider: &provider, | |
169 | Output: &diff, | |
170 | }, | |
171 | ||
172 | &EvalReadDataApply{ | |
173 | Info: info, | |
174 | Diff: &diff, | |
175 | Provider: &provider, | |
176 | Output: &state, | |
177 | }, | |
178 | ||
179 | &EvalWriteState{ | |
180 | Name: stateId, | |
181 | ResourceType: n.Config.Type, | |
15c0b25d | 182 | Provider: n.ResolvedProvider, |
bae9f6d2 JC |
183 | Dependencies: stateDeps, |
184 | State: &state, | |
185 | }, | |
186 | ||
187 | // Clear the diff now that we've applied it, so | |
188 | // later nodes won't see a diff that's now a no-op. | |
189 | &EvalWriteDiff{ | |
190 | Name: stateId, | |
191 | Diff: nil, | |
192 | }, | |
193 | ||
194 | &EvalUpdateStateHook{}, | |
195 | }, | |
196 | } | |
197 | } | |
198 | ||
199 | func (n *NodeApplyableResource) evalTreeManagedResource( | |
200 | stateId string, info *InstanceInfo, | |
201 | resource *Resource, stateDeps []string) EvalNode { | |
202 | // Declare a bunch of variables that are used for state during | |
203 | // evaluation. Most of this are written to by-address below. | |
204 | var provider ResourceProvider | |
205 | var diff, diffApply *InstanceDiff | |
206 | var state *InstanceState | |
207 | var resourceConfig *ResourceConfig | |
208 | var err error | |
209 | var createNew bool | |
210 | var createBeforeDestroyEnabled bool | |
211 | ||
212 | return &EvalSequence{ | |
213 | Nodes: []EvalNode{ | |
214 | // Build the instance info | |
215 | &EvalInstanceInfo{ | |
216 | Info: info, | |
217 | }, | |
218 | ||
219 | // Get the saved diff for apply | |
220 | &EvalReadDiff{ | |
221 | Name: stateId, | |
222 | Diff: &diffApply, | |
223 | }, | |
224 | ||
225 | // We don't want to do any destroys | |
226 | &EvalIf{ | |
227 | If: func(ctx EvalContext) (bool, error) { | |
228 | if diffApply == nil { | |
229 | return true, EvalEarlyExitError{} | |
230 | } | |
231 | ||
232 | if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 { | |
233 | return true, EvalEarlyExitError{} | |
234 | } | |
235 | ||
236 | diffApply.SetDestroy(false) | |
237 | return true, nil | |
238 | }, | |
239 | Then: EvalNoop{}, | |
240 | }, | |
241 | ||
242 | &EvalIf{ | |
243 | If: func(ctx EvalContext) (bool, error) { | |
244 | destroy := false | |
245 | if diffApply != nil { | |
246 | destroy = diffApply.GetDestroy() || diffApply.RequiresNew() | |
247 | } | |
248 | ||
249 | createBeforeDestroyEnabled = | |
250 | n.Config.Lifecycle.CreateBeforeDestroy && | |
251 | destroy | |
252 | ||
253 | return createBeforeDestroyEnabled, nil | |
254 | }, | |
255 | Then: &EvalDeposeState{ | |
256 | Name: stateId, | |
257 | }, | |
258 | }, | |
259 | ||
15c0b25d AP |
260 | // Normally we interpolate count as a preparation step before |
261 | // a DynamicExpand, but an apply graph has pre-expanded nodes | |
262 | // and so the count would otherwise never be interpolated. | |
263 | // | |
264 | // This is redundant when there are multiple instances created | |
265 | // from the same config (count > 1) but harmless since the | |
266 | // underlying structures have mutexes to make this concurrency-safe. | |
267 | // | |
268 | // In most cases this isn't actually needed because we dealt with | |
269 | // all of the counts during the plan walk, but we need to do this | |
270 | // in order to support interpolation of resource counts from | |
271 | // apply-time-interpolated expressions, such as those in | |
272 | // "provisioner" blocks. | |
273 | // | |
274 | // Here we are just populating the interpolated value in-place | |
275 | // inside this RawConfig object, like we would in | |
276 | // NodeAbstractCountResource. | |
277 | &EvalInterpolate{ | |
278 | Config: n.Config.RawCount, | |
279 | ContinueOnErr: true, | |
280 | }, | |
281 | ||
bae9f6d2 JC |
282 | &EvalInterpolate{ |
283 | Config: n.Config.RawConfig.Copy(), | |
284 | Resource: resource, | |
285 | Output: &resourceConfig, | |
286 | }, | |
287 | &EvalGetProvider{ | |
15c0b25d | 288 | Name: n.ResolvedProvider, |
bae9f6d2 JC |
289 | Output: &provider, |
290 | }, | |
291 | &EvalReadState{ | |
292 | Name: stateId, | |
293 | Output: &state, | |
294 | }, | |
295 | // Re-run validation to catch any errors we missed, e.g. type | |
296 | // mismatches on computed values. | |
297 | &EvalValidateResource{ | |
298 | Provider: &provider, | |
299 | Config: &resourceConfig, | |
300 | ResourceName: n.Config.Name, | |
301 | ResourceType: n.Config.Type, | |
302 | ResourceMode: n.Config.Mode, | |
303 | IgnoreWarnings: true, | |
304 | }, | |
305 | &EvalDiff{ | |
306 | Info: info, | |
307 | Config: &resourceConfig, | |
308 | Resource: n.Config, | |
309 | Provider: &provider, | |
310 | Diff: &diffApply, | |
311 | State: &state, | |
312 | OutputDiff: &diffApply, | |
313 | }, | |
314 | ||
315 | // Get the saved diff | |
316 | &EvalReadDiff{ | |
317 | Name: stateId, | |
318 | Diff: &diff, | |
319 | }, | |
320 | ||
321 | // Compare the diffs | |
322 | &EvalCompareDiff{ | |
323 | Info: info, | |
324 | One: &diff, | |
325 | Two: &diffApply, | |
326 | }, | |
327 | ||
328 | &EvalGetProvider{ | |
15c0b25d | 329 | Name: n.ResolvedProvider, |
bae9f6d2 JC |
330 | Output: &provider, |
331 | }, | |
332 | &EvalReadState{ | |
333 | Name: stateId, | |
334 | Output: &state, | |
335 | }, | |
336 | // Call pre-apply hook | |
337 | &EvalApplyPre{ | |
338 | Info: info, | |
339 | State: &state, | |
340 | Diff: &diffApply, | |
341 | }, | |
342 | &EvalApply{ | |
343 | Info: info, | |
344 | State: &state, | |
345 | Diff: &diffApply, | |
346 | Provider: &provider, | |
347 | Output: &state, | |
348 | Error: &err, | |
349 | CreateNew: &createNew, | |
350 | }, | |
351 | &EvalWriteState{ | |
352 | Name: stateId, | |
353 | ResourceType: n.Config.Type, | |
15c0b25d | 354 | Provider: n.ResolvedProvider, |
bae9f6d2 JC |
355 | Dependencies: stateDeps, |
356 | State: &state, | |
357 | }, | |
358 | &EvalApplyProvisioners{ | |
359 | Info: info, | |
360 | State: &state, | |
361 | Resource: n.Config, | |
362 | InterpResource: resource, | |
363 | CreateNew: &createNew, | |
364 | Error: &err, | |
365 | When: config.ProvisionerWhenCreate, | |
366 | }, | |
367 | &EvalIf{ | |
368 | If: func(ctx EvalContext) (bool, error) { | |
369 | return createBeforeDestroyEnabled && err != nil, nil | |
370 | }, | |
371 | Then: &EvalUndeposeState{ | |
372 | Name: stateId, | |
373 | State: &state, | |
374 | }, | |
375 | Else: &EvalWriteState{ | |
376 | Name: stateId, | |
377 | ResourceType: n.Config.Type, | |
15c0b25d | 378 | Provider: n.ResolvedProvider, |
bae9f6d2 JC |
379 | Dependencies: stateDeps, |
380 | State: &state, | |
381 | }, | |
382 | }, | |
383 | ||
384 | // We clear the diff out here so that future nodes | |
385 | // don't see a diff that is already complete. There | |
386 | // is no longer a diff! | |
387 | &EvalWriteDiff{ | |
388 | Name: stateId, | |
389 | Diff: nil, | |
390 | }, | |
391 | ||
392 | &EvalApplyPost{ | |
393 | Info: info, | |
394 | State: &state, | |
395 | Error: &err, | |
396 | }, | |
397 | &EvalUpdateStateHook{}, | |
398 | }, | |
399 | } | |
400 | } |