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