evaltree refactor

This commit is contained in:
Kristin Laemmert 2020-09-29 14:31:20 -04:00
parent c66494b874
commit 184893d1e4
4 changed files with 235 additions and 218 deletions

View File

@ -498,6 +498,8 @@ func (n *NodeAbstractResource) WriteResourceState(ctx EvalContext, addr addrs.Ab
return nil
}
// ReadResourceInstanceState reads the current object for a specific instance in
// the state.
func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, error) {
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
@ -540,31 +542,6 @@ func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr a
return obj, nil
}
// CheckPreventDestroy returns an error if a resource has PreventDestroy
// configured and the diff would destroy the resource.
func (n *NodeAbstractResource) CheckPreventDestroy(addr addrs.AbsResourceInstance, change *plans.ResourceInstanceChange) error {
if change == nil || n.Config == nil || n.Config.Managed == nil {
return nil
}
preventDestroy := n.Config.Managed.PreventDestroy
if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy {
var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Instance cannot be destroyed",
Detail: fmt.Sprintf(
"Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.",
addr,
),
Subject: &n.Config.DeclRange,
})
return diags.Err()
}
return nil
}
// ReadDiff returns the planned change for a particular resource instance
// object.
func (n *NodeAbstractResourceInstance) ReadDiff(ctx EvalContext, providerSchema *ProviderSchema) (*plans.ResourceInstanceChange, error) {
@ -594,6 +571,30 @@ func (n *NodeAbstractResourceInstance) ReadDiff(ctx EvalContext, providerSchema
return change, nil
}
func (n *NodeAbstractResourceInstance) checkPreventDestroy(change *plans.ResourceInstanceChange) error {
if change == nil || n.Config == nil || n.Config.Managed == nil {
return nil
}
preventDestroy := n.Config.Managed.PreventDestroy
if (change.Action == plans.Delete || change.Action.IsReplace()) && preventDestroy {
var diags tfdiags.Diagnostics
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Instance cannot be destroyed",
Detail: fmt.Sprintf(
"Resource %s has lifecycle.prevent_destroy set, but the plan calls for this resource to be destroyed. To avoid this error and continue with the plan, either disable lifecycle.prevent_destroy or reduce the scope of the plan using the -target flag.",
n.Addr.String(),
),
Subject: &n.Config.DeclRange,
})
return diags.Err()
}
return nil
}
// graphNodesAreResourceInstancesInDifferentInstancesOfSameModule is an
// annoyingly-task-specific helper function that returns true if and only if
// the following conditions hold:

View File

@ -1,12 +1,8 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
)
@ -25,7 +21,7 @@ var (
_ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeEvalable = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeExecutable = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil)
)
@ -36,53 +32,46 @@ func (n *NodePlanDestroyableResourceInstance) DestroyAddr() *addrs.AbsResourceIn
}
// GraphNodeEvalable
func (n *NodePlanDestroyableResourceInstance) EvalTree() EvalNode {
func (n *NodePlanDestroyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
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 provider providers.Interface
var providerSchema *ProviderSchema
var change *plans.ResourceInstanceChange
var state *states.ResourceInstanceObject
if n.ResolvedProvider.Provider.Type == "" {
// Should never happen; indicates that the graph was not constructed
// correctly since we didn't get our provider attached.
panic(fmt.Sprintf("%T %q was not assigned a resolved provider", n, dag.VertexName(n)))
_, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
if err != nil {
return err
}
return &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Addr: n.ResolvedProvider,
Output: &provider,
Schema: &providerSchema,
},
&EvalReadState{
Addr: addr.Resource,
Provider: &provider,
ProviderSchema: &providerSchema,
Output: &state,
},
&EvalDiffDestroy{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
State: &state,
Output: &change,
},
&EvalCheckPreventDestroy{
Addr: addr.Resource,
Config: n.Config,
Change: &change,
},
&EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
},
},
state, err = n.ReadResourceInstanceState(ctx, addr)
if err != nil {
return err
}
diffDestroy := &EvalDiffDestroy{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
State: &state,
Output: &change,
}
_, err = diffDestroy.Eval(ctx)
if err != nil {
return err
}
err = n.checkPreventDestroy(change)
if err != nil {
return err
}
writeDiff := &EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
}
_, err = writeDiff.Eval(ctx)
return err
}

