]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/terraform/eval_state.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / terraform / eval_state.go
1 package terraform
2
3 import (
4 "fmt"
5 "log"
6
7 "github.com/hashicorp/terraform/addrs"
8 "github.com/hashicorp/terraform/configs"
9 "github.com/hashicorp/terraform/providers"
10 "github.com/hashicorp/terraform/states"
11 "github.com/hashicorp/terraform/tfdiags"
12 )
13
14 // EvalReadState is an EvalNode implementation that reads the
15 // current object for a specific instance in the state.
16 type EvalReadState struct {
17 // Addr is the address of the instance to read state for.
18 Addr addrs.ResourceInstance
19
20 // ProviderSchema is the schema for the provider given in Provider.
21 ProviderSchema **ProviderSchema
22
23 // Provider is the provider that will subsequently perform actions on
24 // the the state object. This is used to perform any schema upgrades
25 // that might be required to prepare the stored data for use.
26 Provider *providers.Interface
27
28 // Output will be written with a pointer to the retrieved object.
29 Output **states.ResourceInstanceObject
30 }
31
32 func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
33 if n.Provider == nil || *n.Provider == nil {
34 panic("EvalReadState used with no Provider object")
35 }
36 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
37 panic("EvalReadState used with no ProviderSchema object")
38 }
39
40 absAddr := n.Addr.Absolute(ctx.Path())
41 log.Printf("[TRACE] EvalReadState: reading state for %s", absAddr)
42
43 src := ctx.State().ResourceInstanceObject(absAddr, states.CurrentGen)
44 if src == nil {
45 // Presumably we only have deposed objects, then.
46 log.Printf("[TRACE] EvalReadState: no state present for %s", absAddr)
47 return nil, nil
48 }
49
50 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
51 if schema == nil {
52 // Shouldn't happen since we should've failed long ago if no schema is present
53 return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", absAddr)
54 }
55 var diags tfdiags.Diagnostics
56 src, diags = UpgradeResourceState(absAddr, *n.Provider, src, schema, currentVersion)
57 if diags.HasErrors() {
58 // Note that we don't have any channel to return warnings here. We'll
59 // accept that for now since warnings during a schema upgrade would
60 // be pretty weird anyway, since this operation is supposed to seem
61 // invisible to the user.
62 return nil, diags.Err()
63 }
64
65 obj, err := src.Decode(schema.ImpliedType())
66 if err != nil {
67 return nil, err
68 }
69
70 if n.Output != nil {
71 *n.Output = obj
72 }
73 return obj, nil
74 }
75
76 // EvalReadStateDeposed is an EvalNode implementation that reads the
77 // deposed InstanceState for a specific resource out of the state
78 type EvalReadStateDeposed struct {
79 // Addr is the address of the instance to read state for.
80 Addr addrs.ResourceInstance
81
82 // Key identifies which deposed object we will read.
83 Key states.DeposedKey
84
85 // ProviderSchema is the schema for the provider given in Provider.
86 ProviderSchema **ProviderSchema
87
88 // Provider is the provider that will subsequently perform actions on
89 // the the state object. This is used to perform any schema upgrades
90 // that might be required to prepare the stored data for use.
91 Provider *providers.Interface
92
93 // Output will be written with a pointer to the retrieved object.
94 Output **states.ResourceInstanceObject
95 }
96
97 func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
98 if n.Provider == nil || *n.Provider == nil {
99 panic("EvalReadStateDeposed used with no Provider object")
100 }
101 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
102 panic("EvalReadStateDeposed used with no ProviderSchema object")
103 }
104
105 key := n.Key
106 if key == states.NotDeposed {
107 return nil, fmt.Errorf("EvalReadStateDeposed used with no instance key; this is a bug in Terraform and should be reported")
108 }
109 absAddr := n.Addr.Absolute(ctx.Path())
110 log.Printf("[TRACE] EvalReadStateDeposed: reading state for %s deposed object %s", absAddr, n.Key)
111
112 src := ctx.State().ResourceInstanceObject(absAddr, key)
113 if src == nil {
114 // Presumably we only have deposed objects, then.
115 log.Printf("[TRACE] EvalReadStateDeposed: no state present for %s deposed object %s", absAddr, n.Key)
116 return nil, nil
117 }
118
119 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
120 if schema == nil {
121 // Shouldn't happen since we should've failed long ago if no schema is present
122 return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", absAddr)
123 }
124 var diags tfdiags.Diagnostics
125 src, diags = UpgradeResourceState(absAddr, *n.Provider, src, schema, currentVersion)
126 if diags.HasErrors() {
127 // Note that we don't have any channel to return warnings here. We'll
128 // accept that for now since warnings during a schema upgrade would
129 // be pretty weird anyway, since this operation is supposed to seem
130 // invisible to the user.
131 return nil, diags.Err()
132 }
133
134 obj, err := src.Decode(schema.ImpliedType())
135 if err != nil {
136 return nil, err
137 }
138 if n.Output != nil {
139 *n.Output = obj
140 }
141 return obj, nil
142 }
143
144 // EvalRequireState is an EvalNode implementation that exits early if the given
145 // object is null.
146 type EvalRequireState struct {
147 State **states.ResourceInstanceObject
148 }
149
150 func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) {
151 if n.State == nil {
152 return nil, EvalEarlyExitError{}
153 }
154
155 state := *n.State
156 if state == nil || state.Value.IsNull() {
157 return nil, EvalEarlyExitError{}
158 }
159
160 return nil, nil
161 }
162
163 // EvalUpdateStateHook is an EvalNode implementation that calls the
164 // PostStateUpdate hook with the current state.
165 type EvalUpdateStateHook struct{}
166
167 func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
168 // In principle we could grab the lock here just long enough to take a
169 // deep copy and then pass that to our hooks below, but we'll instead
170 // hold the hook for the duration to avoid the potential confusing
171 // situation of us racing to call PostStateUpdate concurrently with
172 // different state snapshots.
173 stateSync := ctx.State()
174 state := stateSync.Lock().DeepCopy()
175 defer stateSync.Unlock()
176
177 // Call the hook
178 err := ctx.Hook(func(h Hook) (HookAction, error) {
179 return h.PostStateUpdate(state)
180 })
181 if err != nil {
182 return nil, err
183 }
184
185 return nil, nil
186 }
187
188 // EvalWriteState is an EvalNode implementation that saves the given object
189 // as the current object for the selected resource instance.
190 type EvalWriteState struct {
191 // Addr is the address of the instance to read state for.
192 Addr addrs.ResourceInstance
193
194 // State is the object state to save.
195 State **states.ResourceInstanceObject
196
197 // ProviderSchema is the schema for the provider given in ProviderAddr.
198 ProviderSchema **ProviderSchema
199
200 // ProviderAddr is the address of the provider configuration that
201 // produced the given object.
202 ProviderAddr addrs.AbsProviderConfig
203 }
204
205 func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
206 if n.State == nil {
207 // Note that a pointer _to_ nil is valid here, indicating the total
208 // absense of an object as we'd see during destroy.
209 panic("EvalWriteState used with no ResourceInstanceObject")
210 }
211
212 absAddr := n.Addr.Absolute(ctx.Path())
213 state := ctx.State()
214
215 if n.ProviderAddr.ProviderConfig.Type == "" {
216 return nil, fmt.Errorf("failed to write state for %s, missing provider type", absAddr)
217 }
218
219 obj := *n.State
220 if obj == nil || obj.Value.IsNull() {
221 // No need to encode anything: we'll just write it directly.
222 state.SetResourceInstanceCurrent(absAddr, nil, n.ProviderAddr)
223 log.Printf("[TRACE] EvalWriteState: removing state object for %s", absAddr)
224 return nil, nil
225 }
226 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
227 // Should never happen, unless our state object is nil
228 panic("EvalWriteState used with pointer to nil ProviderSchema object")
229 }
230
231 if obj != nil {
232 log.Printf("[TRACE] EvalWriteState: writing current state object for %s", absAddr)
233 } else {
234 log.Printf("[TRACE] EvalWriteState: removing current state object for %s", absAddr)
235 }
236
237 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
238 if schema == nil {
239 // It shouldn't be possible to get this far in any real scenario
240 // without a schema, but we might end up here in contrived tests that
241 // fail to set up their world properly.
242 return nil, fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
243 }
244 src, err := obj.Encode(schema.ImpliedType(), currentVersion)
245 if err != nil {
246 return nil, fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
247 }
248
249 state.SetResourceInstanceCurrent(absAddr, src, n.ProviderAddr)
250 return nil, nil
251 }
252
253 // EvalWriteStateDeposed is an EvalNode implementation that writes
254 // an InstanceState out to the Deposed list of a resource in the state.
255 type EvalWriteStateDeposed struct {
256 // Addr is the address of the instance to read state for.
257 Addr addrs.ResourceInstance
258
259 // Key indicates which deposed object to write to.
260 Key states.DeposedKey
261
262 // State is the object state to save.
263 State **states.ResourceInstanceObject
264
265 // ProviderSchema is the schema for the provider given in ProviderAddr.
266 ProviderSchema **ProviderSchema
267
268 // ProviderAddr is the address of the provider configuration that
269 // produced the given object.
270 ProviderAddr addrs.AbsProviderConfig
271 }
272
273 func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) {
274 if n.State == nil {
275 // Note that a pointer _to_ nil is valid here, indicating the total
276 // absense of an object as we'd see during destroy.
277 panic("EvalWriteStateDeposed used with no ResourceInstanceObject")
278 }
279
280 absAddr := n.Addr.Absolute(ctx.Path())
281 key := n.Key
282 state := ctx.State()
283
284 if key == states.NotDeposed {
285 // should never happen
286 return nil, fmt.Errorf("can't save deposed object for %s without a deposed key; this is a bug in Terraform that should be reported", absAddr)
287 }
288
289 obj := *n.State
290 if obj == nil {
291 // No need to encode anything: we'll just write it directly.
292 state.SetResourceInstanceDeposed(absAddr, key, nil, n.ProviderAddr)
293 log.Printf("[TRACE] EvalWriteStateDeposed: removing state object for %s deposed %s", absAddr, key)
294 return nil, nil
295 }
296 if n.ProviderSchema == nil || *n.ProviderSchema == nil {
297 // Should never happen, unless our state object is nil
298 panic("EvalWriteStateDeposed used with no ProviderSchema object")
299 }
300
301 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource())
302 if schema == nil {
303 // It shouldn't be possible to get this far in any real scenario
304 // without a schema, but we might end up here in contrived tests that
305 // fail to set up their world properly.
306 return nil, fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
307 }
308 src, err := obj.Encode(schema.ImpliedType(), currentVersion)
309 if err != nil {
310 return nil, fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
311 }
312
313 log.Printf("[TRACE] EvalWriteStateDeposed: writing state object for %s deposed %s", absAddr, key)
314 state.SetResourceInstanceDeposed(absAddr, key, src, n.ProviderAddr)
315 return nil, nil
316 }
317
318 // EvalDeposeState is an EvalNode implementation that moves the current object
319 // for the given instance to instead be a deposed object, leaving the instance
320 // with no current object.
321 // This is used at the beginning of a create-before-destroy replace action so
322 // that the create can create while preserving the old state of the
323 // to-be-destroyed object.
324 type EvalDeposeState struct {
325 Addr addrs.ResourceInstance
326
327 // ForceKey, if a value other than states.NotDeposed, will be used as the
328 // key for the newly-created deposed object that results from this action.
329 // If set to states.NotDeposed (the zero value), a new unique key will be
330 // allocated.
331 ForceKey states.DeposedKey
332
333 // OutputKey, if non-nil, will be written with the deposed object key that
334 // was generated for the object. This can then be passed to
335 // EvalUndeposeState.Key so it knows which deposed instance to forget.
336 OutputKey *states.DeposedKey
337 }
338
339 // TODO: test
340 func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) {
341 absAddr := n.Addr.Absolute(ctx.Path())
342 state := ctx.State()
343
344 var key states.DeposedKey
345 if n.ForceKey == states.NotDeposed {
346 key = state.DeposeResourceInstanceObject(absAddr)
347 } else {
348 key = n.ForceKey
349 state.DeposeResourceInstanceObjectForceKey(absAddr, key)
350 }
351 log.Printf("[TRACE] EvalDeposeState: prior object for %s now deposed with key %s", absAddr, key)
352
353 if n.OutputKey != nil {
354 *n.OutputKey = key
355 }
356
357 return nil, nil
358 }
359
360 // EvalMaybeRestoreDeposedObject is an EvalNode implementation that will
361 // restore a particular deposed object of the specified resource instance
362 // to be the "current" object if and only if the instance doesn't currently
363 // have a current object.
364 //
365 // This is intended for use when the create leg of a create before destroy
366 // fails with no partial new object: if we didn't take any action, the user
367 // would be left in the unfortunate situation of having no current object
368 // and the previously-workign object now deposed. This EvalNode causes a
369 // better outcome by restoring things to how they were before the replace
370 // operation began.
371 //
372 // The create operation may have produced a partial result even though it
373 // failed and it's important that we don't "forget" that state, so in that
374 // situation the prior object remains deposed and the partial new object
375 // remains the current object, allowing the situation to hopefully be
376 // improved in a subsequent run.
377 type EvalMaybeRestoreDeposedObject struct {
378 Addr addrs.ResourceInstance
379
380 // Key is a pointer to the deposed object key that should be forgotten
381 // from the state, which must be non-nil.
382 Key *states.DeposedKey
383 }
384
385 // TODO: test
386 func (n *EvalMaybeRestoreDeposedObject) Eval(ctx EvalContext) (interface{}, error) {
387 absAddr := n.Addr.Absolute(ctx.Path())
388 dk := *n.Key
389 state := ctx.State()
390
391 restored := state.MaybeRestoreResourceInstanceDeposed(absAddr, dk)
392 if restored {
393 log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s was restored as the current object", absAddr, dk)
394 } else {
395 log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s remains deposed", absAddr, dk)
396 }
397
398 return nil, nil
399 }
400
401 // EvalWriteResourceState is an EvalNode implementation that ensures that
402 // a suitable resource-level state record is present in the state, if that's
403 // required for the "each mode" of that resource.
404 //
405 // This is important primarily for the situation where count = 0, since this
406 // eval is the only change we get to set the resource "each mode" to list
407 // in that case, allowing expression evaluation to see it as a zero-element
408 // list rather than as not set at all.
409 type EvalWriteResourceState struct {
410 Addr addrs.Resource
411 Config *configs.Resource
412 ProviderAddr addrs.AbsProviderConfig
413 }
414
415 // TODO: test
416 func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
417 var diags tfdiags.Diagnostics
418 absAddr := n.Addr.Absolute(ctx.Path())
419 state := ctx.State()
420
421 count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
422 diags = diags.Append(countDiags)
423 if countDiags.HasErrors() {
424 return nil, diags.Err()
425 }
426
427 // Currently we ony support NoEach and EachList, because for_each support
428 // is not fully wired up across Terraform. Once for_each support is added,
429 // we'll need to handle that here too, setting states.EachMap if the
430 // assigned expression is a map.
431 eachMode := states.NoEach
432 if count >= 0 { // -1 signals "count not set"
433 eachMode = states.EachList
434 }
435
436 // This method takes care of all of the business logic of updating this
437 // while ensuring that any existing instances are preserved, etc.
438 state.SetResourceMeta(absAddr, eachMode, n.ProviderAddr)
439
440 return nil, nil
441 }
442
443 // EvalForgetResourceState is an EvalNode implementation that prunes out an
444 // empty resource-level state for a given resource address, or produces an
445 // error if it isn't empty after all.
446 //
447 // This should be the last action taken for a resource that has been removed
448 // from the configuration altogether, to clean up the leftover husk of the
449 // resource in the state after other EvalNodes have destroyed and removed
450 // all of the instances and instance objects beneath it.
451 type EvalForgetResourceState struct {
452 Addr addrs.Resource
453 }
454
455 func (n *EvalForgetResourceState) Eval(ctx EvalContext) (interface{}, error) {
456 absAddr := n.Addr.Absolute(ctx.Path())
457 state := ctx.State()
458
459 pruned := state.RemoveResourceIfEmpty(absAddr)
460 if !pruned {
461 // If this produces an error, it indicates a bug elsewhere in Terraform
462 // -- probably missing graph nodes, graph edges, or
463 // incorrectly-implemented evaluation steps.
464 return nil, fmt.Errorf("orphan resource %s still has a non-empty state after apply; this is a bug in Terraform", absAddr)
465 }
466 log.Printf("[TRACE] EvalForgetResourceState: Pruned husk of %s from state", absAddr)
467
468 return nil, nil
469 }