]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
15c0b25d AP |
3 | import ( |
4 | "fmt" | |
107c1cdb ND |
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" | |
15c0b25d | 12 | ) |
bae9f6d2 JC |
13 | |
14 | // EvalReadState is an EvalNode implementation that reads the | |
107c1cdb | 15 | // current object for a specific instance in the state. |
bae9f6d2 | 16 | type EvalReadState struct { |
107c1cdb ND |
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 | |
bae9f6d2 JC |
30 | } |
31 | ||
32 | func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
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 | |
bae9f6d2 JC |
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 { | |
107c1cdb ND |
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 | |
bae9f6d2 JC |
95 | } |
96 | ||
97 | func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 104 | |
107c1cdb ND |
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") | |
bae9f6d2 | 108 | } |
107c1cdb ND |
109 | absAddr := n.Addr.Absolute(ctx.Path()) |
110 | log.Printf("[TRACE] EvalReadStateDeposed: reading state for %s deposed object %s", absAddr, n.Key) | |
bae9f6d2 | 111 | |
107c1cdb ND |
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) | |
bae9f6d2 JC |
116 | return nil, nil |
117 | } | |
118 | ||
107c1cdb ND |
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()) | |
bae9f6d2 JC |
135 | if err != nil { |
136 | return nil, err | |
137 | } | |
107c1cdb ND |
138 | if n.Output != nil { |
139 | *n.Output = obj | |
bae9f6d2 | 140 | } |
107c1cdb | 141 | return obj, nil |
bae9f6d2 JC |
142 | } |
143 | ||
107c1cdb ND |
144 | // EvalRequireState is an EvalNode implementation that exits early if the given |
145 | // object is null. | |
bae9f6d2 | 146 | type EvalRequireState struct { |
107c1cdb | 147 | State **states.ResourceInstanceObject |
bae9f6d2 JC |
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 | |
107c1cdb | 156 | if state == nil || state.Value.IsNull() { |
bae9f6d2 JC |
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) { | |
107c1cdb ND |
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() | |
bae9f6d2 JC |
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 | ||
107c1cdb ND |
188 | // EvalWriteState is an EvalNode implementation that saves the given object |
189 | // as the current object for the selected resource instance. | |
bae9f6d2 | 190 | type EvalWriteState struct { |
107c1cdb ND |
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 | |
bae9f6d2 JC |
203 | } |
204 | ||
205 | func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
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 | |
bae9f6d2 JC |
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 { | |
107c1cdb ND |
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 | |
bae9f6d2 JC |
271 | } |
272 | ||
273 | func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 279 | |
107c1cdb ND |
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) | |
bae9f6d2 JC |
311 | } |
312 | ||
107c1cdb ND |
313 | log.Printf("[TRACE] EvalWriteStateDeposed: writing state object for %s deposed %s", absAddr, key) |
314 | state.SetResourceInstanceDeposed(absAddr, key, src, n.ProviderAddr) | |
bae9f6d2 JC |
315 | return nil, nil |
316 | } | |
317 | ||
107c1cdb ND |
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. | |
bae9f6d2 | 324 | type EvalDeposeState struct { |
107c1cdb ND |
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 | |
bae9f6d2 JC |
337 | } |
338 | ||
339 | // TODO: test | |
340 | func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) { | |
107c1cdb ND |
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) | |
bae9f6d2 | 350 | } |
107c1cdb | 351 | log.Printf("[TRACE] EvalDeposeState: prior object for %s now deposed with key %s", absAddr, key) |
bae9f6d2 | 352 | |
107c1cdb ND |
353 | if n.OutputKey != nil { |
354 | *n.OutputKey = key | |
bae9f6d2 JC |
355 | } |
356 | ||
bae9f6d2 JC |
357 | return nil, nil |
358 | } | |
359 | ||
107c1cdb ND |
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 | |
bae9f6d2 JC |
383 | } |
384 | ||
385 | // TODO: test | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 397 | |
107c1cdb ND |
398 | return nil, nil |
399 | } | |
bae9f6d2 | 400 | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 414 | |
107c1cdb ND |
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() | |
bae9f6d2 JC |
425 | } |
426 | ||
107c1cdb ND |
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 | |
bae9f6d2 JC |
434 | } |
435 | ||
107c1cdb ND |
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) | |
bae9f6d2 JC |
467 | |
468 | return nil, nil | |
469 | } |