From 93f3050dbd60307417534658b03db30e3f048a5a Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 12 Feb 2015 14:46:22 -0800 Subject: [PATCH] terraform: make things more linear --- terraform/context.go | 18 ++++++++ terraform/context_test.go | 7 ++- terraform/eval_diff.go | 65 ++++++++++++++++++++------ terraform/eval_provider.go | 7 ++- terraform/eval_refresh.go | 19 +++++--- terraform/eval_state.go | 26 ++++++----- terraform/state.go | 2 +- terraform/transform_orphan.go | 30 ++++++++---- terraform/transform_resource.go | 81 ++++++++++++++++++++++++++------- terraform/transform_tainted.go | 30 ++++++++---- 10 files changed, 213 insertions(+), 72 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index 44a128e9f..df83b0689 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -81,6 +81,24 @@ func (c *Context2) GraphBuilder() GraphBuilder { } } +// Apply applies the changes represented by this context and returns +// the resulting state. +// +// In addition to returning the resulting state, this context is updated +// with the latest state. +func (c *Context2) Apply() (*State, error) { + // Copy our own state + c.state = c.state.deepcopy() + + // Do the walk + _, err := c.walk(walkApply) + + // Clean out any unused things + c.state.prune() + + return c.state, err +} + // Plan generates an execution plan for the given context. // // The execution plan encapsulates the context and can be stored diff --git a/terraform/context_test.go b/terraform/context_test.go index e8db4202f..5948489a9 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -2671,13 +2671,15 @@ func TestContextInput_varOnly(t *testing.T) { t.Fatalf("bad: \n%s", actualStr) } } +*/ -func TestContextApply(t *testing.T) { +/* +func TestContext2Apply(t *testing.T) { m := testModule(t, "apply-good") p := testProvider("aws") p.ApplyFn = testApplyFn p.DiffFn = testDiffFn - ctx := testContext(t, &ContextOpts{ + ctx := testContext2(t, &ContextOpts{ Module: m, Providers: map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -2705,6 +2707,7 @@ func TestContextApply(t *testing.T) { } } +/* func TestContextApply_emptyModule(t *testing.T) { m := testModule(t, "apply-empty-module") p := testProvider("aws") diff --git a/terraform/eval_diff.go b/terraform/eval_diff.go index 9a956fd40..7e95b3ba4 100644 --- a/terraform/eval_diff.go +++ b/terraform/eval_diff.go @@ -3,11 +3,12 @@ package terraform // EvalDiff is an EvalNode implementation that does a refresh for // a resource. type EvalDiff struct { - Info *InstanceInfo - Config EvalNode - Provider EvalNode - State EvalNode - Output *InstanceDiff + Info *InstanceInfo + Config EvalNode + Provider EvalNode + State EvalNode + Output **InstanceDiff + OutputState **InstanceState } func (n *EvalDiff) Args() ([]EvalNode, []EvalType) { @@ -82,11 +83,12 @@ func (n *EvalDiff) Eval( } // Update our output - *n.Output = *diff + *n.Output = diff + *n.OutputState = state // Merge our state so that the state is updated with our plan - if !diff.Empty() { - state = state.MergeDiff(diff) + if !diff.Empty() && n.OutputState != nil { + *n.OutputState = state.MergeDiff(diff) } return state, nil @@ -101,7 +103,7 @@ func (n *EvalDiff) Type() EvalType { type EvalDiffDestroy struct { Info *InstanceInfo State EvalNode - Output *InstanceDiff + Output **InstanceDiff } func (n *EvalDiffDestroy) Args() ([]EvalNode, []EvalType) { @@ -142,7 +144,7 @@ func (n *EvalDiffDestroy) Eval( } // Update our output - *n.Output = *diff + *n.Output = diff return nil, nil } @@ -188,7 +190,7 @@ func (n *EvalDiffDestroyModule) Type() EvalType { // the full diff. type EvalDiffTainted struct { Name string - Diff *InstanceDiff + Diff **InstanceDiff } func (n *EvalDiffTainted) Args() ([]EvalNode, []EvalType) { @@ -218,7 +220,7 @@ func (n *EvalDiffTainted) Eval( // If we have tainted, then mark it on the diff if len(rs.Tainted) > 0 { - n.Diff.DestroyTainted = true + (*n.Diff).DestroyTainted = true } return nil, nil @@ -228,11 +230,46 @@ func (n *EvalDiffTainted) Type() EvalType { return EvalTypeNull } +// EvalReadDiff is an EvalNode implementation that writes the diff to +// the full diff. +type EvalReadDiff struct { + Name string + Diff **InstanceDiff +} + +func (n *EvalReadDiff) Args() ([]EvalNode, []EvalType) { + return nil, nil +} + +// TODO: test +func (n *EvalReadDiff) Eval( + ctx EvalContext, args []interface{}) (interface{}, error) { + diff, lock := ctx.Diff() + + // Acquire the lock so that we can do this safely concurrently + lock.Lock() + defer lock.Unlock() + + // Write the diff + modDiff := diff.ModuleByPath(ctx.Path()) + if modDiff == nil { + return nil, nil + } + + *n.Diff = modDiff.Resources[n.Name] + + return nil, nil +} + +func (n *EvalReadDiff) Type() EvalType { + return EvalTypeNull +} + // EvalWriteDiff is an EvalNode implementation that writes the diff to // the full diff. type EvalWriteDiff struct { Name string - Diff *InstanceDiff + Diff **InstanceDiff } func (n *EvalWriteDiff) Args() ([]EvalNode, []EvalType) { @@ -245,7 +282,7 @@ func (n *EvalWriteDiff) Eval( diff, lock := ctx.Diff() // The diff to write, if its empty it should write nil - diffVal := n.Diff + diffVal := *n.Diff if diffVal.Empty() { diffVal = nil } diff --git a/terraform/eval_provider.go b/terraform/eval_provider.go index 35d6e87c5..4d34b1033 100644 --- a/terraform/eval_provider.go +++ b/terraform/eval_provider.go @@ -55,7 +55,8 @@ func (n *EvalInitProvider) Type() EvalType { // EvalGetProvider is an EvalNode implementation that retrieves an already // initialized provider instance for the given name. type EvalGetProvider struct { - Name string + Name string + Output *ResourceProvider } func (n *EvalGetProvider) Args() ([]EvalNode, []EvalType) { @@ -69,6 +70,10 @@ func (n *EvalGetProvider) Eval( return nil, fmt.Errorf("provider %s not initialized", n.Name) } + if n.Output != nil { + *n.Output = result + } + return result, nil } diff --git a/terraform/eval_refresh.go b/terraform/eval_refresh.go index 34bea90f8..5188650d3 100644 --- a/terraform/eval_refresh.go +++ b/terraform/eval_refresh.go @@ -1,29 +1,31 @@ package terraform +import ( + "log" +) + // EvalRefresh is an EvalNode implementation that does a refresh for // a resource. type EvalRefresh struct { Provider EvalNode - State EvalNode + State **InstanceState Info *InstanceInfo + Output **InstanceState } func (n *EvalRefresh) Args() ([]EvalNode, []EvalType) { - return []EvalNode{n.Provider, n.State}, - []EvalType{EvalTypeResourceProvider, EvalTypeInstanceState} + return []EvalNode{n.Provider}, []EvalType{EvalTypeResourceProvider} } // TODO: test func (n *EvalRefresh) Eval( ctx EvalContext, args []interface{}) (interface{}, error) { - var state *InstanceState provider := args[0].(ResourceProvider) - if args[1] != nil { - state = args[1].(*InstanceState) - } + state := *n.State // If we have no state, we don't do any refreshing if state == nil { + log.Printf("[DEBUG] refresh: %s: no state, not refreshing", n.Info.Id) return nil, nil } @@ -49,6 +51,9 @@ func (n *EvalRefresh) Eval( return nil, err } + if n.Output != nil { + *n.Output = state + } return state, nil } diff --git a/terraform/eval_state.go b/terraform/eval_state.go index 92d612777..d7eca6432 100644 --- a/terraform/eval_state.go +++ b/terraform/eval_state.go @@ -10,6 +10,7 @@ type EvalReadState struct { Name string Tainted bool TaintedIndex int + Output **InstanceState } func (n *EvalReadState) Args() ([]EvalNode, []EvalType) { @@ -37,13 +38,21 @@ func (n *EvalReadState) Eval( return nil, nil } + var result *InstanceState if !n.Tainted { // Return the primary - return rs.Primary, nil + result = rs.Primary } else { // Return the proper tainted resource - return rs.Tainted[n.TaintedIndex], nil + result = rs.Tainted[n.TaintedIndex] } + + // Write the result to the output pointer + if n.Output != nil { + *n.Output = result + } + + return result, nil } func (n *EvalReadState) Type() EvalType { @@ -56,23 +65,18 @@ type EvalWriteState struct { Name string ResourceType string Dependencies []string - State EvalNode + State **InstanceState Tainted bool TaintedIndex int } func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) { - return []EvalNode{n.State}, []EvalType{EvalTypeInstanceState} + return nil, nil } // TODO: test func (n *EvalWriteState) Eval( ctx EvalContext, args []interface{}) (interface{}, error) { - var instanceState *InstanceState - if args[0] != nil { - instanceState = args[0].(*InstanceState) - } - state, lock := ctx.State() if state == nil { return nil, fmt.Errorf("cannot write state to nil state") @@ -100,11 +104,11 @@ func (n *EvalWriteState) Eval( if n.Tainted { if n.TaintedIndex != -1 { - rs.Tainted[n.TaintedIndex] = instanceState + rs.Tainted[n.TaintedIndex] = *n.State } } else { // Set the primary state - rs.Primary = instanceState + rs.Primary = *n.State } // Prune because why not, we can clear out old useless entries now diff --git a/terraform/state.go b/terraform/state.go index d14ae043b..2d1f95d84 100644 --- a/terraform/state.go +++ b/terraform/state.go @@ -550,7 +550,7 @@ func (r *ResourceState) prune() { n := len(r.Tainted) for i := 0; i < n; i++ { inst := r.Tainted[i] - if inst.ID == "" { + if inst == nil || inst.ID == "" { copy(r.Tainted[i:], r.Tainted[i+1:]) r.Tainted[n-1] = nil n-- diff --git a/terraform/transform_orphan.go b/terraform/transform_orphan.go index 427da5297..6d446f41b 100644 --- a/terraform/transform_orphan.go +++ b/terraform/transform_orphan.go @@ -171,6 +171,8 @@ func (n *graphNodeOrphanResource) ProvidedBy() []string { // GraphNodeEvalable impl. func (n *graphNodeOrphanResource) EvalTree() EvalNode { + var state *InstanceState + seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} // Build instance info @@ -180,22 +182,30 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode { // Refresh the resource seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkRefresh}, - Node: &EvalWriteState{ - Name: n.ResourceName, - ResourceType: n.ResourceType, - Dependencies: n.DependentOn(), - State: &EvalRefresh{ - Info: info, - Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, - State: &EvalReadState{ - Name: n.ResourceName, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalReadState{ + Name: n.ResourceName, + Output: &state, + }, + &EvalRefresh{ + Info: info, + Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, + State: &state, + Output: &state, + }, + &EvalWriteState{ + Name: n.ResourceName, + ResourceType: n.ResourceType, + Dependencies: n.DependentOn(), + State: &state, }, }, }, }) // Diff the resource - var diff InstanceDiff + var diff *InstanceDiff seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkPlan, walkPlanDestroy}, Node: &EvalSequence{ diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index ed87bc868..e490c51e5 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -98,6 +98,9 @@ func (n *graphNodeExpandedResource) ProvidedBy() []string { // GraphNodeEvalable impl. func (n *graphNodeExpandedResource) EvalTree() EvalNode { + var diff *InstanceDiff + var state *InstanceState + // Build the resource. If we aren't part of a multi-resource, then // we still consider ourselves as count index zero. index := n.Index @@ -145,35 +148,46 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { // Refresh the resource seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkRefresh}, - Node: &EvalWriteState{ - Name: n.stateId(), - ResourceType: n.Resource.Type, - Dependencies: n.DependentOn(), - State: &EvalRefresh{ - Info: info, - Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, - State: &EvalReadState{Name: n.stateId()}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalReadState{ + Name: n.stateId(), + Output: &state, + }, + &EvalRefresh{ + Info: info, + Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, + State: &state, + Output: &state, + }, + &EvalWriteState{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &state, + }, }, }, }) // Diff the resource - var diff InstanceDiff seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkPlan}, Node: &EvalSequence{ Nodes: []EvalNode{ + &EvalDiff{ + Info: info, + Config: interpolateNode, + Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, + State: &EvalReadState{Name: n.stateId()}, + Output: &diff, + OutputState: &state, + }, &EvalWriteState{ Name: n.stateId(), ResourceType: n.Resource.Type, Dependencies: n.DependentOn(), - State: &EvalDiff{ - Info: info, - Config: interpolateNode, - Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, - State: &EvalReadState{Name: n.stateId()}, - Output: &diff, - }, + State: &state, }, &EvalDiffTainted{ Diff: &diff, @@ -205,6 +219,41 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode { }, }) + // Diff the resource for destruction + var provider ResourceProvider + seq.Nodes = append(seq.Nodes, &EvalOpFilter{ + Ops: []walkOperation{walkApply}, + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalGetProvider{ + Name: n.ProvidedBy()[0], + Output: &provider, + }, + &EvalReadDiff{ + Name: n.stateId(), + Diff: &diff, + }, + &EvalReadState{ + Name: n.stateId(), + Output: &state, + }, + &EvalApply{ + Info: info, + State: &state, + Diff: &diff, + Provider: &provider, + Output: &state, + }, + &EvalWriteState{ + Name: n.stateId(), + ResourceType: n.Resource.Type, + Dependencies: n.DependentOn(), + State: &state, + }, + }, + }, + }) + return seq } diff --git a/terraform/transform_tainted.go b/terraform/transform_tainted.go index 1ae16558f..c6bbdb2b6 100644 --- a/terraform/transform_tainted.go +++ b/terraform/transform_tainted.go @@ -65,6 +65,8 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string { // GraphNodeEvalable impl. func (n *graphNodeTaintedResource) EvalTree() EvalNode { + var state *InstanceState + seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} // Build instance info @@ -74,19 +76,27 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode { // Refresh the resource seq.Nodes = append(seq.Nodes, &EvalOpFilter{ Ops: []walkOperation{walkRefresh}, - Node: &EvalWriteState{ - Name: n.ResourceName, - ResourceType: n.ResourceType, - Dependencies: n.DependentOn(), - Tainted: true, - TaintedIndex: n.Index, - State: &EvalRefresh{ - Info: info, - Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, - State: &EvalReadState{ + Node: &EvalSequence{ + Nodes: []EvalNode{ + &EvalReadState{ Name: n.ResourceName, Tainted: true, TaintedIndex: n.Index, + Output: &state, + }, + &EvalRefresh{ + Info: info, + Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, + State: &state, + Output: &state, + }, + &EvalWriteState{ + Name: n.ResourceName, + ResourceType: n.ResourceType, + Dependencies: n.DependentOn(), + State: &state, + Tainted: true, + TaintedIndex: n.Index, }, }, },