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