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  }