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