1package terraform
2
3import (
4	"fmt"
5	"log"
6
7	"github.com/hashicorp/terraform/internal/addrs"
8	"github.com/hashicorp/terraform/internal/configs"
9	"github.com/hashicorp/terraform/internal/plans"
10	"github.com/hashicorp/terraform/internal/plans/objchange"
11	"github.com/hashicorp/terraform/internal/states"
12	"github.com/hashicorp/terraform/internal/tfdiags"
13)
14
15// NodeApplyableResourceInstance represents a resource instance that is
16// "applyable": it is ready to be applied and is represented by a diff.
17//
18// This node is for a specific instance of a resource. It will usually be
19// accompanied in the graph by a NodeApplyableResource representing its
20// containing resource, and should depend on that node to ensure that the
21// state is properly prepared to receive changes to instances.
22type NodeApplyableResourceInstance struct {
23	*NodeAbstractResourceInstance
24
25	graphNodeDeposer // implementation of GraphNodeDeposerConfig
26
27	// If this node is forced to be CreateBeforeDestroy, we need to record that
28	// in the state to.
29	ForceCreateBeforeDestroy bool
30
31	// forceReplace are resource instance addresses where the user wants to
32	// force generating a replace action. This set isn't pre-filtered, so
33	// it might contain addresses that have nothing to do with the resource
34	// that this node represents, which the node itself must therefore ignore.
35	forceReplace []addrs.AbsResourceInstance
36}
37
38var (
39	_ GraphNodeConfigResource     = (*NodeApplyableResourceInstance)(nil)
40	_ GraphNodeResourceInstance   = (*NodeApplyableResourceInstance)(nil)
41	_ GraphNodeCreator            = (*NodeApplyableResourceInstance)(nil)
42	_ GraphNodeReferencer         = (*NodeApplyableResourceInstance)(nil)
43	_ GraphNodeDeposer            = (*NodeApplyableResourceInstance)(nil)
44	_ GraphNodeExecutable         = (*NodeApplyableResourceInstance)(nil)
45	_ GraphNodeAttachDependencies = (*NodeApplyableResourceInstance)(nil)
46)
47
48// CreateBeforeDestroy returns this node's CreateBeforeDestroy status.
49func (n *NodeApplyableResourceInstance) CreateBeforeDestroy() bool {
50	if n.ForceCreateBeforeDestroy {
51		return n.ForceCreateBeforeDestroy
52	}
53
54	if n.Config != nil && n.Config.Managed != nil {
55		return n.Config.Managed.CreateBeforeDestroy
56	}
57
58	return false
59}
60
61func (n *NodeApplyableResourceInstance) ModifyCreateBeforeDestroy(v bool) error {
62	n.ForceCreateBeforeDestroy = v
63	return nil
64}
65
66// GraphNodeCreator
67func (n *NodeApplyableResourceInstance) CreateAddr() *addrs.AbsResourceInstance {
68	addr := n.ResourceInstanceAddr()
69	return &addr
70}
71
72// GraphNodeReferencer, overriding NodeAbstractResourceInstance
73func (n *NodeApplyableResourceInstance) References() []*addrs.Reference {
74	// Start with the usual resource instance implementation
75	ret := n.NodeAbstractResourceInstance.References()
76
77	// Applying a resource must also depend on the destruction of any of its
78	// dependencies, since this may for example affect the outcome of
79	// evaluating an entire list of resources with "count" set (by reducing
80	// the count).
81	//
82	// However, we can't do this in create_before_destroy mode because that
83	// would create a dependency cycle. We make a compromise here of requiring
84	// changes to be updated across two applies in this case, since the first
85	// plan will use the old values.
86	if !n.CreateBeforeDestroy() {
87		for _, ref := range ret {
88			switch tr := ref.Subject.(type) {
89			case addrs.ResourceInstance:
90				newRef := *ref // shallow copy so we can mutate
91				newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
92				newRef.Remaining = nil // can't access attributes of something being destroyed
93				ret = append(ret, &newRef)
94			case addrs.Resource:
95				newRef := *ref // shallow copy so we can mutate
96				newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
97				newRef.Remaining = nil // can't access attributes of something being destroyed
98				ret = append(ret, &newRef)
99			}
100		}
101	}
102
103	return ret
104}
105
106// GraphNodeAttachDependencies
107func (n *NodeApplyableResourceInstance) AttachDependencies(deps []addrs.ConfigResource) {
108	n.Dependencies = deps
109}
110
111// GraphNodeExecutable
112func (n *NodeApplyableResourceInstance) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
113	addr := n.ResourceInstanceAddr()
114
115	if n.Config == nil {
116		// This should not be possible, but we've got here in at least one
117		// case as discussed in the following issue:
118		//    https://github.com/hashicorp/terraform/issues/21258
119		// To avoid an outright crash here, we'll instead return an explicit
120		// error.
121		diags = diags.Append(tfdiags.Sourceless(
122			tfdiags.Error,
123			"Resource node has no configuration attached",
124			fmt.Sprintf(
125				"The graph node for %s has no configuration attached to it. This suggests a bug in Terraform's apply graph builder; please report it!",
126				addr,
127			),
128		))
129		return diags
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.managedResourceExecute(ctx)
136	case addrs.DataResourceMode:
137		return n.dataResourceExecute(ctx)
138	default:
139		panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
140	}
141}
142
143func (n *NodeApplyableResourceInstance) dataResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
144	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
145	diags = diags.Append(err)
146	if diags.HasErrors() {
147		return diags
148	}
149
150	change, err := n.readDiff(ctx, providerSchema)
151	diags = diags.Append(err)
152	if diags.HasErrors() {
153		return diags
154	}
155	// Stop early if we don't actually have a diff
156	if change == nil {
157		return diags
158	}
159
160	// In this particular call to applyDataSource we include our planned
161	// change, which signals that we expect this read to complete fully
162	// with no unknown values; it'll produce an error if not.
163	state, applyDiags := n.applyDataSource(ctx, change)
164	diags = diags.Append(applyDiags)
165	if diags.HasErrors() {
166		return diags
167	}
168
169	diags = diags.Append(n.writeResourceInstanceState(ctx, state, workingState))
170	if diags.HasErrors() {
171		return diags
172	}
173
174	diags = diags.Append(n.writeChange(ctx, nil, ""))
175
176	diags = diags.Append(updateStateHook(ctx))
177	return diags
178}
179
180func (n *NodeApplyableResourceInstance) managedResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) {
181	// Declare a bunch of variables that are used for state during
182	// evaluation. Most of this are written to by-address below.
183	var state *states.ResourceInstanceObject
184	var createBeforeDestroyEnabled bool
185	var deposedKey states.DeposedKey
186
187	addr := n.ResourceInstanceAddr().Resource
188	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
189	diags = diags.Append(err)
190	if diags.HasErrors() {
191		return diags
192	}
193
194	// Get the saved diff for apply
195	diffApply, err := n.readDiff(ctx, providerSchema)
196	diags = diags.Append(err)
197	if diags.HasErrors() {
198		return diags
199	}
200
201	// We don't want to do any destroys
202	// (these are handled by NodeDestroyResourceInstance instead)
203	if diffApply == nil || diffApply.Action == plans.Delete {
204		return diags
205	}
206
207	destroy := (diffApply.Action == plans.Delete || diffApply.Action.IsReplace())
208	// Get the stored action for CBD if we have a plan already
209	createBeforeDestroyEnabled = diffApply.Change.Action == plans.CreateThenDelete
210
211	if destroy && n.CreateBeforeDestroy() {
212		createBeforeDestroyEnabled = true
213	}
214
215	if createBeforeDestroyEnabled {
216		state := ctx.State()
217		if n.PreallocatedDeposedKey == states.NotDeposed {
218			deposedKey = state.DeposeResourceInstanceObject(n.Addr)
219		} else {
220			deposedKey = n.PreallocatedDeposedKey
221			state.DeposeResourceInstanceObjectForceKey(n.Addr, deposedKey)
222		}
223		log.Printf("[TRACE] managedResourceExecute: prior object for %s now deposed with key %s", n.Addr, deposedKey)
224	}
225
226	state, readDiags := n.readResourceInstanceState(ctx, n.ResourceInstanceAddr())
227	diags = diags.Append(readDiags)
228	if diags.HasErrors() {
229		return diags
230	}
231
232	// Get the saved diff
233	diff, err := n.readDiff(ctx, providerSchema)
234	diags = diags.Append(err)
235	if diags.HasErrors() {
236		return diags
237	}
238
239	// Make a new diff, in case we've learned new values in the state
240	// during apply which we can now incorporate.
241	diffApply, _, planDiags := n.plan(ctx, diff, state, false, n.forceReplace)
242	diags = diags.Append(planDiags)
243	if diags.HasErrors() {
244		return diags
245	}
246
247	// Compare the diffs
248	diags = diags.Append(n.checkPlannedChange(ctx, diff, diffApply, providerSchema))
249	if diags.HasErrors() {
250		return diags
251	}
252
253	state, readDiags = n.readResourceInstanceState(ctx, n.ResourceInstanceAddr())
254	diags = diags.Append(readDiags)
255	if diags.HasErrors() {
256		return diags
257	}
258
259	diffApply = reducePlan(addr, diffApply, false)
260	// reducePlan may have simplified our planned change
261	// into a NoOp if it only requires destroying, since destroying
262	// is handled by NodeDestroyResourceInstance.
263	if diffApply == nil || diffApply.Action == plans.NoOp {
264		return diags
265	}
266
267	diags = diags.Append(n.preApplyHook(ctx, diffApply))
268	if diags.HasErrors() {
269		return diags
270	}
271
272	state, applyDiags := n.apply(ctx, state, diffApply, n.Config, n.CreateBeforeDestroy())
273	diags = diags.Append(applyDiags)
274
275	// We clear the change out here so that future nodes don't see a change
276	// that is already complete.
277	err = n.writeChange(ctx, nil, "")
278	if err != nil {
279		return diags.Append(err)
280	}
281
282	state = maybeTainted(addr.Absolute(ctx.Path()), state, diffApply, diags.Err())
283
284	if state != nil {
285		// dependencies are always updated to match the configuration during apply
286		state.Dependencies = n.Dependencies
287	}
288	err = n.writeResourceInstanceState(ctx, state, workingState)
289	if err != nil {
290		return diags.Append(err)
291	}
292
293	// Run Provisioners
294	createNew := (diffApply.Action == plans.Create || diffApply.Action.IsReplace())
295	applyProvisionersDiags := n.evalApplyProvisioners(ctx, state, createNew, configs.ProvisionerWhenCreate)
296	// the provisioner errors count as port of the apply error, so we can bundle the diags
297	diags = diags.Append(applyProvisionersDiags)
298
299	state = maybeTainted(addr.Absolute(ctx.Path()), state, diffApply, diags.Err())
300
301	err = n.writeResourceInstanceState(ctx, state, workingState)
302	if err != nil {
303		return diags.Append(err)
304	}
305
306	if createBeforeDestroyEnabled && diags.HasErrors() {
307		if deposedKey == states.NotDeposed {
308			// This should never happen, and so it always indicates a bug.
309			// We should evaluate this node only if we've previously deposed
310			// an object as part of the same operation.
311			if diffApply != nil {
312				diags = diags.Append(tfdiags.Sourceless(
313					tfdiags.Error,
314					"Attempt to restore non-existent deposed object",
315					fmt.Sprintf(
316						"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This occurred during a %s action. This is a bug in Terraform; please report it!",
317						addr, diffApply.Action,
318					),
319				))
320			} else {
321				diags = diags.Append(tfdiags.Sourceless(
322					tfdiags.Error,
323					"Attempt to restore non-existent deposed object",
324					fmt.Sprintf(
325						"Terraform has encountered a bug where it would need to restore a deposed object for %s without knowing a deposed object key for that object. This is a bug in Terraform; please report it!",
326						addr,
327					),
328				))
329			}
330		} else {
331			restored := ctx.State().MaybeRestoreResourceInstanceDeposed(addr.Absolute(ctx.Path()), deposedKey)
332			if restored {
333				log.Printf("[TRACE] managedResourceExecute: %s deposed object %s was restored as the current object", addr, deposedKey)
334			} else {
335				log.Printf("[TRACE] managedResourceExecute: %s deposed object %s remains deposed", addr, deposedKey)
336			}
337		}
338	}
339
340	diags = diags.Append(n.postApplyHook(ctx, state, diags.Err()))
341	diags = diags.Append(updateStateHook(ctx))
342	return diags
343}
344
345// checkPlannedChange produces errors if the _actual_ expected value is not
346// compatible with what was recorded in the plan.
347//
348// Errors here are most often indicative of a bug in the provider, so our error
349// messages will report with that in mind. It's also possible that there's a bug
350// in Terraform's Core's own "proposed new value" code in EvalDiff.
351func (n *NodeApplyableResourceInstance) checkPlannedChange(ctx EvalContext, plannedChange, actualChange *plans.ResourceInstanceChange, providerSchema *ProviderSchema) tfdiags.Diagnostics {
352	var diags tfdiags.Diagnostics
353	addr := n.ResourceInstanceAddr().Resource
354
355	schema, _ := providerSchema.SchemaForResourceAddr(addr.ContainingResource())
356	if schema == nil {
357		// Should be caught during validation, so we don't bother with a pretty error here
358		diags = diags.Append(fmt.Errorf("provider does not support %q", addr.Resource.Type))
359		return diags
360	}
361
362	absAddr := addr.Absolute(ctx.Path())
363
364	log.Printf("[TRACE] checkPlannedChange: Verifying that actual change (action %s) matches planned change (action %s)", actualChange.Action, plannedChange.Action)
365
366	if plannedChange.Action != actualChange.Action {
367		switch {
368		case plannedChange.Action == plans.Update && actualChange.Action == plans.NoOp:
369			// It's okay for an update to become a NoOp once we've filled in
370			// all of the unknown values, since the final values might actually
371			// match what was there before after all.
372			log.Printf("[DEBUG] After incorporating new values learned so far during apply, %s change has become NoOp", absAddr)
373
374		case (plannedChange.Action == plans.CreateThenDelete && actualChange.Action == plans.DeleteThenCreate) ||
375			(plannedChange.Action == plans.DeleteThenCreate && actualChange.Action == plans.CreateThenDelete):
376			// If the order of replacement changed, then that is a bug in terraform
377			diags = diags.Append(tfdiags.Sourceless(
378				tfdiags.Error,
379				"Terraform produced inconsistent final plan",
380				fmt.Sprintf(
381					"When expanding the plan for %s to include new values learned so far during apply, the planned action changed from %s to %s.\n\nThis is a bug in Terraform and should be reported.",
382					absAddr, plannedChange.Action, actualChange.Action,
383				),
384			))
385		default:
386			diags = diags.Append(tfdiags.Sourceless(
387				tfdiags.Error,
388				"Provider produced inconsistent final plan",
389				fmt.Sprintf(
390					"When expanding the plan for %s to include new values learned so far during apply, provider %q changed the planned action from %s to %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
391					absAddr, n.ResolvedProvider.Provider.String(),
392					plannedChange.Action, actualChange.Action,
393				),
394			))
395		}
396	}
397
398	errs := objchange.AssertObjectCompatible(schema, plannedChange.After, actualChange.After)
399	for _, err := range errs {
400		diags = diags.Append(tfdiags.Sourceless(
401			tfdiags.Error,
402			"Provider produced inconsistent final plan",
403			fmt.Sprintf(
404				"When expanding the plan for %s to include new values learned so far during apply, provider %q produced an invalid new value for %s.\n\nThis is a bug in the provider, which should be reported in the provider's own issue tracker.",
405				absAddr, n.ResolvedProvider.Provider.String(), tfdiags.FormatError(err),
406			),
407		))
408	}
409	return diags
410}
411
412// maybeTainted takes the resource addr, new value, planned change, and possible
413// error from an apply operation and return a new instance object marked as
414// tainted if it appears that a create operation has failed.
415func maybeTainted(addr addrs.AbsResourceInstance, state *states.ResourceInstanceObject, change *plans.ResourceInstanceChange, err error) *states.ResourceInstanceObject {
416	if state == nil || change == nil || err == nil {
417		return state
418	}
419	if state.Status == states.ObjectTainted {
420		log.Printf("[TRACE] maybeTainted: %s was already tainted, so nothing to do", addr)
421		return state
422	}
423	if change.Action == plans.Create {
424		// If there are errors during a _create_ then the object is
425		// in an undefined state, and so we'll mark it as tainted so
426		// we can try again on the next run.
427		//
428		// We don't do this for other change actions because errors
429		// during updates will often not change the remote object at all.
430		// If there _were_ changes prior to the error, it's the provider's
431		// responsibility to record the effect of those changes in the
432		// object value it returned.
433		log.Printf("[TRACE] maybeTainted: %s encountered an error during creation, so it is now marked as tainted", addr)
434		return state.AsTainted()
435	}
436	return state
437}
438