1package terraform
2
3import (
4	"fmt"
5	"log"
6
7	"github.com/hashicorp/terraform/internal/addrs"
8	"github.com/hashicorp/terraform/internal/dag"
9	"github.com/hashicorp/terraform/internal/plans"
10	"github.com/hashicorp/terraform/internal/states"
11	"github.com/hashicorp/terraform/internal/tfdiags"
12)
13
14// ConcreteResourceInstanceDeposedNodeFunc is a callback type used to convert
15// an abstract resource instance to a concrete one of some type that has
16// an associated deposed object key.
17type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex
18
19type GraphNodeDeposedResourceInstanceObject interface {
20	DeposedInstanceObjectKey() states.DeposedKey
21}
22
23// NodePlanDeposedResourceInstanceObject represents deposed resource
24// instance objects during plan. These are distinct from the primary object
25// for each resource instance since the only valid operation to do with them
26// is to destroy them.
27//
28// This node type is also used during the refresh walk to ensure that the
29// record of a deposed object is up-to-date before we plan to destroy it.
30type NodePlanDeposedResourceInstanceObject struct {
31	*NodeAbstractResourceInstance
32	DeposedKey states.DeposedKey
33
34	// skipRefresh indicates that we should skip refreshing individual instances
35	skipRefresh bool
36
37	// skipPlanChanges indicates we should skip trying to plan change actions
38	// for any instances.
39	skipPlanChanges bool
40}
41
42var (
43	_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
44	_ GraphNodeConfigResource                = (*NodePlanDeposedResourceInstanceObject)(nil)
45	_ GraphNodeResourceInstance              = (*NodePlanDeposedResourceInstanceObject)(nil)
46	_ GraphNodeReferenceable                 = (*NodePlanDeposedResourceInstanceObject)(nil)
47	_ GraphNodeReferencer                    = (*NodePlanDeposedResourceInstanceObject)(nil)
48	_ GraphNodeExecutable                    = (*NodePlanDeposedResourceInstanceObject)(nil)
49	_ GraphNodeProviderConsumer              = (*NodePlanDeposedResourceInstanceObject)(nil)
50	_ GraphNodeProvisionerConsumer           = (*NodePlanDeposedResourceInstanceObject)(nil)
51)
52
53func (n *NodePlanDeposedResourceInstanceObject) Name() string {
54	return fmt.Sprintf("%s (deposed %s)", n.ResourceInstanceAddr().String(), n.DeposedKey)
55}
56
57func (n *NodePlanDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
58	return n.DeposedKey
59}
60
61// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
62func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
63	// Deposed objects don't participate in references.
64	return nil
65}
66
67// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
68func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference {
69	// We don't evaluate configuration for deposed objects, so they effectively
70	// make no references.
71	return nil
72}
73
74// GraphNodeEvalable impl.
75func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
76	log.Printf("[TRACE] NodePlanDeposedResourceInstanceObject: planning %s deposed object %s", n.Addr, n.DeposedKey)
77
78	// Read the state for the deposed resource instance
79	state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
80	diags = diags.Append(err)
81	if diags.HasErrors() {
82		return diags
83	}
84
85	// Note any upgrades that readResourceInstanceState might've done in the
86	// prevRunState, so that it'll conform to current schema.
87	diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, prevRunState))
88	if diags.HasErrors() {
89		return diags
90	}
91	// Also the refreshState, because that should still reflect schema upgrades
92	// even if not refreshing.
93	diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, refreshState))
94	if diags.HasErrors() {
95		return diags
96	}
97
98	// We don't refresh during the planDestroy walk, since that is only adding
99	// the destroy changes to the plan and the provider will not be configured
100	// at this point. The other nodes use separate types for plan and destroy,
101	// while deposed instances are always a destroy operation, so the logic
102	// here is a bit overloaded.
103	if !n.skipRefresh && op != walkPlanDestroy {
104		// Refresh this object even though it is going to be destroyed, in
105		// case it's already been deleted outside of Terraform. If this is a
106		// normal plan, providers expect a Read request to remove missing
107		// resources from the plan before apply, and may not handle a missing
108		// resource during Delete correctly. If this is a simple refresh,
109		// Terraform is expected to remove the missing resource from the state
110		// entirely
111		refreshedState, refreshDiags := n.refresh(ctx, n.DeposedKey, state)
112		diags = diags.Append(refreshDiags)
113		if diags.HasErrors() {
114			return diags
115		}
116
117		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, refreshedState, refreshState))
118		if diags.HasErrors() {
119			return diags
120		}
121
122		// If we refreshed then our subsequent planning should be in terms of
123		// the new object, not the original object.
124		state = refreshedState
125	}
126
127	if !n.skipPlanChanges {
128		var change *plans.ResourceInstanceChange
129		change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
130		diags = diags.Append(destroyPlanDiags)
131		if diags.HasErrors() {
132			return diags
133		}
134
135		// NOTE: We don't check prevent_destroy for deposed objects, even
136		// though we would do so here for a "current" object, because
137		// if we've reached a point where an object is already deposed then
138		// we've already planned and partially-executed a create_before_destroy
139		// replace and we would've checked prevent_destroy at that point. We're
140		// now just need to get the deposed object destroyed, because there
141		// should be a new object already serving as its replacement.
142
143		diags = diags.Append(n.writeChange(ctx, change, n.DeposedKey))
144		if diags.HasErrors() {
145			return diags
146		}
147
148		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, nil, workingState))
149	} else {
150		// The working state should at least be updated with the result
151		// of upgrading and refreshing from above.
152		diags = diags.Append(n.writeResourceInstanceStateDeposed(ctx, n.DeposedKey, state, workingState))
153	}
154
155	return diags
156}
157
158// NodeDestroyDeposedResourceInstanceObject represents deposed resource
159// instance objects during apply. Nodes of this type are inserted by
160// DiffTransformer when the planned changeset contains "delete" changes for
161// deposed instance objects, and its only supported operation is to destroy
162// and then forget the associated object.
163type NodeDestroyDeposedResourceInstanceObject struct {
164	*NodeAbstractResourceInstance
165	DeposedKey states.DeposedKey
166}
167
168var (
169	_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
170	_ GraphNodeConfigResource                = (*NodeDestroyDeposedResourceInstanceObject)(nil)
171	_ GraphNodeResourceInstance              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
172	_ GraphNodeDestroyer                     = (*NodeDestroyDeposedResourceInstanceObject)(nil)
173	_ GraphNodeDestroyerCBD                  = (*NodeDestroyDeposedResourceInstanceObject)(nil)
174	_ GraphNodeReferenceable                 = (*NodeDestroyDeposedResourceInstanceObject)(nil)
175	_ GraphNodeReferencer                    = (*NodeDestroyDeposedResourceInstanceObject)(nil)
176	_ GraphNodeExecutable                    = (*NodeDestroyDeposedResourceInstanceObject)(nil)
177	_ GraphNodeProviderConsumer              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
178	_ GraphNodeProvisionerConsumer           = (*NodeDestroyDeposedResourceInstanceObject)(nil)
179)
180
181func (n *NodeDestroyDeposedResourceInstanceObject) Name() string {
182	return fmt.Sprintf("%s (destroy deposed %s)", n.ResourceInstanceAddr(), n.DeposedKey)
183}
184
185func (n *NodeDestroyDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
186	return n.DeposedKey
187}
188
189// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
190func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
191	// Deposed objects don't participate in references.
192	return nil
193}
194
195// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
196func (n *NodeDestroyDeposedResourceInstanceObject) References() []*addrs.Reference {
197	// We don't evaluate configuration for deposed objects, so they effectively
198	// make no references.
199	return nil
200}
201
202// GraphNodeDestroyer
203func (n *NodeDestroyDeposedResourceInstanceObject) DestroyAddr() *addrs.AbsResourceInstance {
204	addr := n.ResourceInstanceAddr()
205	return &addr
206}
207
208// GraphNodeDestroyerCBD
209func (n *NodeDestroyDeposedResourceInstanceObject) CreateBeforeDestroy() bool {
210	// A deposed instance is always CreateBeforeDestroy by definition, since
211	// we use deposed only to handle create-before-destroy.
212	return true
213}
214
215// GraphNodeDestroyerCBD
216func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v bool) error {
217	if !v {
218		// Should never happen: deposed instances are _always_ create_before_destroy.
219		return fmt.Errorf("can't deactivate create_before_destroy for a deposed instance")
220	}
221	return nil
222}
223
224// GraphNodeExecutable impl.
225func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
226	var change *plans.ResourceInstanceChange
227
228	// Read the state for the deposed resource instance
229	state, err := n.readResourceInstanceStateDeposed(ctx, n.Addr, n.DeposedKey)
230	if err != nil {
231		return diags.Append(err)
232	}
233
234	if state == nil {
235		diags = diags.Append(fmt.Errorf("missing deposed state for %s (%s)", n.Addr, n.DeposedKey))
236		return diags
237	}
238
239	change, destroyPlanDiags := n.planDestroy(ctx, state, n.DeposedKey)
240	diags = diags.Append(destroyPlanDiags)
241	if diags.HasErrors() {
242		return diags
243	}
244
245	// Call pre-apply hook
246	diags = diags.Append(n.preApplyHook(ctx, change))
247	if diags.HasErrors() {
248		return diags
249	}
250
251	// we pass a nil configuration to apply because we are destroying
252	state, applyDiags := n.apply(ctx, state, change, nil, false)
253	diags = diags.Append(applyDiags)
254	// don't return immediately on errors, we need to handle the state
255
256	// Always write the resource back to the state deposed. If it
257	// was successfully destroyed it will be pruned. If it was not, it will
258	// be caught on the next run.
259	writeDiags := n.writeResourceInstanceState(ctx, state)
260	diags.Append(writeDiags)
261	if diags.HasErrors() {
262		return diags
263	}
264
265	diags = diags.Append(n.postApplyHook(ctx, state, diags.Err()))
266
267	return diags.Append(updateStateHook(ctx))
268}
269
270// GraphNodeDeposer is an optional interface implemented by graph nodes that
271// might create a single new deposed object for a specific associated resource
272// instance, allowing a caller to optionally pre-allocate a DeposedKey for
273// it.
274type GraphNodeDeposer interface {
275	// SetPreallocatedDeposedKey will be called during graph construction
276	// if a particular node must use a pre-allocated deposed key if/when it
277	// "deposes" the current object of its associated resource instance.
278	SetPreallocatedDeposedKey(key states.DeposedKey)
279}
280
281// graphNodeDeposer is an embeddable implementation of GraphNodeDeposer.
282// Embed it in a node type to get automatic support for it, and then access
283// the field PreallocatedDeposedKey to access any pre-allocated key.
284type graphNodeDeposer struct {
285	PreallocatedDeposedKey states.DeposedKey
286}
287
288func (n *graphNodeDeposer) SetPreallocatedDeposedKey(key states.DeposedKey) {
289	n.PreallocatedDeposedKey = key
290}
291
292func (n *NodeDestroyDeposedResourceInstanceObject) writeResourceInstanceState(ctx EvalContext, obj *states.ResourceInstanceObject) error {
293	absAddr := n.Addr
294	key := n.DeposedKey
295	state := ctx.State()
296
297	if key == states.NotDeposed {
298		// should never happen
299		return fmt.Errorf("can't save deposed object for %s without a deposed key; this is a bug in Terraform that should be reported", absAddr)
300	}
301
302	if obj == nil {
303		// No need to encode anything: we'll just write it directly.
304		state.SetResourceInstanceDeposed(absAddr, key, nil, n.ResolvedProvider)
305		log.Printf("[TRACE] writeResourceInstanceStateDeposed: removing state object for %s deposed %s", absAddr, key)
306		return nil
307	}
308
309	_, providerSchema, err := getProvider(ctx, n.ResolvedProvider)
310	if err != nil {
311		return err
312	}
313	if providerSchema == nil {
314		// Should never happen, unless our state object is nil
315		panic("writeResourceInstanceStateDeposed used with no ProviderSchema object")
316	}
317
318	schema, currentVersion := providerSchema.SchemaForResourceAddr(absAddr.ContainingResource().Resource)
319	if schema == nil {
320		// It shouldn't be possible to get this far in any real scenario
321		// without a schema, but we might end up here in contrived tests that
322		// fail to set up their world properly.
323		return fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr)
324	}
325	src, err := obj.Encode(schema.ImpliedType(), currentVersion)
326	if err != nil {
327		return fmt.Errorf("failed to encode %s in state: %s", absAddr, err)
328	}
329
330	log.Printf("[TRACE] writeResourceInstanceStateDeposed: writing state object for %s deposed %s", absAddr, key)
331	state.SetResourceInstanceDeposed(absAddr, key, src, n.ResolvedProvider)
332	return nil
333}
334