terraform: refactor NodeDestroyDeposedResourceInstanceObject and NodePlanDeposedResourceInstanceObject

The various Eval()s will be refactored in a later PR.
This commit is contained in:
Kristin Laemmert 2020-09-29 10:58:35 -04:00
parent 3bb64e80d5
commit c66494b874
2 changed files with 264 additions and 108 deletions

View File

@ -6,7 +6,6 @@ import (
"github.com/hashicorp/terraform/addrs" "github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag" "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"
) )
@ -37,7 +36,7 @@ var (
_ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeResourceInstance = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeEvalable = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeExecutable = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
_ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodePlanDeposedResourceInstanceObject)(nil)
) )
@ -64,55 +63,58 @@ func (n *NodePlanDeposedResourceInstanceObject) References() []*addrs.Reference
} }
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *NodePlanDeposedResourceInstanceObject) EvalTree() EvalNode { func (n *NodePlanDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error {
addr := n.ResourceInstanceAddr() addr := n.ResourceInstanceAddr()
var provider providers.Interface provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
var providerSchema *ProviderSchema if err != nil {
var state *states.ResourceInstanceObject return err
}
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// During the plan walk we always produce a planned destroy change, because // During the plan walk we always produce a planned destroy change, because
// destroying is the only supported action for deposed objects. // destroying is the only supported action for deposed objects.
var change *plans.ResourceInstanceChange var change *plans.ResourceInstanceChange
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ var state *states.ResourceInstanceObject
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.
},
},
})
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 // NodeDestroyDeposedResourceInstanceObject represents deposed resource
@ -133,7 +135,7 @@ var (
_ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeDestroyerCBD = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferenceable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeReferencer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeEvalable = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeExecutable = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProviderConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
_ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil) _ GraphNodeProvisionerConsumer = (*NodeDestroyDeposedResourceInstanceObject)(nil)
) )
@ -181,74 +183,98 @@ func (n *NodeDestroyDeposedResourceInstanceObject) ModifyCreateBeforeDestroy(v b
return nil return nil
} }
// GraphNodeEvalable impl. // GraphNodeExecutable impl.
func (n *NodeDestroyDeposedResourceInstanceObject) EvalTree() EvalNode { func (n *NodeDestroyDeposedResourceInstanceObject) Execute(ctx EvalContext, op walkOperation) error {
addr := n.ResourceInstanceAddr() addr := n.ResourceInstanceAddr().Resource
var provider providers.Interface
var providerSchema *ProviderSchema
var state *states.ResourceInstanceObject var state *states.ResourceInstanceObject
var change *plans.ResourceInstanceChange var change *plans.ResourceInstanceChange
var err error var applyError error
return &EvalSequence{ provider, providerSchema, err := GetProvider(ctx, n.ResolvedProvider)
Nodes: []EvalNode{ if err != nil {
&EvalGetProvider{ return err
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{},
},
} }
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 // GraphNodeDeposer is an optional interface implemented by graph nodes that

View File

@ -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")
}
}