1package terraform
2
3import (
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.
16type 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
32func (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
78type 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
97func (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.
146type EvalRequireState struct {
147	State **states.ResourceInstanceObject
148}
149
150func (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.
165type EvalUpdateStateHook struct{}
166
167func (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.
190type 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
205func (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.
255type 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
273func (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.
324type 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
340func (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.
377type 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
386func (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.
409type EvalWriteResourceState struct {
410	Addr         addrs.Resource
411	Config       *configs.Resource
412	ProviderAddr addrs.AbsProviderConfig
413}
414
415// TODO: test
416func (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.
451type EvalForgetResourceState struct {
452	Addr addrs.Resource
453}
454
455func (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}
470