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 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) { func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr addrs.AbsResourceInstance) (*states.ResourceInstanceObject, error) {
provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
@ -540,31 +542,6 @@ func (n *NodeAbstractResource) ReadResourceInstanceState(ctx EvalContext, addr a
return obj, nil 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 // ReadDiff returns the planned change for a particular resource instance
// object. // object.
func (n *NodeAbstractResourceInstance) ReadDiff(ctx EvalContext, providerSchema *ProviderSchema) (*plans.ResourceInstanceChange, error) { 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 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 // graphNodesAreResourceInstancesInDifferentInstancesOfSameModule is an
// annoyingly-task-specific helper function that returns true if and only if // annoyingly-task-specific helper function that returns true if and only if
// the following conditions hold: // the following conditions hold:

View File

@ -1,12 +1,8 @@
package terraform package terraform
import ( import (
"fmt"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
) )
@ -25,7 +21,7 @@ var (
_ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeResourceInstance = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeEvalable = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeExecutable = (*NodePlanDestroyableResourceInstance)(nil)
_ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil) _ GraphNodeProviderConsumer = (*NodePlanDestroyableResourceInstance)(nil)
) )
@ -36,53 +32,46 @@ func (n *NodePlanDestroyableResourceInstance) DestroyAddr() *addrs.AbsResourceIn
} }
// GraphNodeEvalable // GraphNodeEvalable
func (n *NodePlanDestroyableResourceInstance) EvalTree() EvalNode { func (n *NodePlanDestroyableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
addr := n.ResourceInstanceAddr() addr := n.ResourceInstanceAddr()
// Declare a bunch of variables that are used for state during // Declare a bunch of variables that are used for state during
// evaluation. These are written to by address in the EvalNodes we // evaluation. These are written to by address in the EvalNodes we
// declare below. // declare below.
var provider providers.Interface
var providerSchema *ProviderSchema
var change *plans.ResourceInstanceChange var change *plans.ResourceInstanceChange
var state *states.ResourceInstanceObject var state *states.ResourceInstanceObject
if n.ResolvedProvider.Provider.Type == "" { _, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
// Should never happen; indicates that the graph was not constructed if err != nil {
// correctly since we didn't get our provider attached. return err
panic(fmt.Sprintf("%T %q was not assigned a resolved provider", n, dag.VertexName(n)))
} }
return &EvalSequence{ state, err = n.ReadResourceInstanceState(ctx, addr)
Nodes: []EvalNode{ if err != nil {
&EvalGetProvider{ return err
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,
},
},
} }
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" "fmt"
"github.com/hashicorp/terraform/plans" "github.com/hashicorp/terraform/plans"
"github.com/hashicorp/terraform/providers"
"github.com/hashicorp/terraform/states" "github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
@ -27,183 +26,211 @@ var (
_ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil) _ GraphNodeResourceInstance = (*NodePlannableResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil) _ GraphNodeAttachResourceConfig = (*NodePlannableResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil) _ GraphNodeAttachResourceState = (*NodePlannableResourceInstance)(nil)
_ GraphNodeEvalable = (*NodePlannableResourceInstance)(nil) _ GraphNodeExecutable = (*NodePlannableResourceInstance)(nil)
) )
// GraphNodeEvalable // GraphNodeEvalable
func (n *NodePlannableResourceInstance) EvalTree() EvalNode { func (n *NodePlannableResourceInstance) Execute(ctx EvalContext, op walkOperation) error {
addr := n.ResourceInstanceAddr() addr := n.ResourceInstanceAddr()
// Eval info is different depending on what kind of resource this is // Eval info is different depending on what kind of resource this is
switch addr.Resource.Resource.Mode { switch addr.Resource.Resource.Mode {
case addrs.ManagedResourceMode: case addrs.ManagedResourceMode:
return n.evalTreeManagedResource(addr) return n.managedResourceExecute(ctx, n.skipRefresh)
case addrs.DataResourceMode: case addrs.DataResourceMode:
return n.evalTreeDataResource(addr) return n.dataResourceExecute(ctx)
default: default:
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode)) 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 config := n.Config
var provider providers.Interface addr := n.ResourceInstanceAddr()
var providerSchema *ProviderSchema
var change *plans.ResourceInstanceChange var change *plans.ResourceInstanceChange
var state *states.ResourceInstanceObject var state *states.ResourceInstanceObject
return &EvalSequence{ provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
Nodes: []EvalNode{ if err != nil {
&EvalGetProvider{ return err
Addr: n.ResolvedProvider, }
Output: &provider,
Schema: &providerSchema,
},
&EvalReadState{ state, err = n.ReadResourceInstanceState(ctx, addr)
Addr: addr.Resource, if err != nil {
Provider: &provider, return err
ProviderSchema: &providerSchema, }
Output: &state,
},
&EvalValidateSelfRef{ validateSelfRef := &EvalValidateSelfRef{
Addr: addr.Resource, Addr: addr.Resource,
Config: config.Config, Config: config.Config,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
}, }
_, err = validateSelfRef.Eval(ctx)
if err != nil {
return err
}
&evalReadDataPlan{ readDataPlan := &evalReadDataPlan{
evalReadData: evalReadData{ evalReadData: evalReadData{
Addr: addr.Resource, Addr: addr.Resource,
Config: n.Config, Config: n.Config,
Provider: &provider, Provider: &provider,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
ProviderMetas: n.ProviderMetas, ProviderMetas: n.ProviderMetas,
ProviderSchema: &providerSchema, ProviderSchema: &providerSchema,
OutputChange: &change, OutputChange: &change,
State: &state, State: &state,
dependsOn: n.dependsOn, 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,
},
}, },
} }
_, 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 config := n.Config
var provider providers.Interface addr := n.ResourceInstanceAddr()
var providerSchema *ProviderSchema
var change *plans.ResourceInstanceChange var change *plans.ResourceInstanceChange
var instanceRefreshState *states.ResourceInstanceObject var instanceRefreshState *states.ResourceInstanceObject
var instancePlanState *states.ResourceInstanceObject var instancePlanState *states.ResourceInstanceObject
return &EvalSequence{ provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
Nodes: []EvalNode{ if err != nil {
&EvalGetProvider{ return err
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,
},
},
} }
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 return err
} }
err = n.CheckPreventDestroy(addr, change) err = n.checkPreventDestroy(change)
if err != nil { if err != nil {
return err return err
} }