terraform: make things more linear

This commit is contained in:
Mitchell Hashimoto 2015-02-12 14:46:22 -08:00
parent 67e7aeeea0
commit 93f3050dbd
10 changed files with 213 additions and 72 deletions

View File

@ -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. // Plan generates an execution plan for the given context.
// //
// The execution plan encapsulates the context and can be stored // The execution plan encapsulates the context and can be stored

View File

@ -2671,13 +2671,15 @@ func TestContextInput_varOnly(t *testing.T) {
t.Fatalf("bad: \n%s", actualStr) t.Fatalf("bad: \n%s", actualStr)
} }
} }
*/
func TestContextApply(t *testing.T) { /*
func TestContext2Apply(t *testing.T) {
m := testModule(t, "apply-good") m := testModule(t, "apply-good")
p := testProvider("aws") p := testProvider("aws")
p.ApplyFn = testApplyFn p.ApplyFn = testApplyFn
p.DiffFn = testDiffFn p.DiffFn = testDiffFn
ctx := testContext(t, &ContextOpts{ ctx := testContext2(t, &ContextOpts{
Module: m, Module: m,
Providers: map[string]ResourceProviderFactory{ Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p), "aws": testProviderFuncFixed(p),
@ -2705,6 +2707,7 @@ func TestContextApply(t *testing.T) {
} }
} }
/*
func TestContextApply_emptyModule(t *testing.T) { func TestContextApply_emptyModule(t *testing.T) {
m := testModule(t, "apply-empty-module") m := testModule(t, "apply-empty-module")
p := testProvider("aws") p := testProvider("aws")

View File

@ -3,11 +3,12 @@ package terraform
// EvalDiff is an EvalNode implementation that does a refresh for // EvalDiff is an EvalNode implementation that does a refresh for
// a resource. // a resource.
type EvalDiff struct { type EvalDiff struct {
Info *InstanceInfo Info *InstanceInfo
Config EvalNode Config EvalNode
Provider EvalNode Provider EvalNode
State EvalNode State EvalNode
Output *InstanceDiff Output **InstanceDiff
OutputState **InstanceState
} }
func (n *EvalDiff) Args() ([]EvalNode, []EvalType) { func (n *EvalDiff) Args() ([]EvalNode, []EvalType) {
@ -82,11 +83,12 @@ func (n *EvalDiff) Eval(
} }
// Update our output // Update our output
*n.Output = *diff *n.Output = diff
*n.OutputState = state
// Merge our state so that the state is updated with our plan // Merge our state so that the state is updated with our plan
if !diff.Empty() { if !diff.Empty() && n.OutputState != nil {
state = state.MergeDiff(diff) *n.OutputState = state.MergeDiff(diff)
} }
return state, nil return state, nil
@ -101,7 +103,7 @@ func (n *EvalDiff) Type() EvalType {
type EvalDiffDestroy struct { type EvalDiffDestroy struct {
Info *InstanceInfo Info *InstanceInfo
State EvalNode State EvalNode
Output *InstanceDiff Output **InstanceDiff
} }
func (n *EvalDiffDestroy) Args() ([]EvalNode, []EvalType) { func (n *EvalDiffDestroy) Args() ([]EvalNode, []EvalType) {
@ -142,7 +144,7 @@ func (n *EvalDiffDestroy) Eval(
} }
// Update our output // Update our output
*n.Output = *diff *n.Output = diff
return nil, nil return nil, nil
} }
@ -188,7 +190,7 @@ func (n *EvalDiffDestroyModule) Type() EvalType {
// the full diff. // the full diff.
type EvalDiffTainted struct { type EvalDiffTainted struct {
Name string Name string
Diff *InstanceDiff Diff **InstanceDiff
} }
func (n *EvalDiffTainted) Args() ([]EvalNode, []EvalType) { 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 we have tainted, then mark it on the diff
if len(rs.Tainted) > 0 { if len(rs.Tainted) > 0 {
n.Diff.DestroyTainted = true (*n.Diff).DestroyTainted = true
} }
return nil, nil return nil, nil
@ -228,11 +230,46 @@ func (n *EvalDiffTainted) Type() EvalType {
return EvalTypeNull 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 // EvalWriteDiff is an EvalNode implementation that writes the diff to
// the full diff. // the full diff.
type EvalWriteDiff struct { type EvalWriteDiff struct {
Name string Name string
Diff *InstanceDiff Diff **InstanceDiff
} }
func (n *EvalWriteDiff) Args() ([]EvalNode, []EvalType) { func (n *EvalWriteDiff) Args() ([]EvalNode, []EvalType) {
@ -245,7 +282,7 @@ func (n *EvalWriteDiff) Eval(
diff, lock := ctx.Diff() diff, lock := ctx.Diff()
// The diff to write, if its empty it should write nil // The diff to write, if its empty it should write nil
diffVal := n.Diff diffVal := *n.Diff
if diffVal.Empty() { if diffVal.Empty() {
diffVal = nil diffVal = nil
} }

View File

@ -55,7 +55,8 @@ func (n *EvalInitProvider) Type() EvalType {
// EvalGetProvider is an EvalNode implementation that retrieves an already // EvalGetProvider is an EvalNode implementation that retrieves an already
// initialized provider instance for the given name. // initialized provider instance for the given name.
type EvalGetProvider struct { type EvalGetProvider struct {
Name string Name string
Output *ResourceProvider
} }
func (n *EvalGetProvider) Args() ([]EvalNode, []EvalType) { func (n *EvalGetProvider) Args() ([]EvalNode, []EvalType) {
@ -69,6 +70,10 @@ func (n *EvalGetProvider) Eval(
return nil, fmt.Errorf("provider %s not initialized", n.Name) return nil, fmt.Errorf("provider %s not initialized", n.Name)
} }
if n.Output != nil {
*n.Output = result
}
return result, nil return result, nil
} }

View File

@ -1,29 +1,31 @@
package terraform package terraform
import (
"log"
)
// EvalRefresh is an EvalNode implementation that does a refresh for // EvalRefresh is an EvalNode implementation that does a refresh for
// a resource. // a resource.
type EvalRefresh struct { type EvalRefresh struct {
Provider EvalNode Provider EvalNode
State EvalNode State **InstanceState
Info *InstanceInfo Info *InstanceInfo
Output **InstanceState
} }
func (n *EvalRefresh) Args() ([]EvalNode, []EvalType) { func (n *EvalRefresh) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.Provider, n.State}, return []EvalNode{n.Provider}, []EvalType{EvalTypeResourceProvider}
[]EvalType{EvalTypeResourceProvider, EvalTypeInstanceState}
} }
// TODO: test // TODO: test
func (n *EvalRefresh) Eval( func (n *EvalRefresh) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) { ctx EvalContext, args []interface{}) (interface{}, error) {
var state *InstanceState
provider := args[0].(ResourceProvider) provider := args[0].(ResourceProvider)
if args[1] != nil { state := *n.State
state = args[1].(*InstanceState)
}
// If we have no state, we don't do any refreshing // If we have no state, we don't do any refreshing
if state == nil { if state == nil {
log.Printf("[DEBUG] refresh: %s: no state, not refreshing", n.Info.Id)
return nil, nil return nil, nil
} }
@ -49,6 +51,9 @@ func (n *EvalRefresh) Eval(
return nil, err return nil, err
} }
if n.Output != nil {
*n.Output = state
}
return state, nil return state, nil
} }

View File

@ -10,6 +10,7 @@ type EvalReadState struct {
Name string Name string
Tainted bool Tainted bool
TaintedIndex int TaintedIndex int
Output **InstanceState
} }
func (n *EvalReadState) Args() ([]EvalNode, []EvalType) { func (n *EvalReadState) Args() ([]EvalNode, []EvalType) {
@ -37,13 +38,21 @@ func (n *EvalReadState) Eval(
return nil, nil return nil, nil
} }
var result *InstanceState
if !n.Tainted { if !n.Tainted {
// Return the primary // Return the primary
return rs.Primary, nil result = rs.Primary
} else { } else {
// Return the proper tainted resource // 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 { func (n *EvalReadState) Type() EvalType {
@ -56,23 +65,18 @@ type EvalWriteState struct {
Name string Name string
ResourceType string ResourceType string
Dependencies []string Dependencies []string
State EvalNode State **InstanceState
Tainted bool Tainted bool
TaintedIndex int TaintedIndex int
} }
func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) { func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.State}, []EvalType{EvalTypeInstanceState} return nil, nil
} }
// TODO: test // TODO: test
func (n *EvalWriteState) Eval( func (n *EvalWriteState) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) { ctx EvalContext, args []interface{}) (interface{}, error) {
var instanceState *InstanceState
if args[0] != nil {
instanceState = args[0].(*InstanceState)
}
state, lock := ctx.State() state, lock := ctx.State()
if state == nil { if state == nil {
return nil, fmt.Errorf("cannot write state to nil state") return nil, fmt.Errorf("cannot write state to nil state")
@ -100,11 +104,11 @@ func (n *EvalWriteState) Eval(
if n.Tainted { if n.Tainted {
if n.TaintedIndex != -1 { if n.TaintedIndex != -1 {
rs.Tainted[n.TaintedIndex] = instanceState rs.Tainted[n.TaintedIndex] = *n.State
} }
} else { } else {
// Set the primary state // Set the primary state
rs.Primary = instanceState rs.Primary = *n.State
} }
// Prune because why not, we can clear out old useless entries now // Prune because why not, we can clear out old useless entries now

View File

@ -550,7 +550,7 @@ func (r *ResourceState) prune() {
n := len(r.Tainted) n := len(r.Tainted)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
inst := r.Tainted[i] inst := r.Tainted[i]
if inst.ID == "" { if inst == nil || inst.ID == "" {
copy(r.Tainted[i:], r.Tainted[i+1:]) copy(r.Tainted[i:], r.Tainted[i+1:])
r.Tainted[n-1] = nil r.Tainted[n-1] = nil
n-- n--

View File

@ -171,6 +171,8 @@ func (n *graphNodeOrphanResource) ProvidedBy() []string {
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *graphNodeOrphanResource) EvalTree() EvalNode { func (n *graphNodeOrphanResource) EvalTree() EvalNode {
var state *InstanceState
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Build instance info // Build instance info
@ -180,22 +182,30 @@ func (n *graphNodeOrphanResource) EvalTree() EvalNode {
// Refresh the resource // Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh}, Ops: []walkOperation{walkRefresh},
Node: &EvalWriteState{ Node: &EvalSequence{
Name: n.ResourceName, Nodes: []EvalNode{
ResourceType: n.ResourceType, &EvalReadState{
Dependencies: n.DependentOn(), Name: n.ResourceName,
State: &EvalRefresh{ Output: &state,
Info: info, },
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, &EvalRefresh{
State: &EvalReadState{ Info: info,
Name: n.ResourceName, 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 // Diff the resource
var diff InstanceDiff var diff *InstanceDiff
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan, walkPlanDestroy}, Ops: []walkOperation{walkPlan, walkPlanDestroy},
Node: &EvalSequence{ Node: &EvalSequence{

View File

@ -98,6 +98,9 @@ func (n *graphNodeExpandedResource) ProvidedBy() []string {
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *graphNodeExpandedResource) EvalTree() EvalNode { func (n *graphNodeExpandedResource) EvalTree() EvalNode {
var diff *InstanceDiff
var state *InstanceState
// Build the resource. If we aren't part of a multi-resource, then // Build the resource. If we aren't part of a multi-resource, then
// we still consider ourselves as count index zero. // we still consider ourselves as count index zero.
index := n.Index index := n.Index
@ -145,35 +148,46 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
// Refresh the resource // Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh}, Ops: []walkOperation{walkRefresh},
Node: &EvalWriteState{ Node: &EvalSequence{
Name: n.stateId(), Nodes: []EvalNode{
ResourceType: n.Resource.Type, &EvalReadState{
Dependencies: n.DependentOn(), Name: n.stateId(),
State: &EvalRefresh{ Output: &state,
Info: info, },
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]}, &EvalRefresh{
State: &EvalReadState{Name: n.stateId()}, 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 // Diff the resource
var diff InstanceDiff
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkPlan}, Ops: []walkOperation{walkPlan},
Node: &EvalSequence{ Node: &EvalSequence{
Nodes: []EvalNode{ Nodes: []EvalNode{
&EvalDiff{
Info: info,
Config: interpolateNode,
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
State: &EvalReadState{Name: n.stateId()},
Output: &diff,
OutputState: &state,
},
&EvalWriteState{ &EvalWriteState{
Name: n.stateId(), Name: n.stateId(),
ResourceType: n.Resource.Type, ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(), Dependencies: n.DependentOn(),
State: &EvalDiff{ State: &state,
Info: info,
Config: interpolateNode,
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
State: &EvalReadState{Name: n.stateId()},
Output: &diff,
},
}, },
&EvalDiffTainted{ &EvalDiffTainted{
Diff: &diff, 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 return seq
} }

View File

@ -65,6 +65,8 @@ func (n *graphNodeTaintedResource) ProvidedBy() []string {
// GraphNodeEvalable impl. // GraphNodeEvalable impl.
func (n *graphNodeTaintedResource) EvalTree() EvalNode { func (n *graphNodeTaintedResource) EvalTree() EvalNode {
var state *InstanceState
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)} seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Build instance info // Build instance info
@ -74,19 +76,27 @@ func (n *graphNodeTaintedResource) EvalTree() EvalNode {
// Refresh the resource // Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{ seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh}, Ops: []walkOperation{walkRefresh},
Node: &EvalWriteState{ Node: &EvalSequence{
Name: n.ResourceName, Nodes: []EvalNode{
ResourceType: n.ResourceType, &EvalReadState{
Dependencies: n.DependentOn(),
Tainted: true,
TaintedIndex: n.Index,
State: &EvalRefresh{
Info: info,
Provider: &EvalGetProvider{Name: n.ProvidedBy()[0]},
State: &EvalReadState{
Name: n.ResourceName, Name: n.ResourceName,
Tainted: true, Tainted: true,
TaintedIndex: n.Index, 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,
}, },
}, },
}, },