From 184893d1e4d985e812b6a53babd72aadce5a1cb3 Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 29 Sep 2020 14:31:20 -0400 Subject: [PATCH] evaltree refactor --- terraform/node_resource_abstract.go | 51 ++-- terraform/node_resource_plan_destroy.go | 75 +++--- terraform/node_resource_plan_instance.go | 325 ++++++++++++----------- terraform/node_resource_plan_orphan.go | 2 +- 4 files changed, 235 insertions(+), 218 deletions(-) diff --git a/terraform/node_resource_abstract.go b/terraform/node_resource_abstract.go index 60971812c..c6fdc19b0 100644 --- a/terraform/node_resource_abstract.go +++ b/terraform/node_resource_abstract.go @@ -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: diff --git a/terraform/node_resource_plan_destroy.go b/terraform/node_resource_plan_destroy.go index d53c1441c..0118815bd 100644 --- a/terraform/node_resource_plan_destroy.go +++ b/terraform/node_resource_plan_destroy.go @@ -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 } diff --git a/terraform/node_resource_plan_instance.go b/terraform/node_resource_plan_instance.go index 2605b6f14..cca60dcac 100644 --- a/terraform/node_resource_plan_instance.go +++ b/terraform/node_resource_plan_instance.go @@ -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 } diff --git a/terraform/node_resource_plan_orphan.go b/terraform/node_resource_plan_orphan.go index 1580be829..ca0200054 100644 --- a/terraform/node_resource_plan_orphan.go +++ b/terraform/node_resource_plan_orphan.go @@ -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 }