terraform: destroy node should not create

This commit is contained in:
Mitchell Hashimoto 2015-02-24 22:45:47 -08:00
parent 53a75f63ba
commit 6affc57b2d
5 changed files with 195 additions and 4 deletions

View File

@ -238,6 +238,38 @@ func (n *EvalDiffTainted) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil
}
// EvalFilterDiff is an EvalNode implementation that filters the diff
// according to some filter.
type EvalFilterDiff struct {
// Input and output
Diff **InstanceDiff
Output **InstanceDiff
// Destroy, if true, will only include a destroy diff if it is set.
Destroy bool
}
func (n *EvalFilterDiff) Eval(ctx EvalContext) (interface{}, error) {
if *n.Diff == nil {
return nil, nil
}
input := *n.Diff
result := new(InstanceDiff)
if n.Destroy {
if input.Destroy || input.RequiresNew() {
result.Destroy = true
}
}
if n.Output != nil {
*n.Output = result
}
return nil, nil
}
// EvalReadDiff is an EvalNode implementation that writes the diff to
// the full diff.
type EvalReadDiff struct {
@ -275,7 +307,10 @@ func (n *EvalWriteDiff) Eval(ctx EvalContext) (interface{}, error) {
diff, lock := ctx.Diff()
// The diff to write, if its empty it should write nil
diffVal := *n.Diff
var diffVal *InstanceDiff
if n.Diff != nil {
diffVal = *n.Diff
}
if diffVal.Empty() {
diffVal = nil
}

View File

@ -0,0 +1,78 @@
package terraform
import (
"reflect"
"testing"
)
func TestEvalFilterDiff(t *testing.T) {
ctx := new(MockEvalContext)
cases := []struct {
Node *EvalFilterDiff
Input *InstanceDiff
Output *InstanceDiff
}{
// With no settings, it returns an empty diff
{
&EvalFilterDiff{},
&InstanceDiff{Destroy: true},
&InstanceDiff{},
},
// Destroy
{
&EvalFilterDiff{Destroy: true},
&InstanceDiff{Destroy: false},
&InstanceDiff{Destroy: false},
},
{
&EvalFilterDiff{Destroy: true},
&InstanceDiff{Destroy: true},
&InstanceDiff{Destroy: true},
},
{
&EvalFilterDiff{Destroy: true},
&InstanceDiff{
Destroy: true,
Attributes: map[string]*ResourceAttrDiff{
"foo": &ResourceAttrDiff{},
},
},
&InstanceDiff{Destroy: true},
},
{
&EvalFilterDiff{Destroy: true},
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"foo": &ResourceAttrDiff{
RequiresNew: true,
},
},
},
&InstanceDiff{Destroy: true},
},
{
&EvalFilterDiff{Destroy: true},
&InstanceDiff{
Attributes: map[string]*ResourceAttrDiff{
"foo": &ResourceAttrDiff{},
},
},
&InstanceDiff{Destroy: false},
},
}
for i, tc := range cases {
var output *InstanceDiff
tc.Node.Diff = &tc.Input
tc.Node.Output = &output
if _, err := tc.Node.Eval(ctx); err != nil {
t.Fatalf("err: %s", err)
}
if !reflect.DeepEqual(output, tc.Output) {
t.Fatalf("bad: %d\n\n%#v", i, output)
}
}
}

View File

@ -43,10 +43,9 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
if idx < 0 {
idx = len(rs.Tainted) - 1
}
if idx < len(rs.Tainted) {
if idx >= 0 && idx < len(rs.Tainted) {
// Return the proper tainted resource
result = rs.Tainted[n.TaintedIndex]
result = rs.Tainted[idx]
}
}
@ -58,6 +57,25 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
return result, nil
}
// EvalRequireState is an EvalNode implementation that early exits
// if the state doesn't have an ID.
type EvalRequireState struct {
State **InstanceState
}
func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) {
if n.State == nil {
return nil, EvalEarlyExitError{}
}
state := *n.State
if state == nil || state.ID == "" {
return nil, EvalEarlyExitError{}
}
return nil, nil
}
// EvalUpdateStateHook is an EvalNode implementation that calls the
// PostStateUpdate hook with the current state.
type EvalUpdateStateHook struct{}

View File

@ -5,6 +5,47 @@ import (
"testing"
)
func TestEvalRequireState(t *testing.T) {
ctx := new(MockEvalContext)
cases := []struct {
State *InstanceState
Exit bool
}{
{
nil,
true,
},
{
&InstanceState{},
true,
},
{
&InstanceState{ID: "foo"},
false,
},
}
var exitVal EvalEarlyExitError
for _, tc := range cases {
node := &EvalRequireState{State: &tc.State}
_, err := node.Eval(ctx)
if tc.Exit {
if err != exitVal {
t.Fatalf("should've exited: %#v", tc.State)
}
continue
}
if !tc.Exit && err != nil {
t.Fatalf("shouldn't exit: %#v", tc.State)
}
if err != nil {
t.Fatalf("err: %s", err)
}
}
}
func TestEvalUpdateStateHook(t *testing.T) {
mockHook := new(MockHook)

View File

@ -386,6 +386,15 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
Name: n.stateId(),
},
},
// We clear the diff out here so that future nodes
// don't see a diff that is already complete. There
// is no longer a diff!
&EvalWriteDiff{
Name: n.stateId(),
Diff: nil,
},
&EvalWriteState{
Name: n.stateId(),
ResourceType: n.Resource.Type,
@ -455,6 +464,13 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
Diff: &diffApply,
},
// Filter the diff so we only get the destroy
&EvalFilterDiff{
Diff: &diffApply,
Output: &diffApply,
Destroy: true,
},
// If we're not destroying, then compare diffs
&EvalIf{
If: func(ctx EvalContext) (bool, error) {
@ -477,6 +493,9 @@ func (n *graphNodeExpandedResourceDestroy) EvalTree() EvalNode {
Tainted: n.Resource.Lifecycle.CreateBeforeDestroy,
TaintedIndex: -1,
},
&EvalRequireState{
State: &state,
},
&EvalApply{
Info: info,
State: &state,