From 426f25308531dd238170d044ebfb90c34e258f7d Mon Sep 17 00:00:00 2001 From: Paul Hinze Date: Mon, 2 Mar 2015 15:34:05 -0600 Subject: [PATCH] core: [refactor] split WriteState EvalNodes This is the non-DRY pass. --- terraform/eval_state.go | 141 ++++++++++++++++++++++++++------ terraform/eval_state_test.go | 76 +++++++++++++++++ terraform/transform_deposed.go | 14 ++-- terraform/transform_resource.go | 37 +++++++-- terraform/transform_tainted.go | 7 +- 5 files changed, 230 insertions(+), 45 deletions(-) diff --git a/terraform/eval_state.go b/terraform/eval_state.go index cd2e0ae99..2fa4b8d06 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -56,6 +56,9 @@ func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) { }) } +// Does the bulk of the work for the various flavors of ReadState eval nodes. +// Each node just provides a function to get from the ResourceState to the +// InstanceState, and this takes care of all the plumbing. func readInstanceFromState( ctx EvalContext, resourceName string, @@ -138,17 +141,12 @@ func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) { // EvalWriteState is an EvalNode implementation that reads the // InstanceState for a specific resource out of the state. type EvalWriteState struct { - Name string - ResourceType string - Dependencies []string - State **InstanceState - Tainted *bool - TaintedIndex int - TaintedClearPrimary bool - Deposed bool + Name string + ResourceType string + Dependencies []string + State **InstanceState } -// TODO: test func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { state, lock := ctx.State() if state == nil { @@ -175,23 +173,120 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { rs.Type = n.ResourceType rs.Dependencies = n.Dependencies - if n.Tainted != nil && *n.Tainted { - if n.TaintedIndex != -1 { - rs.Tainted[n.TaintedIndex] = *n.State - } else { - rs.Tainted = append(rs.Tainted, *n.State) - } + rs.Primary = *n.State - if n.TaintedClearPrimary { - rs.Primary = nil - } - } else if n.Deposed { - rs.Deposed = *n.State - } else { - // Set the primary state - rs.Primary = *n.State + return nil, nil +} + +type EvalWriteStateTainted struct { + Name string + ResourceType string + Dependencies []string + State **InstanceState + TaintedIndex int +} + +func (n *EvalWriteStateTainted) Eval(ctx EvalContext) (interface{}, error) { + state, lock := ctx.State() + if state == nil { + return nil, fmt.Errorf("cannot write state to nil state") } + // Get a write lock so we can access this instance + lock.Lock() + defer lock.Unlock() + + // Look for the module state. If we don't have one, create it. + mod := state.ModuleByPath(ctx.Path()) + if mod == nil { + mod = state.AddModule(ctx.Path()) + } + + // Look for the resource state. + rs := mod.Resources[n.Name] + if rs == nil { + rs = &ResourceState{} + rs.init() + mod.Resources[n.Name] = rs + } + rs.Type = n.ResourceType + rs.Dependencies = n.Dependencies + + if n.TaintedIndex == -1 { + rs.Tainted = append(rs.Tainted, *n.State) + } else { + rs.Tainted[n.TaintedIndex] = *n.State + } + + return nil, nil +} + +type EvalWriteStateDeposed struct { + Name string + ResourceType string + Dependencies []string + State **InstanceState +} + +func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) { + state, lock := ctx.State() + if state == nil { + return nil, fmt.Errorf("cannot write state to nil state") + } + + // Get a write lock so we can access this instance + lock.Lock() + defer lock.Unlock() + + // Look for the module state. If we don't have one, create it. + mod := state.ModuleByPath(ctx.Path()) + if mod == nil { + mod = state.AddModule(ctx.Path()) + } + + // Look for the resource state. + rs := mod.Resources[n.Name] + if rs == nil { + rs = &ResourceState{} + rs.init() + mod.Resources[n.Name] = rs + } + rs.Type = n.ResourceType + rs.Dependencies = n.Dependencies + + rs.Deposed = *n.State + + return nil, nil +} + +// EvalClearPrimaryState is an EvalNode implementation that clears the primary +// instance from a resource state. +type EvalClearPrimaryState struct { + Name string +} + +func (n *EvalClearPrimaryState) Eval(ctx EvalContext) (interface{}, error) { + state, lock := ctx.State() + + // Get a read lock so we can access this instance + lock.RLock() + defer lock.RUnlock() + + // Look for the module state. If we don't have one, then it doesn't matter. + mod := state.ModuleByPath(ctx.Path()) + if mod == nil { + return nil, nil + } + + // Look for the resource state. If we don't have one, then it is okay. + rs := mod.Resources[n.Name] + if rs == nil { + return nil, nil + } + + // Clear primary from the resource state + rs.Primary = nil + return nil, nil } diff --git a/terraform/eval_state_test.go b/terraform/eval_state_test.go index fc638ee3e..168955c0a 100644 --- a/terraform/eval_state_test.go +++ b/terraform/eval_state_test.go @@ -147,3 +147,79 @@ func TestEvalReadState(t *testing.T) { output = nil } } + +func TestEvalWriteState(t *testing.T) { + state := &State{} + ctx := new(MockEvalContext) + ctx.StateState = state + ctx.StateLock = new(sync.RWMutex) + ctx.PathPath = rootModulePath + + is := &InstanceState{ID: "i-abc123"} + node := &EvalWriteState{ + Name: "restype.resname", + ResourceType: "restype", + State: &is, + } + _, err := node.Eval(ctx) + if err != nil { + t.Fatalf("Got err: %#v", err) + } + + rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"] + if rs.Type != "restype" { + t.Fatalf("expected type 'restype': %#v", rs) + } + if rs.Primary.ID != "i-abc123" { + t.Fatalf("expected primary instance to have ID 'i-abc123': %#v", rs) + } +} + +func TestEvalWriteStateTainted(t *testing.T) { + state := &State{} + ctx := new(MockEvalContext) + ctx.StateState = state + ctx.StateLock = new(sync.RWMutex) + ctx.PathPath = rootModulePath + + is := &InstanceState{ID: "i-abc123"} + node := &EvalWriteStateTainted{ + Name: "restype.resname", + ResourceType: "restype", + State: &is, + TaintedIndex: -1, + } + _, err := node.Eval(ctx) + if err != nil { + t.Fatalf("Got err: %#v", err) + } + + rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"] + if len(rs.Tainted) == 1 && rs.Tainted[0].ID != "i-abc123" { + t.Fatalf("expected tainted instance to have ID 'i-abc123': %#v", rs) + } +} + +func TestEvalWriteStateDeposed(t *testing.T) { + state := &State{} + ctx := new(MockEvalContext) + ctx.StateState = state + ctx.StateLock = new(sync.RWMutex) + ctx.PathPath = rootModulePath + + is := &InstanceState{ID: "i-abc123"} + node := &EvalWriteStateDeposed{ + Name: "restype.resname", + ResourceType: "restype", + State: &is, + } + _, err := node.Eval(ctx) + if err != nil { + t.Fatalf("Got err: %#v", err) + } + + rs := state.ModuleByPath(ctx.Path()).Resources["restype.resname"] + if rs.Deposed == nil || rs.Deposed.ID != "i-abc123" { + t.Fatalf("expected deposed instance to have ID 'i-abc123': %#v", rs) + } +} diff --git a/terraform/transform_deposed.go b/terraform/transform_deposed.go index d9739b96e..9a86dd5df 100644 --- a/terraform/transform_deposed.go +++ b/terraform/transform_deposed.go @@ -17,7 +17,7 @@ type DeposedTransformer struct { func (t *DeposedTransformer) Transform(g *Graph) error { state := t.State.ModuleByPath(g.Path) if state == nil { - // If there is no state for our module there can't be any tainted + // If there is no state for our module there can't be any deposed // resources, since they live in the state. return nil } @@ -27,7 +27,7 @@ func (t *DeposedTransformer) Transform(g *Graph) error { state = state.View(t.View) } - // Go through all the resources in our state to look for tainted resources + // Go through all the resources in our state to look for deposed resources for k, rs := range state.Resources { if rs.Deposed == nil { continue @@ -86,11 +86,10 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { State: &state, Output: &state, }, - &EvalWriteState{ + &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, State: &state, - Deposed: true, }, }, }, @@ -100,7 +99,6 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { var diff *InstanceDiff var err error var emptyState *InstanceState - tainted := true seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkApply}, Node: &EvalSequence{ @@ -129,19 +127,17 @@ func (n *graphNodeDeposedResource) EvalTree() EvalNode { // Always write the resource back to the state tainted... if it // successfully destroyed it will be pruned. If it did not, it will // remain tainted. - &EvalWriteState{ + &EvalWriteStateTainted{ Name: n.ResourceName, ResourceType: n.ResourceType, State: &state, - Tainted: &tainted, TaintedIndex: -1, }, // Then clear the deposed state. - &EvalWriteState{ + &EvalWriteStateDeposed{ Name: n.ResourceName, ResourceType: n.ResourceType, State: &emptyState, - Deposed: true, }, &EvalReturnError{ Error: &err, diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 6a38ab624..aa5e17998 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -395,14 +395,35 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { Diff: nil, }, - &EvalWriteState{ - Name: n.stateId(), - ResourceType: n.Resource.Type, - Dependencies: n.DependentOn(), - State: &state, - Tainted: &tainted, - TaintedIndex: -1, - TaintedClearPrimary: !n.Resource.Lifecycle.CreateBeforeDestroy, + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + return tainted, nil + }, + Then: &EvalSequence{ + Nodes: []EvalNode{ + &EvalWriteStateTainted{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &state, + TaintedIndex: -1, + }, + &EvalIf{ + If: func(ctx EvalContext) (bool, error) { + return !n.Resource.Lifecycle.CreateBeforeDestroy, nil + }, + Then: &EvalClearPrimaryState{ + Name: n.stateId(), + }, + }, + }, + }, + Else: &EvalWriteState{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &state, + }, }, &EvalApplyPost{ Info: info, diff --git a/terraform/transform_tainted.go b/terraform/transform_tainted.go index 7e68b87cd..9fbc53b14 100644 --- a/terraform/transform_tainted.go +++ b/terraform/transform_tainted.go @@ -71,7 +71,6 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string { func (n *graphNodeTaintedResource) EvalTree() EvalNode { var provider ResourceProvider var state *InstanceState - tainted := true seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} @@ -99,11 +98,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { State: &state, Output: &state, }, - &EvalWriteState{ + &EvalWriteStateTainted{ Name: n.ResourceName, ResourceType: n.ResourceType, State: &state, - Tainted: &tainted, TaintedIndex: n.Index, }, }, @@ -137,11 +135,10 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { Provider: &provider, Output: &state, }, - &EvalWriteState{ + &EvalWriteStateTainted{ Name: n.ResourceName, ResourceType: n.ResourceType, State: &state, - Tainted: &tainted, TaintedIndex: n.Index, }, &EvalUpdateStateHook{},