From c66494b874104bd698fc3a226da56a2940a73bbf Mon Sep 17 00:00:00 2001 From: Kristin Laemmert Date: Tue, 29 Sep 2020 10:58:35 -0400 Subject: [PATCH] terraform: refactor NodeDestroyDeposedResourceInstanceObject and NodePlanDeposedResourceInstanceObject The various Eval()s will be refactored in a later PR. --- terraform/node_resource_destroy_deposed.go | 242 ++++++++++-------- .../node_resource_destroy_deposed_test.go | 130 ++++++++++ 2 files changed, 264 insertions(+), 108 deletions(-) create mode 100644 terraform/node_resource_destroy_deposed_test.go diff --git a/terraform/node_resource_destroy_deposed.go b/terraform/node_resource_destroy_deposed.go index 550cffea5..6d5af049b 100644 --- a/terraform/node_resource_destroy_deposed.go +++ b/terraform/node_resource_destroy_deposed.go @@ -6,7 +6,6 @@ import ( "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" ) @@ -37,7 +36,7 @@ var ( _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil) + _ GraphNodeExecutable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) ) @@ -64,55 +63,58 @@ func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference } // GraphNodeEvalable impl. -func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode { +func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error { addr := n.ResourceInstanceAddr() - var provider providers.Interface - var providerSchema *ProviderSchema - var state *states.ResourceInstanceObject - - seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err + } // During the plan walk we always produce a planned destroy change, because // destroying is the only supported action for deposed objects. var change *plans.ResourceInstanceChange - seq.Nodes = append(seq.Nodes, &EvalOpFilter{ - Ops: []walkOperation{walkPlan, walkPlanDestroy}, - Node: &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadStateDeposed{ - Addr: addr.Resource, - Output: &state, - Key: n.DeposedKey, - Provider: &provider, - ProviderSchema: &providerSchema, - }, - &EvalDiffDestroy{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - DeposedKey: n.DeposedKey, - State: &state, - Output: &change, - }, - &EvalWriteDiff{ - Addr: addr.Resource, - DeposedKey: n.DeposedKey, - ProviderSchema: &providerSchema, - Change: &change, - }, - // Since deposed objects cannot be referenced by expressions - // elsewhere, we don't need to also record the planned new - // state in this case. - }, - }, - }) + var state *states.ResourceInstanceObject - return seq + switch op { + case walkPlan, walkPlanDestroy: + + readStateDeposed := &EvalReadStateDeposed{ + Addr: addr.Resource, + Output: &state, + Key: n.DeposedKey, + Provider: &provider, + ProviderSchema: &providerSchema, + } + _, err = readStateDeposed.Eval(ctx) + if err != nil { + return err + } + + diffDestroy := &EvalDiffDestroy{ + Addr: addr.Resource, + ProviderAddr: n.ResolvedProvider, + DeposedKey: n.DeposedKey, + State: &state, + Output: &change, + } + _, err = diffDestroy.Eval(ctx) + if err != nil { + return err + } + + writeDiff := &EvalWriteDiff{ + Addr: addr.Resource, + DeposedKey: n.DeposedKey, + ProviderSchema: &providerSchema, + Change: &change, + } + _, err = writeDiff.Eval(ctx) + if err != nil { + return err + } + } + return nil } // NodeDestroyDeposedResourceInstanceObject represents deposed resource @@ -133,7 +135,7 @@ var ( _ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil) - _ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil) + _ GraphNodeExecutable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) ) @@ -181,74 +183,98 @@ func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v b return nil } -// GraphNodeEvalable impl. -func (n *NodeDestroyDeposedResourceInstanceObject) EvalTree() EvalNode { - addr := n.ResourceInstanceAddr() +// GraphNodeExecutable impl. +func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error { + addr := n.ResourceInstanceAddr().Resource - var provider providers.Interface - var providerSchema *ProviderSchema var state *states.ResourceInstanceObject var change *plans.ResourceInstanceChange - var err error + var applyError error - return &EvalSequence{ - Nodes: []EvalNode{ - &EvalGetProvider{ - Addr: n.ResolvedProvider, - Output: &provider, - Schema: &providerSchema, - }, - &EvalReadStateDeposed{ - Addr: addr.Resource, - Output: &state, - Key: n.DeposedKey, - Provider: &provider, - ProviderSchema: &providerSchema, - }, - &EvalDiffDestroy{ - Addr: addr.Resource, - ProviderAddr: n.ResolvedProvider, - State: &state, - Output: &change, - }, - // Call pre-apply hook - &EvalApplyPre{ - Addr: addr.Resource, - State: &state, - Change: &change, - }, - &EvalApply{ - Addr: addr.Resource, - Config: nil, // No configuration because we are destroying - State: &state, - Change: &change, - Provider: &provider, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - Output: &state, - Error: &err, - }, - // Always write the resource back to the state deposed... if it - // was successfully destroyed it will be pruned. If it was not, it will - // be caught on the next run. - &EvalWriteStateDeposed{ - Addr: addr.Resource, - Key: n.DeposedKey, - ProviderAddr: n.ResolvedProvider, - ProviderSchema: &providerSchema, - State: &state, - }, - &EvalApplyPost{ - Addr: addr.Resource, - State: &state, - Error: &err, - }, - &EvalReturnError{ - Error: &err, - }, - &EvalUpdateStateHook{}, - }, + provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider) + if err != nil { + return err } + + readStateDeposed := &EvalReadStateDeposed{ + Addr: addr, + Output: &state, + Key: n.DeposedKey, + Provider: &provider, + ProviderSchema: &providerSchema, + } + _, err = readStateDeposed.Eval(ctx) + if err != nil { + return err + } + + diffDestroy := &EvalDiffDestroy{ + Addr: addr, + ProviderAddr: n.ResolvedProvider, + State: &state, + Output: &change, + } + _, err = diffDestroy.Eval(ctx) + if err != nil { + return err + } + + // Call pre-apply hook + applyPre := &EvalApplyPre{ + Addr: addr, + State: &state, + Change: &change, + } + _, err = applyPre.Eval(ctx) + if err != nil { + return err + } + + apply := &EvalApply{ + Addr: addr, + Config: nil, // No configuration because we are destroying + State: &state, + Change: &change, + Provider: &provider, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + Output: &state, + Error: &applyError, + } + _, err = apply.Eval(ctx) + if err != nil { + return err + } + + // Always write the resource back to the state deposed. If it + // was successfully destroyed it will be pruned. If it was not, it will + // be caught on the next run. + writeStateDeposed := &EvalWriteStateDeposed{ + Addr: addr, + Key: n.DeposedKey, + ProviderAddr: n.ResolvedProvider, + ProviderSchema: &providerSchema, + State: &state, + } + _, err = writeStateDeposed.Eval(ctx) + if err != nil { + return err + } + + applyPost := &EvalApplyPost{ + Addr: addr, + State: &state, + Error: &applyError, + } + _, err = applyPost.Eval(ctx) + if err != nil { + return err + } + if applyError != nil { + return applyError + } + UpdateStateHook(ctx) + return nil } // GraphNodeDeposer is an optional interface implemented by graph nodes that diff --git a/terraform/node_resource_destroy_deposed_test.go b/terraform/node_resource_destroy_deposed_test.go new file mode 100644 index 000000000..584dab5ac --- /dev/null +++ b/terraform/node_resource_destroy_deposed_test.go @@ -0,0 +1,130 @@ +package terraform + +import ( + "testing" + + "github.com/hashicorp/terraform/addrs" + "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/plans" + "github.com/hashicorp/terraform/providers" + "github.com/hashicorp/terraform/states" + "github.com/zclconf/go-cty/cty" +) + +func TestNodePlanDeposedResourceInstanceObject_Execute(t *testing.T) { + deposedKey := states.NewDeposedKey() + state := states.NewState() + absResource := mustResourceInstanceAddr("test_instance.foo") + state.Module(addrs.RootModuleInstance).SetResourceInstanceDeposed( + absResource.Resource, + deposedKey, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectTainted, + AttrsJSON: []byte(`{"id":"bar"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + p := testProvider("test") + p.UpgradeResourceStateResponse = providers.UpgradeResourceStateResponse{ + UpgradedState: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + } + ctx := &MockEvalContext{ + StateState: state.SyncWrapper(), + ProviderProvider: p, + ProviderSchemaSchema: &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }, + ChangesChanges: plans.NewChanges().SyncWrapper(), + } + + node := NodePlanDeposedResourceInstanceObject{ + NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ + Addr: absResource, + NodeAbstractResource: NodeAbstractResource{ + ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + }, + }, + DeposedKey: deposedKey, + } + err := node.Execute(ctx, walkPlan) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + change := ctx.Changes().GetResourceInstanceChange(absResource, deposedKey) + if change.ChangeSrc.Action != plans.Delete { + t.Fatalf("delete change not planned") + } + +} + +func TestNodeDestroyDeposedResourceInstanceObject_Execute(t *testing.T) { + deposedKey := states.NewDeposedKey() + state := states.NewState() + absResource := mustResourceInstanceAddr("test_instance.foo") + state.Module(addrs.RootModuleInstance).SetResourceInstanceDeposed( + absResource.Resource, + deposedKey, + &states.ResourceInstanceObjectSrc{ + Status: states.ObjectTainted, + AttrsJSON: []byte(`{"id":"bar"}`), + }, + mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + ) + + p := testProvider("test") + p.UpgradeResourceStateResponse = providers.UpgradeResourceStateResponse{ + UpgradedState: cty.ObjectVal(map[string]cty.Value{ + "id": cty.StringVal("bar"), + }), + } + ctx := &MockEvalContext{ + StateState: state.SyncWrapper(), + ProviderProvider: p, + ProviderSchemaSchema: &ProviderSchema{ + ResourceTypes: map[string]*configschema.Block{ + "test_instance": { + Attributes: map[string]*configschema.Attribute{ + "id": { + Type: cty.String, + Computed: true, + }, + }, + }, + }, + }, + ChangesChanges: plans.NewChanges().SyncWrapper(), + } + + node := NodeDestroyDeposedResourceInstanceObject{ + NodeAbstractResourceInstance: &NodeAbstractResourceInstance{ + Addr: absResource, + NodeAbstractResource: NodeAbstractResource{ + ResolvedProvider: mustProviderConfig(`provider["registry.terraform.io/hashicorp/test"]`), + }, + }, + DeposedKey: deposedKey, + } + err := node.Execute(ctx, walkApply) + + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + + if !state.Empty() { + t.Fatalf("resources left in state after destroy") + } +}