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