1package terraform
2
3import (
4	"fmt"
5
6	"github.com/hashicorp/terraform-plugin-sdk/internal/addrs"
7	"github.com/hashicorp/terraform-plugin-sdk/internal/dag"
8	"github.com/hashicorp/terraform-plugin-sdk/internal/plans"
9	"github.com/hashicorp/terraform-plugin-sdk/internal/providers"
10	"github.com/hashicorp/terraform-plugin-sdk/internal/states"
11)
12
13// ConcreteResourceInstanceDeposedNodeFunc is a callback type used to convert
14// an abstract resource instance to a concrete one of some type that has
15// an associated deposed object key.
16type ConcreteResourceInstanceDeposedNodeFunc func(*NodeAbstractResourceInstance, states.DeposedKey) dag.Vertex
17
18type GraphNodeDeposedResourceInstanceObject interface {
19	DeposedInstanceObjectKey() states.DeposedKey
20}
21
22// NodePlanDeposedResourceInstanceObject represents deposed resource
23// instance objects during plan. These are distinct from the primary object
24// for each resource instance since the only valid operation to do with them
25// is to destroy them.
26//
27// This node type is also used during the refresh walk to ensure that the
28// record of a deposed object is up-to-date before we plan to destroy it.
29type NodePlanDeposedResourceInstanceObject struct {
30	*NodeAbstractResourceInstance
31	DeposedKey states.DeposedKey
32}
33
34var (
35	_ GraphNodeDeposedResourceInstanceObject = (*NodePlanDeposedResourceInstanceObject)(nil)
36	_ GraphNodeResource                      = (*NodePlanDeposedResourceInstanceObject)(nil)
37	_ GraphNodeResourceInstance              = (*NodePlanDeposedResourceInstanceObject)(nil)
38	_ GraphNodeReferenceable                 = (*NodePlanDeposedResourceInstanceObject)(nil)
39	_ GraphNodeReferencer                    = (*NodePlanDeposedResourceInstanceObject)(nil)
40	_ GraphNodeEvalable                      = (*NodePlanDeposedResourceInstanceObject)(nil)
41	_ GraphNodeProviderConsumer              = (*NodePlanDeposedResourceInstanceObject)(nil)
42	_ GraphNodeProvisionerConsumer           = (*NodePlanDeposedResourceInstanceObject)(nil)
43)
44
45func (n *NodePlanDeposedResourceInstanceObject) Name() string {
46	return fmt.Sprintf("%s (deposed %s)", n.ResourceInstanceAddr().String(), n.DeposedKey)
47}
48
49func (n *NodePlanDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
50	return n.DeposedKey
51}
52
53// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
54func (n *NodePlanDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
55	// Deposed objects don't participate in references.
56	return nil
57}
58
59// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
60func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference {
61	// We don't evaluate configuration for deposed objects, so they effectively
62	// make no references.
63	return nil
64}
65
66// GraphNodeEvalable impl.
67func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode {
68	addr := n.ResourceInstanceAddr()
69
70	var provider providers.Interface
71	var providerSchema *ProviderSchema
72	var state *states.ResourceInstanceObject
73
74	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
75
76	// During the refresh walk we will ensure that our record of the deposed
77	// object is up-to-date. If it was already deleted outside of Terraform
78	// then this will remove it from state and thus avoid us planning a
79	// destroy for it during the subsequent plan walk.
80	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
81		Ops: []walkOperation{walkRefresh},
82		Node: &EvalSequence{
83			Nodes: []EvalNode{
84				&EvalGetProvider{
85					Addr:   n.ResolvedProvider,
86					Output: &provider,
87					Schema: &providerSchema,
88				},
89				&EvalReadStateDeposed{
90					Addr:           addr.Resource,
91					Provider:       &provider,
92					ProviderSchema: &providerSchema,
93					Key:            n.DeposedKey,
94					Output:         &state,
95				},
96				&EvalRefresh{
97					Addr:           addr.Resource,
98					ProviderAddr:   n.ResolvedProvider,
99					Provider:       &provider,
100					ProviderSchema: &providerSchema,
101					State:          &state,
102					Output:         &state,
103				},
104				&EvalWriteStateDeposed{
105					Addr:           addr.Resource,
106					Key:            n.DeposedKey,
107					ProviderAddr:   n.ResolvedProvider,
108					ProviderSchema: &providerSchema,
109					State:          &state,
110				},
111			},
112		},
113	})
114
115	// During the plan walk we always produce a planned destroy change, because
116	// destroying is the only supported action for deposed objects.
117	var change *plans.ResourceInstanceChange
118	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
119		Ops: []walkOperation{walkPlan, walkPlanDestroy},
120		Node: &EvalSequence{
121			Nodes: []EvalNode{
122				&EvalGetProvider{
123					Addr:   n.ResolvedProvider,
124					Output: &provider,
125					Schema: &providerSchema,
126				},
127				&EvalReadStateDeposed{
128					Addr:           addr.Resource,
129					Output:         &state,
130					Key:            n.DeposedKey,
131					Provider:       &provider,
132					ProviderSchema: &providerSchema,
133				},
134				&EvalDiffDestroy{
135					Addr:         addr.Resource,
136					ProviderAddr: n.ResolvedProvider,
137					DeposedKey:   n.DeposedKey,
138					State:        &state,
139					Output:       &change,
140				},
141				&EvalWriteDiff{
142					Addr:           addr.Resource,
143					DeposedKey:     n.DeposedKey,
144					ProviderSchema: &providerSchema,
145					Change:         &change,
146				},
147				// Since deposed objects cannot be referenced by expressions
148				// elsewhere, we don't need to also record the planned new
149				// state in this case.
150			},
151		},
152	})
153
154	return seq
155}
156
157// NodeDestroyDeposedResourceInstanceObject represents deposed resource
158// instance objects during apply. Nodes of this type are inserted by
159// DiffTransformer when the planned changeset contains "delete" changes for
160// deposed instance objects, and its only supported operation is to destroy
161// and then forget the associated object.
162type NodeDestroyDeposedResourceInstanceObject struct {
163	*NodeAbstractResourceInstance
164	DeposedKey states.DeposedKey
165}
166
167var (
168	_ GraphNodeDeposedResourceInstanceObject = (*NodeDestroyDeposedResourceInstanceObject)(nil)
169	_ GraphNodeResource                      = (*NodeDestroyDeposedResourceInstanceObject)(nil)
170	_ GraphNodeResourceInstance              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
171	_ GraphNodeDestroyer                     = (*NodeDestroyDeposedResourceInstanceObject)(nil)
172	_ GraphNodeDestroyerCBD                  = (*NodeDestroyDeposedResourceInstanceObject)(nil)
173	_ GraphNodeReferenceable                 = (*NodeDestroyDeposedResourceInstanceObject)(nil)
174	_ GraphNodeReferencer                    = (*NodeDestroyDeposedResourceInstanceObject)(nil)
175	_ GraphNodeEvalable                      = (*NodeDestroyDeposedResourceInstanceObject)(nil)
176	_ GraphNodeProviderConsumer              = (*NodeDestroyDeposedResourceInstanceObject)(nil)
177	_ GraphNodeProvisionerConsumer           = (*NodeDestroyDeposedResourceInstanceObject)(nil)
178)
179
180func (n *NodeDestroyDeposedResourceInstanceObject) Name() string {
181	return fmt.Sprintf("%s (destroy deposed %s)", n.Addr.String(), n.DeposedKey)
182}
183
184func (n *NodeDestroyDeposedResourceInstanceObject) DeposedInstanceObjectKey() states.DeposedKey {
185	return n.DeposedKey
186}
187
188// GraphNodeReferenceable implementation, overriding the one from NodeAbstractResourceInstance
189func (n *NodeDestroyDeposedResourceInstanceObject) ReferenceableAddrs() []addrs.Referenceable {
190	// Deposed objects don't participate in references.
191	return nil
192}
193
194// GraphNodeReferencer implementation, overriding the one from NodeAbstractResourceInstance
195func (n *NodeDestroyDeposedResourceInstanceObject) References() []*addrs.Reference {
196	// We don't evaluate configuration for deposed objects, so they effectively
197	// make no references.
198	return nil
199}
200
201// GraphNodeDestroyer
202func (n *NodeDestroyDeposedResourceInstanceObject) DestroyAddr() *addrs.AbsResourceInstance {
203	addr := n.ResourceInstanceAddr()
204	return &addr
205}
206
207// GraphNodeDestroyerCBD
208func (n *NodeDestroyDeposedResourceInstanceObject) CreateBeforeDestroy() bool {
209	// A deposed instance is always CreateBeforeDestroy by definition, since
210	// we use deposed only to handle create-before-destroy.
211	return true
212}
213
214// GraphNodeDestroyerCBD
215func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v bool) error {
216	if !v {
217		// Should never happen: deposed instances are _always_ create_before_destroy.
218		return fmt.Errorf("can't deactivate create_before_destroy for a deposed instance")
219	}
220	return nil
221}
222
223// GraphNodeEvalable impl.
224func (n *NodeDestroyDeposedResourceInstanceObject) EvalTree() EvalNode {
225	addr := n.ResourceInstanceAddr()
226
227	var provider providers.Interface
228	var providerSchema *ProviderSchema
229	var state *states.ResourceInstanceObject
230	var change *plans.ResourceInstanceChange
231	var err error
232
233	return &EvalSequence{
234		Nodes: []EvalNode{
235			&EvalGetProvider{
236				Addr:   n.ResolvedProvider,
237				Output: &provider,
238				Schema: &providerSchema,
239			},
240			&EvalReadStateDeposed{
241				Addr:           addr.Resource,
242				Output:         &state,
243				Key:            n.DeposedKey,
244				Provider:       &provider,
245				ProviderSchema: &providerSchema,
246			},
247			&EvalDiffDestroy{
248				Addr:         addr.Resource,
249				ProviderAddr: n.ResolvedProvider,
250				State:        &state,
251				Output:       &change,
252			},
253			// Call pre-apply hook
254			&EvalApplyPre{
255				Addr:   addr.Resource,
256				State:  &state,
257				Change: &change,
258			},
259			&EvalApply{
260				Addr:           addr.Resource,
261				Config:         nil, // No configuration because we are destroying
262				State:          &state,
263				Change:         &change,
264				Provider:       &provider,
265				ProviderAddr:   n.ResolvedProvider,
266				ProviderSchema: &providerSchema,
267				Output:         &state,
268				Error:          &err,
269			},
270			// Always write the resource back to the state deposed... if it
271			// was successfully destroyed it will be pruned. If it was not, it will
272			// be caught on the next run.
273			&EvalWriteStateDeposed{
274				Addr:           addr.Resource,
275				Key:            n.DeposedKey,
276				ProviderAddr:   n.ResolvedProvider,
277				ProviderSchema: &providerSchema,
278				State:          &state,
279			},
280			&EvalApplyPost{
281				Addr:  addr.Resource,
282				State: &state,
283				Error: &err,
284			},
285			&EvalReturnError{
286				Error: &err,
287			},
288			&EvalUpdateStateHook{},
289		},
290	}
291}
292
293// GraphNodeDeposer is an optional interface implemented by graph nodes that
294// might create a single new deposed object for a specific associated resource
295// instance, allowing a caller to optionally pre-allocate a DeposedKey for
296// it.
297type GraphNodeDeposer interface {
298	// SetPreallocatedDeposedKey will be called during graph construction
299	// if a particular node must use a pre-allocated deposed key if/when it
300	// "deposes" the current object of its associated resource instance.
301	SetPreallocatedDeposedKey(key states.DeposedKey)
302}
303
304// graphNodeDeposer is an embeddable implementation of GraphNodeDeposer.
305// Embed it in a node type to get automatic support for it, and then access
306// the field PreallocatedDeposedKey to access any pre-allocated key.
307type graphNodeDeposer struct {
308	PreallocatedDeposedKey states.DeposedKey
309}
310
311func (n *graphNodeDeposer) SetPreallocatedDeposedKey(key states.DeposedKey) {
312	n.PreallocatedDeposedKey = key
313}
314