View File

@ -4,7 +4,6 @@ import (
"fmt"
"github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/addrs"
@ -27,183 +26,211 @@ var (
_ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil)
_ GraphNodeEvalable = (*NodePlannableResourceInstance)(nil)
_ GraphNodeExecutable = (*NodePlannableResourceInstance)(nil)
)
// GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode {
func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
addr := n.ResourceInstanceAddr()
// Eval info is different depending on what kind of resource this is
switch addr.Resource.Resource.Mode {
case addrs.ManagedResourceMode:
return n.evalTreeManagedResource(addr)
return n.managedResourceExecute(ctx, n.skipRefresh)
case addrs.DataResourceMode:
return n.evalTreeDataResource(addr)
return n.dataResourceExecute(ctx)
default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
}
}
func (n *NodePlannableResourceInstance) evalTreeDataResource(addr addrs.AbsResourceInstance) EvalNode {
func (n *NodePlannableResourceInstance) dataResourceExecute(ctx EvalContext) error {
config := n.Config
var provider providers.Interface
var providerSchema *ProviderSchema
addr := n.ResourceInstanceAddr()
var change *plans.ResourceInstanceChange
var state *states.ResourceInstanceObject
return &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Addr: n.ResolvedProvider,
Output: &provider,
Schema: &providerSchema,
},
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
if err != nil {
return err
}
&EvalReadState{
Addr: addr.Resource,
Provider: &provider,
ProviderSchema: &providerSchema,
Output: &state,
},
state, err = n.ReadResourceInstanceState(ctx, addr)
if err != nil {
return err
}
&EvalValidateSelfRef{
Addr: addr.Resource,
Config: config.Config,
ProviderSchema: &providerSchema,
},
validateSelfRef := &EvalValidateSelfRef{
Addr: addr.Resource,
Config: config.Config,
ProviderSchema: &providerSchema,
}
_, err = validateSelfRef.Eval(ctx)
if err != nil {
return err
}
&evalReadDataPlan{
evalReadData: evalReadData{
Addr: addr.Resource,
Config: n.Config,
Provider: &provider,
ProviderAddr: n.ResolvedProvider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
OutputChange: &change,
State: &state,
dependsOn: n.dependsOn,
},
},
// write the data source into both the refresh state and the
// working state
&EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema,
State: &state,
targetState: refreshState,
},
&EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema,
State: &state,
},
&EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
},
readDataPlan := &evalReadDataPlan{
evalReadData: evalReadData{
Addr: addr.Resource,
Config: n.Config,
Provider: &provider,
ProviderAddr: n.ResolvedProvider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
OutputChange: &change,
State: &state,
dependsOn: n.dependsOn,
},
}
_, err = readDataPlan.Eval(ctx)
if err != nil {
return err
}
// write the data source into both the refresh state and the
// working state
writeRefreshState := &EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema,
State: &state,
targetState: refreshState,
}
_, err = writeRefreshState.Eval(ctx)
if err != nil {
return err
}
writeState := &EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema,
State: &state,
}
_, err = writeState.Eval(ctx)
if err != nil {
return err
}
writeDiff := &EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
}
_, err = writeDiff.Eval(ctx)
return err
}
func (n *NodePlannableResourceInstance) evalTreeManagedResource(addr addrs.AbsResourceInstance) EvalNode {
func (n *NodePlannableResourceInstance) managedResourceExecute(ctx EvalContext, skipRefresh bool) error {
config := n.Config
var provider providers.Interface
var providerSchema *ProviderSchema
addr := n.ResourceInstanceAddr()
var change *plans.ResourceInstanceChange
var instanceRefreshState *states.ResourceInstanceObject
var instancePlanState *states.ResourceInstanceObject
return &EvalSequence{
Nodes: []EvalNode{
&EvalGetProvider{
Addr: n.ResolvedProvider,
Output: &provider,
Schema: &providerSchema,
},
&EvalValidateSelfRef{
Addr: addr.Resource,
Config: config.Config,
ProviderSchema: &providerSchema,
},
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
return !n.skipRefresh, nil
},
Then: &EvalSequence{
Nodes: []EvalNode{
// Refresh the instance
&EvalReadState{
Addr: addr.Resource,
Provider: &provider,
ProviderSchema: &providerSchema,
Output: &instanceRefreshState,
},
&EvalRefreshLifecycle{
Addr: addr,
Config: n.Config,
State: &instanceRefreshState,
ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy,
},
&EvalRefresh{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
Provider: &provider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
State: &instanceRefreshState,
Output: &instanceRefreshState,
},
&EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
State: &instanceRefreshState,
ProviderSchema: &providerSchema,
targetState: refreshState,
Dependencies: &n.Dependencies,
},
},
},
},
// Plan the instance
&EvalDiff{
Addr: addr.Resource,
Config: n.Config,
CreateBeforeDestroy: n.ForceCreateBeforeDestroy,
Provider: &provider,
ProviderAddr: n.ResolvedProvider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
State: &instanceRefreshState,
OutputChange: &change,
OutputState: &instancePlanState,
},
&EvalCheckPreventDestroy{
Addr: addr.Resource,
Config: n.Config,
Change: &change,
},
&EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
State: &instancePlanState,
ProviderSchema: &providerSchema,
},
&EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
},
},
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
if err != nil {
return err
}
validateSelfRef := &EvalValidateSelfRef{
Addr: addr.Resource,
Config: config.Config,
ProviderSchema: &providerSchema,
}
_, err = validateSelfRef.Eval(ctx)
if err != nil {
return err
}
// Refresh, maybe
if !skipRefresh {
instanceRefreshState, err = n.ReadResourceInstanceState(ctx, addr)
if err != nil {
return err
}
refreshLifecycle := &EvalRefreshLifecycle{
Addr: addr,
Config: n.Config,
State: &instanceRefreshState,
ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy,
}
_, err = refreshLifecycle.Eval(ctx)
if err != nil {
return err
}
refresh := &EvalRefresh{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
Provider: &provider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
State: &instanceRefreshState,
Output: &instanceRefreshState,
}
_, err = refresh.Eval(ctx)
if err != nil {
return err
}
writeRefreshState := &EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
ProviderSchema: &providerSchema,
State: &instanceRefreshState,
targetState: refreshState,
Dependencies: &n.Dependencies,
}
_, err = writeRefreshState.Eval(ctx)
if err != nil {
return err
}
}
// Plan the instance
diff := &EvalDiff{
Addr: addr.Resource,
Config: n.Config,
CreateBeforeDestroy: n.ForceCreateBeforeDestroy,
Provider: &provider,
ProviderAddr: n.ResolvedProvider,
ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema,
State: &instanceRefreshState,
OutputChange: &change,
OutputState: &instancePlanState,
}
_, err = diff.Eval(ctx)
if err != nil {
return err
}
err = n.checkPreventDestroy(change)
if err != nil {
return err
}
writeState := &EvalWriteState{
Addr: addr.Resource,
ProviderAddr: n.ResolvedProvider,
State: &instancePlanState,
ProviderSchema: &providerSchema,
}
_, err = writeState.Eval(ctx)
if err != nil {
return err
}
writeDiff := &EvalWriteDiff{
Addr: addr.Resource,
ProviderSchema: &providerSchema,
Change: &change,
}
_, err = writeDiff.Eval(ctx)
return err
}

View File

@ -57,7 +57,7 @@ func (n *NodePlannableResourceInstanceOrphan) Execute(ctx EvalContext, op walkOp
return err
}
err = n.CheckPreventDestroy(addr, change)
err = n.checkPreventDestroy(change)
if err != nil {
return err
}