6 "github.com/zclconf/go-cty/cty"
8 "github.com/hashicorp/terraform/addrs"
9 "github.com/hashicorp/terraform/configs"
10 "github.com/hashicorp/terraform/plans"
11 "github.com/hashicorp/terraform/providers"
12 "github.com/hashicorp/terraform/states"
13 "github.com/hashicorp/terraform/tfdiags"
16 // NodeApplyableResourceInstance represents a resource instance that is
17 // "applyable": it is ready to be applied and is represented by a diff.
19 // This node is for a specific instance of a resource. It will usually be
20 // accompanied in the graph by a NodeApplyableResource representing its
21 // containing resource, and should depend on that node to ensure that the
22 // state is properly prepared to receive changes to instances.
23 type NodeApplyableResourceInstance struct {
24 *NodeAbstractResourceInstance
26 destroyNode GraphNodeDestroyerCBD
27 graphNodeDeposer // implementation of GraphNodeDeposer
31 _ GraphNodeResource = (*NodeApplyableResourceInstance)(nil)
32 _ GraphNodeResourceInstance = (*NodeApplyableResourceInstance)(nil)
33 _ GraphNodeCreator = (*NodeApplyableResourceInstance)(nil)
34 _ GraphNodeReferencer = (*NodeApplyableResourceInstance)(nil)
35 _ GraphNodeDeposer = (*NodeApplyableResourceInstance)(nil)
36 _ GraphNodeEvalable = (*NodeApplyableResourceInstance)(nil)
39 // GraphNodeAttachDestroyer
40 func (n *NodeApplyableResourceInstance) AttachDestroyNode(d GraphNodeDestroyerCBD) {
44 // createBeforeDestroy checks this nodes config status and the status af any
45 // companion destroy node for CreateBeforeDestroy.
46 func (n *NodeApplyableResourceInstance) createBeforeDestroy() bool {
49 if n.Config != nil && n.Config.Managed != nil {
50 cbd = n.Config.Managed.CreateBeforeDestroy
53 if n.destroyNode != nil {
54 cbd = cbd || n.destroyNode.CreateBeforeDestroy()
61 func (n *NodeApplyableResourceInstance) CreateAddr() *addrs.AbsResourceInstance {
62 addr := n.ResourceInstanceAddr()
66 // GraphNodeReferencer, overriding NodeAbstractResourceInstance
67 func (n *NodeApplyableResourceInstance) References() []*addrs.Reference {
68 // Start with the usual resource instance implementation
69 ret := n.NodeAbstractResourceInstance.References()
71 // Applying a resource must also depend on the destruction of any of its
72 // dependencies, since this may for example affect the outcome of
73 // evaluating an entire list of resources with "count" set (by reducing
76 // However, we can't do this in create_before_destroy mode because that
77 // would create a dependency cycle. We make a compromise here of requiring
78 // changes to be updated across two applies in this case, since the first
79 // plan will use the old values.
80 if !n.createBeforeDestroy() {
81 for _, ref := range ret {
82 switch tr := ref.Subject.(type) {
83 case addrs.ResourceInstance:
84 newRef := *ref // shallow copy so we can mutate
85 newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
86 newRef.Remaining = nil // can't access attributes of something being destroyed
87 ret = append(ret, &newRef)
89 newRef := *ref // shallow copy so we can mutate
90 newRef.Subject = tr.Phase(addrs.ResourceInstancePhaseDestroy)
91 newRef.Remaining = nil // can't access attributes of something being destroyed
92 ret = append(ret, &newRef)
101 func (n *NodeApplyableResourceInstance) EvalTree() EvalNode {
102 addr := n.ResourceInstanceAddr()
104 // State still uses legacy-style internal ids, so we need to shim to get
105 // a suitable key to use.
106 stateId := NewLegacyResourceInstanceAddress(addr).stateId()
108 // Determine the dependencies for the state.
109 stateDeps := n.StateReferences()
112 // This should not be possible, but we've got here in at least one
113 // case as discussed in the following issue:
114 // https://github.com/hashicorp/terraform/issues/21258
115 // To avoid an outright crash here, we'll instead return an explicit
117 var diags tfdiags.Diagnostics
118 diags = diags.Append(tfdiags.Sourceless(
120 "Resource node has no configuration attached",
122 "The graph node for %s has no configuration attached to it. This suggests a bug in Terraform's apply graph builder; please report it!",
127 return &EvalReturnError{
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.evalTreeManagedResource(addr, stateId, stateDeps)
136 case addrs.DataResourceMode:
137 return n.evalTreeDataResource(addr, stateId, stateDeps)
139 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
143 func (n *NodeApplyableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
144 var provider providers.Interface
145 var providerSchema *ProviderSchema
146 var change *plans.ResourceInstanceChange
147 var state *states.ResourceInstanceObject
149 return &EvalSequence{
152 Addr: n.ResolvedProvider,
154 Schema: &providerSchema,
157 // Get the saved diff for apply
160 ProviderSchema: &providerSchema,
164 // Stop early if we don't actually have a diff
166 If: func(ctx EvalContext) (bool, error) {
168 return true, EvalEarlyExitError{}
175 // In this particular call to EvalReadData we include our planned
176 // change, which signals that we expect this read to complete fully
177 // with no unknown values; it'll produce an error if not.
181 Dependencies: n.StateReferences(),
182 Planned: &change, // setting this indicates that the result must be complete
184 ProviderAddr: n.ResolvedProvider,
185 ProviderSchema: &providerSchema,
191 ProviderAddr: n.ResolvedProvider,
192 ProviderSchema: &providerSchema,
196 // Clear the diff now that we've applied it, so
197 // later nodes won't see a diff that's now a no-op.
200 ProviderSchema: &providerSchema,
204 &EvalUpdateStateHook{},
209 func (n *NodeApplyableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance, stateId string, stateDeps []addrs.Referenceable) EvalNode {
210 // Declare a bunch of variables that are used for state during
211 // evaluation. Most of this are written to by-address below.
212 var provider providers.Interface
213 var providerSchema *ProviderSchema
214 var diff, diffApply *plans.ResourceInstanceChange
215 var state *states.ResourceInstanceObject
218 var createBeforeDestroyEnabled bool
219 var configVal cty.Value
220 var deposedKey states.DeposedKey
222 return &EvalSequence{
225 Addr: n.ResolvedProvider,
227 Schema: &providerSchema,
230 // Get the saved diff for apply
233 ProviderSchema: &providerSchema,
237 // We don't want to do any destroys
238 // (these are handled by NodeDestroyResourceInstance instead)
240 If: func(ctx EvalContext) (bool, error) {
241 if diffApply == nil {
242 return true, EvalEarlyExitError{}
244 if diffApply.Action == plans.Delete {
245 return true, EvalEarlyExitError{}
253 If: func(ctx EvalContext) (bool, error) {
255 if diffApply != nil {
256 destroy = (diffApply.Action == plans.Delete || diffApply.Action.IsReplace())
258 if destroy && n.createBeforeDestroy() {
259 createBeforeDestroyEnabled = true
261 return createBeforeDestroyEnabled, nil
263 Then: &EvalDeposeState{
265 ForceKey: n.PreallocatedDeposedKey,
266 OutputKey: &deposedKey,
273 ProviderSchema: &providerSchema,
278 // Get the saved diff
281 ProviderSchema: &providerSchema,
285 // Make a new diff, in case we've learned new values in the state
286 // during apply which we can now incorporate.
291 ProviderAddr: n.ResolvedProvider,
292 ProviderSchema: &providerSchema,
295 OutputChange: &diffApply,
296 OutputValue: &configVal,
301 &EvalCheckPlannedChange{
303 ProviderAddr: n.ResolvedProvider,
304 ProviderSchema: &providerSchema,
310 Addr: n.ResolvedProvider,
312 Schema: &providerSchema,
317 ProviderSchema: &providerSchema,
324 InChange: &diffApply,
326 OutChange: &diffApply,
329 // EvalReduceDiff may have simplified our planned change
330 // into a NoOp if it only requires destroying, since destroying
331 // is handled by NodeDestroyResourceInstance.
333 If: func(ctx EvalContext) (bool, error) {
334 if diffApply == nil || diffApply.Action == plans.NoOp {
335 return true, EvalEarlyExitError{}
342 // Call pre-apply hook
351 Dependencies: n.StateReferences(),
355 ProviderAddr: n.ResolvedProvider,
356 ProviderSchema: &providerSchema,
359 CreateNew: &createNew,
370 ProviderAddr: n.ResolvedProvider,
371 ProviderSchema: &providerSchema,
374 &EvalApplyProvisioners{
376 State: &state, // EvalApplyProvisioners will skip if already tainted
377 ResourceConfig: n.Config,
378 CreateNew: &createNew,
380 When: configs.ProvisionerWhenCreate,
391 ProviderAddr: n.ResolvedProvider,
392 ProviderSchema: &providerSchema,
396 If: func(ctx EvalContext) (bool, error) {
397 return createBeforeDestroyEnabled && err != nil, nil
399 Then: &EvalMaybeRestoreDeposedObject{
405 // We clear the diff out here so that future nodes
406 // don't see a diff that is already complete. There
407 // is no longer a diff!
409 If: func(ctx EvalContext) (bool, error) {
410 if !diff.Action.IsReplace() {
413 if !n.createBeforeDestroy() {
418 Then: &EvalWriteDiff{
420 ProviderSchema: &providerSchema,
430 &EvalUpdateStateHook{},