]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/node_resource_apply_instance.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / node_resource_apply_instance.go
1 package terraform
2
3 import (
4 "fmt"
5
6 "github.com/zclconf/go-cty/cty"
7
8 "github.com/hashicorp/terraform/addrs"
9 "github.com/hashicorp/terraform/configs"
10 "github.com/hashicorp/terraform/plans"
11 "github.com/hashicorp/terraform/providers"
12 "github.com/hashicorp/terraform/states"
13 "github.com/hashicorp/terraform/tfdiags"
14 )
15
16 // NodeApplyableResourceInstance represents a resource instance that is
17 // "applyable": it is ready to be applied and is represented by a diff.
18 //
19 // This node is for a specific instance of a resource. It will usually be
20 // accompanied in the graph by a NodeApplyableResource representing its
21 // containing resource, and should depend on that node to ensure that the
22 // state is properly prepared to receive changes to instances.
23 type NodeApplyableResourceInstance struct {
24 *NodeAbstractResourceInstance
25
26 destroyNode GraphNodeDestroyerCBD
27 graphNodeDeposer // implementation of GraphNodeDeposer
28 }
29
30 var (
31 _ GraphNodeResource = (*NodeApplyableResourceInstance)(nil)
32 _ GraphNodeResourceInstance = (*NodeApplyableResourceInstance)(nil)
33 _ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil)
34 _ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil)
35 _ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil)
36 _ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil)
37 )
38
39 // GraphNodeAttachDestroyer
40 func (n *NodeApplyableResourceInstance) AttachDestroyNode(d GraphNodeDestroyerCBD) {
41 n.destroyNode = d
42 }
43
44 // createBeforeDestroy checks this nodes config status and the status af any
45 // companion destroy node for CreateBeforeDestroy.
46 func (n *NodeApplyableResourceInstance) createBeforeDestroy() bool {
47 cbd := false
48
49 if n.Config != nil && n.Config.Managed != nil {
50 cbd = n.Config.Managed.CreateBeforeDestroy
51 }
52
53 if n.destroyNode != nil {
54 cbd = cbd || n.destroyNode.CreateBeforeDestroy()
55 }
56
57 return cbd
58 }
59
60 // GraphNodeCreator
61 func (n *NodeApplyableResourceInstance) CreateAddr() *addrs.AbsResourceInstance {
62 addr := n.ResourceInstanceAddr()
63 return &addr
64 }
65
66 // GraphNodeReferencer, overriding NodeAbstractResourceInstance
67 func (n *NodeApplyableResourceInstance) References() []*addrs.Reference {
68 // Start with the usual resource instance implementation
69 ret := n.NodeAbstractResourceInstance.References()
70
71 // Applying a resource must also depend on the destruction of any of its
72 // dependencies, since this may for example affect the outcome of
73 // evaluating an entire list of resources with "count" set (by reducing
74 // the count).
75 //
76 // However, we can't do this in create_before_destroy mode because that
77 // would create a dependency cycle. We make a compromise here of requiring
78 // changes to be updated across two applies in this case, since the first
79 // plan will use the old values.
80 if !n.createBeforeDestroy() {
81 for _, ref := range ret {
82 switch tr := ref.Subject.(type) {
83 case addrs.ResourceInstance:
84 newRef := *ref // shallow copy so we can mutate
85 newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
86 newRef.Remaining = nil // can't access attributes of something being destroyed
87 ret = append(ret, &newRef)
88 case addrs.Resource:
89 newRef := *ref // shallow copy so we can mutate
90 newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
91 newRef.Remaining = nil // can't access attributes of something being destroyed
92 ret = append(ret, &newRef)
93 }
94 }
95 }
96
97 return ret
98 }
99
100 // GraphNodeEvalable
101 func (n *NodeApplyableResourceInstance) EvalTree() EvalNode {
102 addr := n.ResourceInstanceAddr()
103
104 // State still uses legacy-style internal ids, so we need to shim to get
105 // a suitable key to use.
106 stateId := NewLegacyResourceInstanceAddress(addr).stateId()
107
108 // Determine the dependencies for the state.
109 stateDeps := n.StateReferences()
110
111 if n.Config == nil {
112 // This should not be possible, but we've got here in at least one
113 // case as discussed in the following issue:
114 // https://github.com/hashicorp/terraform/issues/21258
115 // To avoid an outright crash here, we'll instead return an explicit
116 // error.
117 var diags tfdiags.Diagnostics
118 diags = diags.Append(tfdiags.Sourceless(
119 tfdiags.Error,
120 "Resource node has no configuration attached",
121 fmt.Sprintf(
122 "The graph node for %s has no configuration attached to it. This suggests a bug in Terraform's apply graph builder; please report it!",
123 addr,
124 ),
125 ))
126 err := diags.Err()
127 return &EvalReturnError{
128 Error: &err,
129 }
130 }
131
132 // Eval info is different depending on what kind of resource this is
133 switch n.Config.Mode {
134 case addrs.ManagedResourceMode:
135 return n.evalTreeManagedResource(addr, stateId, stateDeps)
136 case addrs.DataResourceMode:
137 return n.evalTreeDataResource(addr, stateId, stateDeps)
138 default:
139 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
140 }
141 }
142
143 func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
144 var provider providers.Interface
145 var providerSchema *ProviderSchema
146 var change *plans.ResourceInstanceChange
147 var state *states.ResourceInstanceObject
148
149 return &EvalSequence{
150 Nodes: []EvalNode{
151 &EvalGetProvider{
152 Addr: n.ResolvedProvider,
153 Output: &provider,
154 Schema: &providerSchema,
155 },
156
157 // Get the saved diff for apply
158 &EvalReadDiff{
159 Addr: addr.Resource,
160 ProviderSchema: &providerSchema,
161 Change: &change,
162 },
163
164 // Stop early if we don't actually have a diff
165 &EvalIf{
166 If: func(ctx EvalContext) (bool, error) {
167 if change == nil {
168 return true, EvalEarlyExitError{}
169 }
170 return true, nil
171 },
172 Then: EvalNoop{},
173 },
174
175 // In this particular call to EvalReadData we include our planned
176 // change, which signals that we expect this read to complete fully
177 // with no unknown values; it'll produce an error if not.
178 &EvalReadData{
179 Addr: addr.Resource,
180 Config: n.Config,
181 Dependencies: n.StateReferences(),
182 Planned: &change, // setting this indicates that the result must be complete
183 Provider: &provider,
184 ProviderAddr: n.ResolvedProvider,
185 ProviderSchema: &providerSchema,
186 OutputState: &state,
187 },
188
189 &EvalWriteState{
190 Addr: addr.Resource,
191 ProviderAddr: n.ResolvedProvider,
192 ProviderSchema: &providerSchema,
193 State: &state,
194 },
195
196 // Clear the diff now that we've applied it, so
197 // later nodes won't see a diff that's now a no-op.
198 &EvalWriteDiff{
199 Addr: addr.Resource,
200 ProviderSchema: &providerSchema,
201 Change: nil,
202 },
203
204 &EvalUpdateStateHook{},
205 },
206 }
207 }
208
209 func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
210 // Declare a bunch of variables that are used for state during
211 // evaluation. Most of this are written to by-address below.
212 var provider providers.Interface
213 var providerSchema *ProviderSchema
214 var diff, diffApply *plans.ResourceInstanceChange
215 var state *states.ResourceInstanceObject
216 var err error
217 var createNew bool
218 var createBeforeDestroyEnabled bool
219 var configVal cty.Value
220 var deposedKey states.DeposedKey
221
222 return &EvalSequence{
223 Nodes: []EvalNode{
224 &EvalGetProvider{
225 Addr: n.ResolvedProvider,
226 Output: &provider,
227 Schema: &providerSchema,
228 },
229
230 // Get the saved diff for apply
231 &EvalReadDiff{
232 Addr: addr.Resource,
233 ProviderSchema: &providerSchema,
234 Change: &diffApply,
235 },
236
237 // We don't want to do any destroys
238 // (these are handled by NodeDestroyResourceInstance instead)
239 &EvalIf{
240 If: func(ctx EvalContext) (bool, error) {
241 if diffApply == nil {
242 return true, EvalEarlyExitError{}
243 }
244 if diffApply.Action == plans.Delete {
245 return true, EvalEarlyExitError{}
246 }
247 return true, nil
248 },
249 Then: EvalNoop{},
250 },
251
252 &EvalIf{
253 If: func(ctx EvalContext) (bool, error) {
254 destroy := false
255 if diffApply != nil {
256 destroy = (diffApply.Action == plans.Delete || diffApply.Action.IsReplace())
257 }
258 if destroy && n.createBeforeDestroy() {
259 createBeforeDestroyEnabled = true
260 }
261 return createBeforeDestroyEnabled, nil
262 },
263 Then: &EvalDeposeState{
264 Addr: addr.Resource,
265 ForceKey: n.PreallocatedDeposedKey,
266 OutputKey: &deposedKey,
267 },
268 },
269
270 &EvalReadState{
271 Addr: addr.Resource,
272 Provider: &provider,
273 ProviderSchema: &providerSchema,
274
275 Output: &state,
276 },
277
278 // Get the saved diff
279 &EvalReadDiff{
280 Addr: addr.Resource,
281 ProviderSchema: &providerSchema,
282 Change: &diff,
283 },
284
285 // Make a new diff, in case we've learned new values in the state
286 // during apply which we can now incorporate.
287 &EvalDiff{
288 Addr: addr.Resource,
289 Config: n.Config,
290 Provider: &provider,
291 ProviderAddr: n.ResolvedProvider,
292 ProviderSchema: &providerSchema,
293 State: &state,
294 PreviousDiff: &diff,
295 OutputChange: &diffApply,
296 OutputValue: &configVal,
297 OutputState: &state,
298 },
299
300 // Compare the diffs
301 &EvalCheckPlannedChange{
302 Addr: addr.Resource,
303 ProviderAddr: n.ResolvedProvider,
304 ProviderSchema: &providerSchema,
305 Planned: &diff,
306 Actual: &diffApply,
307 },
308
309 &EvalGetProvider{
310 Addr: n.ResolvedProvider,
311 Output: &provider,
312 Schema: &providerSchema,
313 },
314 &EvalReadState{
315 Addr: addr.Resource,
316 Provider: &provider,
317 ProviderSchema: &providerSchema,
318
319 Output: &state,
320 },
321
322 &EvalReduceDiff{
323 Addr: addr.Resource,
324 InChange: &diffApply,
325 Destroy: false,
326 OutChange: &diffApply,
327 },
328
329 // EvalReduceDiff may have simplified our planned change
330 // into a NoOp if it only requires destroying, since destroying
331 // is handled by NodeDestroyResourceInstance.
332 &EvalIf{
333 If: func(ctx EvalContext) (bool, error) {
334 if diffApply == nil || diffApply.Action == plans.NoOp {
335 return true, EvalEarlyExitError{}
336 }
337 return true, nil
338 },
339 Then: EvalNoop{},
340 },
341
342 // Call pre-apply hook
343 &EvalApplyPre{
344 Addr: addr.Resource,
345 State: &state,
346 Change: &diffApply,
347 },
348 &EvalApply{
349 Addr: addr.Resource,
350 Config: n.Config,
351 Dependencies: n.StateReferences(),
352 State: &state,
353 Change: &diffApply,
354 Provider: &provider,
355 ProviderAddr: n.ResolvedProvider,
356 ProviderSchema: &providerSchema,
357 Output: &state,
358 Error: &err,
359 CreateNew: &createNew,
360 },
361 &EvalMaybeTainted{
362 Addr: addr.Resource,
363 State: &state,
364 Change: &diffApply,
365 Error: &err,
366 StateOutput: &state,
367 },
368 &EvalWriteState{
369 Addr: addr.Resource,
370 ProviderAddr: n.ResolvedProvider,
371 ProviderSchema: &providerSchema,
372 State: &state,
373 },
374 &EvalApplyProvisioners{
375 Addr: addr.Resource,
376 State: &state, // EvalApplyProvisioners will skip if already tainted
377 ResourceConfig: n.Config,
378 CreateNew: &createNew,
379 Error: &err,
380 When: configs.ProvisionerWhenCreate,
381 },
382 &EvalMaybeTainted{
383 Addr: addr.Resource,
384 State: &state,
385 Change: &diffApply,
386 Error: &err,
387 StateOutput: &state,
388 },
389 &EvalWriteState{
390 Addr: addr.Resource,
391 ProviderAddr: n.ResolvedProvider,
392 ProviderSchema: &providerSchema,
393 State: &state,
394 },
395 &EvalIf{
396 If: func(ctx EvalContext) (bool, error) {
397 return createBeforeDestroyEnabled && err != nil, nil
398 },
399 Then: &EvalMaybeRestoreDeposedObject{
400 Addr: addr.Resource,
401 Key: &deposedKey,
402 },
403 },
404
405 // We clear the diff out here so that future nodes
406 // don't see a diff that is already complete. There
407 // is no longer a diff!
408 &EvalIf{
409 If: func(ctx EvalContext) (bool, error) {
410 if !diff.Action.IsReplace() {
411 return true, nil
412 }
413 if !n.createBeforeDestroy() {
414 return true, nil
415 }
416 return false, nil
417 },
418 Then: &EvalWriteDiff{
419 Addr: addr.Resource,
420 ProviderSchema: &providerSchema,
421 Change: nil,
422 },
423 },
424
425 &EvalApplyPost{
426 Addr: addr.Resource,
427 State: &state,
428 Error: &err,
429 },
430 &EvalUpdateStateHook{},
431 },
432 }
433 }