opentofu/internal/terraform/node_resource_plan_destroy.go
James Bardin 74885b1108 remove data sources from state read and upgrade
Data sources should not require reading the previous versions. While we
previously skipped the decoding if it were to fail, this removes the
need for any prior state at all.

The only place where the prior state was functionally used was in the
destroy path. Because a data source destroy is only for cleanup purposes
to clean out the state using the same code paths as a managed resource,
we can substitute the prior state in the change change with a null value
to maintain the same behavior.
2022-04-11 11:55:53 -04:00

123 lines
4.2 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
// NodePlanDestroyableResourceInstance represents a resource that is ready
// to be planned for destruction.
type NodePlanDestroyableResourceInstance struct {
*NodeAbstractResourceInstance
// skipRefresh indicates that we should skip refreshing
skipRefresh bool
}
var (
_ GraphNodeModuleInstance = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeReferenceable = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeReferencer = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeDestroyer = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeConfigResource = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeExecutable = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil)
)
// GraphNodeDestroyer
func (n *NodePlanDestroyableResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
addr := n.ResourceInstanceAddr()
return &addr
}
// GraphNodeEvalable
func (n *NodePlanDestroyableResourceInstance) Execute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
addr := n.ResourceInstanceAddr()
switch addr.Resource.Resource.Mode {
case addrs.ManagedResourceMode:
return n.managedResourceExecute(ctx, op)
case addrs.DataResourceMode:
return n.dataResourceExecute(ctx, op)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
}
func (n *NodePlanDestroyableResourceInstance) managedResourceExecute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
addr := n.ResourceInstanceAddr()
// Declare a bunch of variables that are used for state during
// evaluation. These are written to by address in the EvalNodes we
// declare below.
var change *plans.ResourceInstanceChange
var state *states.ResourceInstanceObject
state, err := n.readResourceInstanceState(ctx, addr)
diags = diags.Append(err)
if diags.HasErrors() {
return diags
}
// If we are in the "skip refresh" mode then we will have skipped over our
// usual opportunity to update the previous run state and refresh state
// with the result of any provider schema upgrades, so we'll compensate
// by doing that here.
//
// NOTE: this is coupled with logic in Context.destroyPlan which skips
// running a normal plan walk when refresh is enabled. These two
// conditionals must agree (be exactly opposite) in order to get the
// correct behavior in both cases.
if n.skipRefresh {
diags = diags.Append(n.writeResourceInstanceState(ctx, state, prevRunState))
if diags.HasErrors() {
return diags
}
diags = diags.Append(n.writeResourceInstanceState(ctx, state, refreshState))
if diags.HasErrors() {
return diags
}
}
change, destroyPlanDiags := n.planDestroy(ctx, state, "")
diags = diags.Append(destroyPlanDiags)
if diags.HasErrors() {
return diags
}
diags = diags.Append(n.checkPreventDestroy(change))
if diags.HasErrors() {
return diags
}
diags = diags.Append(n.writeChange(ctx, change, ""))
return diags
}
func (n *NodePlanDestroyableResourceInstance) dataResourceExecute(ctx EvalContext, op walkOperation) (diags tfdiags.Diagnostics) {
// We may not be able to read a prior data source from the state if the
// schema was upgraded and we are destroying before ever refreshing that
// data source. Regardless, a data source "destroy" is simply writing a
// null state, which we can do with a null prior state too.
change := &plans.ResourceInstanceChange{
Addr: n.ResourceInstanceAddr(),
PrevRunAddr: n.prevRunAddr(ctx),
Change: plans.Change{
Action: plans.Delete,
Before: cty.NullVal(cty.DynamicPseudoType),
After: cty.NullVal(cty.DynamicPseudoType),
},
ProviderAddr: n.ResolvedProvider,
}
return diags.Append(n.writeChange(ctx, change, ""))
}