github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/terraform/node_resource_plan_orphan.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/cycloidio/terraform/addrs" 8 "github.com/cycloidio/terraform/plans" 9 "github.com/cycloidio/terraform/states" 10 "github.com/cycloidio/terraform/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 return plans.ResourceInstanceDeleteBecauseNoResourceConfig 161 } 162 163 // If this is a resource instance inside a module instance that's no 164 // longer declared then we will have a config (because config isn't 165 // instance-specific) but the expander will know that our resource 166 // address's module path refers to an undeclared module instance. 167 if expander := ctx.InstanceExpander(); expander != nil { // (sometimes nil in MockEvalContext in tests) 168 validModuleAddr := expander.GetDeepestExistingModuleInstance(n.Addr.Module) 169 if len(validModuleAddr) != len(n.Addr.Module) { 170 // If we get here then at least one step in the resource's module 171 // path is to a module instance that doesn't exist at all, and 172 // so a missing module instance is the delete reason regardless 173 // of whether there might _also_ be a change to the resource 174 // configuration inside the module. (Conceptually the configurations 175 // inside the non-existing module instance don't exist at all, 176 // but they end up existing just as an artifact of the 177 // implementation detail that we detect module instance orphans 178 // only dynamically.) 179 return plans.ResourceInstanceDeleteBecauseNoModule 180 } 181 } 182 183 switch n.Addr.Resource.Key.(type) { 184 case nil: // no instance key at all 185 if cfg.Count != nil || cfg.ForEach != nil { 186 return plans.ResourceInstanceDeleteBecauseWrongRepetition 187 } 188 case addrs.IntKey: 189 if cfg.Count == nil { 190 // This resource isn't using "count" at all, then 191 return plans.ResourceInstanceDeleteBecauseWrongRepetition 192 } 193 194 expander := ctx.InstanceExpander() 195 if expander == nil { 196 break // only for tests that produce an incomplete MockEvalContext 197 } 198 insts := expander.ExpandResource(n.Addr.ContainingResource()) 199 200 declared := false 201 for _, inst := range insts { 202 if n.Addr.Equal(inst) { 203 declared = true 204 } 205 } 206 if !declared { 207 // This instance key is outside of the configured range 208 return plans.ResourceInstanceDeleteBecauseCountIndex 209 } 210 case addrs.StringKey: 211 if cfg.ForEach == nil { 212 // This resource isn't using "for_each" at all, then 213 return plans.ResourceInstanceDeleteBecauseWrongRepetition 214 } 215 216 expander := ctx.InstanceExpander() 217 if expander == nil { 218 break // only for tests that produce an incomplete MockEvalContext 219 } 220 insts := expander.ExpandResource(n.Addr.ContainingResource()) 221 222 declared := false 223 for _, inst := range insts { 224 if n.Addr.Equal(inst) { 225 declared = true 226 } 227 } 228 if !declared { 229 // This instance key is outside of the configured range 230 return plans.ResourceInstanceDeleteBecauseEachKey 231 } 232 } 233 234 // If we get here then the instance key type matches the configured 235 // repetition mode, and so we need to consider whether the key itself 236 // is within the range of the repetition construct. 237 if expander := ctx.InstanceExpander(); expander != nil { // (sometimes nil in MockEvalContext in tests) 238 // First we'll check whether our containing module instance still 239 // exists, so we can talk about that differently in the reason. 240 declared := false 241 for _, inst := range expander.ExpandModule(n.Addr.Module.Module()) { 242 if n.Addr.Module.Equal(inst) { 243 declared = true 244 break 245 } 246 } 247 if !declared { 248 return plans.ResourceInstanceDeleteBecauseNoModule 249 } 250 251 // Now we've proven that we're in a still-existing module instance, 252 // we'll see if our instance key matches something actually declared. 253 declared = false 254 for _, inst := range expander.ExpandResource(n.Addr.ContainingResource()) { 255 if n.Addr.Equal(inst) { 256 declared = true 257 break 258 } 259 } 260 if !declared { 261 // Because we already checked that the key _type_ was correct 262 // above, we can assume that any mismatch here is a range error, 263 // and thus we just need to decide which of the two range 264 // errors we're going to return. 265 switch n.Addr.Resource.Key.(type) { 266 case addrs.IntKey: 267 return plans.ResourceInstanceDeleteBecauseCountIndex 268 case addrs.StringKey: 269 return plans.ResourceInstanceDeleteBecauseEachKey 270 } 271 } 272 } 273 274 // If we didn't find any specific reason to report, we'll report "no reason" 275 // as a fallback, which means the UI should just state it'll be deleted 276 // without any explicit reasoning. 277 return plans.ResourceInstanceChangeNoReason 278 }