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.
//
// 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)
}
}
*/
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")

View File

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

View File

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

View File

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

View File

@ -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

View File

@ -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--

View File

@ -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{

View File

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

View File

@ -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,
},
},
},