github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/terraform/node_resource_plan_orphan.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/plans" 9 "github.com/hashicorp/terraform/internal/states" 10 "github.com/hashicorp/terraform/internal/tfdiags" 11 ) 12 13 // NodePlannableResourceInstanceOrphan represents a resource that is "applyable": 14 // it is ready to be applied and is represented by a diff. 15 type NodePlannableResourceInstanceOrphan struct { 16 *NodeAbstractResourceInstance 17 18 // skipRefresh indicates that we should skip refreshing individual instances 19 skipRefresh bool 20 21 // skipPlanChanges indicates we should skip trying to plan change actions 22 // for any instances. 23 skipPlanChanges bool 24 } 25 26 var ( 27 _ GraphNodeModuleInstance = (*NodePlannableResourceInstanceOrphan)(nil) 28 _ GraphNodeReferenceable = (*NodePlannableResourceInstanceOrphan)(nil) 29 _ GraphNodeReferencer = (*NodePlannableResourceInstanceOrphan)(nil) 30 _ GraphNodeConfigResource = (*NodePlannableResourceInstanceOrphan)(nil) 31 _ GraphNodeResourceInstance = (*NodePlannableResourceInstanceOrphan)(nil) 32 _ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstanceOrphan)(nil) 33 _ GraphNodeAttachResourceState = (*NodePlannableResourceInstanceOrphan)(nil) 34 _ GraphNodeExecutable = (*NodePlannableResourceInstanceOrphan)(nil) 35 _ GraphNodeProviderConsumer = (*NodePlannableResourceInstanceOrphan)(nil) 36 ) 37 38 func (n *NodePlannableResourceInstanceOrphan) Name() string { 39 return n.ResourceInstanceAddr().String() + " (orphan)" 40 } 41 42 // GraphNodeExecutable 43 func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOperation) tfdiags.Diagnostics { 44 addr := n.ResourceInstanceAddr() 45 46 // Eval info is different depending on what kind of resource this is 47 switch addr.Resource.Resource.Mode { 48 case addrs.ManagedResourceMode: 49 return n.managedResourceExecute(ctx) 50 case addrs.DataResourceMode: 51 return n.dataResourceExecute(ctx) 52 default: 53 panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 54 } 55 } 56 57 func (n *NodePlannableResourceInstanceOrphan) ProvidedBy() (addr addrs.ProviderConfig, exact bool) { 58 if n.Addr.Resource.Resource.Mode == addrs.DataResourceMode { 59 // indicate that this node does not require a configured provider 60 return nil, true 61 } 62 return n.NodeAbstractResourceInstance.ProvidedBy() 63 } 64 65 func (n *NodePlannableResourceInstanceOrphan) dataResourceExecute(ctx EvalContext) tfdiags.Diagnostics { 66 // A data source that is no longer in the config is removed from the state 67 log.Printf("[TRACE] NodePlannableResourceInstanceOrphan: removing state object for %s", n.Addr) 68 69 // we need to update both the refresh state to refresh the current data 70 // source, and the working state for plan-time evaluations. 71 refreshState := ctx.RefreshState() 72 refreshState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) 73 74 workingState := ctx.State() 75 workingState.SetResourceInstanceCurrent(n.Addr, nil, n.ResolvedProvider) 76 return nil 77 } 78 79 func (n *NodePlannableResourceInstanceOrphan) managedResourceExecute(ctx EvalContext) (diags tfdiags.Diagnostics) { 80 addr := n.ResourceInstanceAddr() 81 82 oldState, readDiags := n.readResourceInstanceState(ctx, addr) 83 diags = diags.Append(readDiags) 84 if diags.HasErrors() { 85 return diags 86 } 87 88 // Note any upgrades that readResourceInstanceState might've done in the 89 // prevRunState, so that it'll conform to current schema. 90 diags = diags.Append(n.writeResourceInstanceState(ctx, oldState, prevRunState)) 91 if diags.HasErrors() { 92 return diags 93 } 94 // Also the refreshState, because that should still reflect schema upgrades 95 // even if not refreshing. 96 diags = diags.Append(n.writeResourceInstanceState(ctx, oldState, refreshState)) 97 if diags.HasErrors() { 98 return diags 99 } 100 101 if !n.skipRefresh { 102 // Refresh this instance even though it is going to be destroyed, in 103 // order to catch missing resources. If this is a normal plan, 104 // providers expect a Read request to remove missing resources from the 105 // plan before apply, and may not handle a missing resource during 106 // Delete correctly. If this is a simple refresh, Terraform is 107 // expected to remove the missing resource from the state entirely 108 refreshedState, refreshDiags := n.refresh(ctx, states.NotDeposed, oldState) 109 diags = diags.Append(refreshDiags) 110 if diags.HasErrors() { 111 return diags 112 } 113 114 diags = diags.Append(n.writeResourceInstanceState(ctx, refreshedState, refreshState)) 115 if diags.HasErrors() { 116 return diags 117 } 118 119 // If we refreshed then our subsequent planning should be in terms of 120 // the new object, not the original object. 121 oldState = refreshedState 122 } 123 124 if !n.skipPlanChanges { 125 var change *plans.ResourceInstanceChange 126 change, destroyPlanDiags := n.planDestroy(ctx, oldState, "") 127 diags = diags.Append(destroyPlanDiags) 128 if diags.HasErrors() { 129 return diags 130 } 131 132 diags = diags.Append(n.checkPreventDestroy(change)) 133 if diags.HasErrors() { 134 return diags 135 } 136 137 // We might be able to offer an approximate reason for why we are 138 // planning to delete this object. (This is best-effort; we might 139 // sometimes not have a reason.) 140 change.ActionReason = n.deleteActionReason(ctx) 141 142 diags = diags.Append(n.writeChange(ctx, change, "")) 143 if diags.HasErrors() { 144 return diags 145 } 146 147 diags = diags.Append(n.writeResourceInstanceState(ctx, nil, workingState)) 148 } else { 149 // The working state should at least be updated with the result 150 // of upgrading and refreshing from above. 151 diags = diags.Append(n.writeResourceInstanceState(ctx, oldState, workingState)) 152 } 153 154 return diags 155 } 156 157 func (n *NodePlannableResourceInstanceOrphan) deleteActionReason(ctx EvalContext) plans.ResourceInstanceChangeActionReason { 158 cfg := n.Config 159 if cfg == nil { 160 if !n.Addr.Equal(n.prevRunAddr(ctx)) { 161 // This means the resource was moved - see also 162 // ResourceInstanceChange.Moved() which calculates 163 // this the same way. 164 return plans.ResourceInstanceDeleteBecauseNoMoveTarget 165 } 166 167 return plans.ResourceInstanceDeleteBecauseNoResourceConfig 168 } 169 170 // If this is a resource instance inside a module instance that's no 171 // longer declared then we will have a config (because config isn't 172 // instance-specific) but the expander will know that our resource 173 // address's module path refers to an undeclared module instance. 174 if expander := ctx.InstanceExpander(); expander != nil { // (sometimes nil in MockEvalContext in tests) 175 validModuleAddr := expander.GetDeepestExistingModuleInstance(n.Addr.Module) 176 if len(validModuleAddr) != len(n.Addr.Module) { 177 // If we get here then at least one step in the resource's module 178 // path is to a module instance that doesn't exist at all, and 179 // so a missing module instance is the delete reason regardless 180 // of whether there might _also_ be a change to the resource 181 // configuration inside the module. (Conceptually the configurations 182 // inside the non-existing module instance don't exist at all, 183 // but they end up existing just as an artifact of the 184 // implementation detail that we detect module instance orphans 185 // only dynamically.) 186 return plans.ResourceInstanceDeleteBecauseNoModule 187 } 188 } 189 190 switch n.Addr.Resource.Key.(type) { 191 case nil: // no instance key at all 192 if cfg.Count != nil || cfg.ForEach != nil { 193 return plans.ResourceInstanceDeleteBecauseWrongRepetition 194 } 195 case addrs.IntKey: 196 if cfg.Count == nil { 197 // This resource isn't using "count" at all, then 198 return plans.ResourceInstanceDeleteBecauseWrongRepetition 199 } 200 201 expander := ctx.InstanceExpander() 202 if expander == nil { 203 break // only for tests that produce an incomplete MockEvalContext 204 } 205 insts := expander.ExpandResource(n.Addr.ContainingResource()) 206 207 declared := false 208 for _, inst := range insts { 209 if n.Addr.Equal(inst) { 210 declared = true 211 } 212 } 213 if !declared { 214 // This instance key is outside of the configured range 215 return plans.ResourceInstanceDeleteBecauseCountIndex 216 } 217 case addrs.StringKey: 218 if cfg.ForEach == nil { 219 // This resource isn't using "for_each" at all, then 220 return plans.ResourceInstanceDeleteBecauseWrongRepetition 221 } 222 223 expander := ctx.InstanceExpander() 224 if expander == nil { 225 break // only for tests that produce an incomplete MockEvalContext 226 } 227 insts := expander.ExpandResource(n.Addr.ContainingResource()) 228 229 declared := false 230 for _, inst := range insts { 231 if n.Addr.Equal(inst) { 232 declared = true 233 } 234 } 235 if !declared { 236 // This instance key is outside of the configured range 237 return plans.ResourceInstanceDeleteBecauseEachKey 238 } 239 } 240 241 // If we get here then the instance key type matches the configured 242 // repetition mode, and so we need to consider whether the key itself 243 // is within the range of the repetition construct. 244 if expander := ctx.InstanceExpander(); expander != nil { // (sometimes nil in MockEvalContext in tests) 245 // First we'll check whether our containing module instance still 246 // exists, so we can talk about that differently in the reason. 247 declared := false 248 for _, inst := range expander.ExpandModule(n.Addr.Module.Module()) { 249 if n.Addr.Module.Equal(inst) { 250 declared = true 251 break 252 } 253 } 254 if !declared { 255 return plans.ResourceInstanceDeleteBecauseNoModule 256 } 257 258 // Now we've proven that we're in a still-existing module instance, 259 // we'll see if our instance key matches something actually declared. 260 declared = false 261 for _, inst := range expander.ExpandResource(n.Addr.ContainingResource()) { 262 if n.Addr.Equal(inst) { 263 declared = true 264 break 265 } 266 } 267 if !declared { 268 // Because we already checked that the key _type_ was correct 269 // above, we can assume that any mismatch here is a range error, 270 // and thus we just need to decide which of the two range 271 // errors we're going to return. 272 switch n.Addr.Resource.Key.(type) { 273 case addrs.IntKey: 274 return plans.ResourceInstanceDeleteBecauseCountIndex 275 case addrs.StringKey: 276 return plans.ResourceInstanceDeleteBecauseEachKey 277 } 278 } 279 } 280 281 // If we didn't find any specific reason to report, we'll report "no reason" 282 // as a fallback, which means the UI should just state it'll be deleted 283 // without any explicit reasoning. 284 return plans.ResourceInstanceChangeNoReason 285 }