terraform: Refresh, Read/Write state

This commit is contained in:
Mitchell Hashimoto 2015-02-11 08:48:45 -08:00
parent 2b8fd18fa8
commit 1e962b868d
12 changed files with 232 additions and 1 deletions

View File

@ -83,7 +83,7 @@ func (c *Context2) Refresh() (*State, []error) {
return nil, multierror.Append(errs, err).Errors return nil, multierror.Append(errs, err).Errors
} }
return nil, nil return c.state, nil
} }
// Validate validates the configuration and returns any warnings or errors. // Validate validates the configuration and returns any warnings or errors.

View File

@ -1,6 +1,8 @@
package terraform package terraform
import ( import (
"sync"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
) )
@ -42,6 +44,10 @@ type EvalContext interface {
// The resource argument is optional. If given, it is the resource // The resource argument is optional. If given, it is the resource
// that is currently being acted upon. // that is currently being acted upon.
Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error) Interpolate(*config.RawConfig, *Resource) (*ResourceConfig, error)
// State returns the global state as well as the lock that should
// be used to modify that state.
State() (*State, *sync.RWMutex)
} }
// MockEvalContext is a mock version of EvalContext that can be used // MockEvalContext is a mock version of EvalContext that can be used
@ -82,6 +88,10 @@ type MockEvalContext struct {
PathCalled bool PathCalled bool
PathPath []string PathPath []string
StateCalled bool
StateState *State
StateLock *sync.RWMutex
} }
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) { func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
@ -133,3 +143,8 @@ func (c *MockEvalContext) Path() []string {
c.PathCalled = true c.PathCalled = true
return c.PathPath return c.PathPath
} }
func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
c.StateCalled = true
return c.StateState, c.StateLock
}

View File

@ -21,6 +21,8 @@ type BuiltinEvalContext struct {
Provisioners map[string]ResourceProvisionerFactory Provisioners map[string]ResourceProvisionerFactory
ProvisionerCache map[string]ResourceProvisioner ProvisionerCache map[string]ResourceProvisioner
ProvisionerLock *sync.Mutex ProvisionerLock *sync.Mutex
StateValue *State
StateLock *sync.RWMutex
once sync.Once once sync.Once
} }
@ -155,6 +157,10 @@ func (ctx *BuiltinEvalContext) Path() []string {
return ctx.PathValue return ctx.PathValue
} }
func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
return ctx.StateValue, ctx.StateLock
}
func (ctx *BuiltinEvalContext) init() { func (ctx *BuiltinEvalContext) init() {
// We nil-check the things below because they're meant to be configured, // We nil-check the things below because they're meant to be configured,
// and we just default them to non-nil. // and we just default them to non-nil.

View File

@ -21,3 +21,38 @@ func EvalNodeFilterOp(op walkOperation) EvalNodeFilterFunc {
return EvalNoop{} return EvalNoop{}
} }
} }
// EvalOpFilter is an EvalNode implementation that is a proxy to
// another node but filters based on the operation.
type EvalOpFilter struct {
// Ops is the list of operations to include this node in.
Ops []walkOperation
// Node is the node to execute
Node EvalNode
}
func (n *EvalOpFilter) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.Node}, []EvalType{n.Node.Type()}
}
// TODO: test
func (n *EvalOpFilter) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
return args[0], nil
}
func (n *EvalOpFilter) Type() EvalType {
return n.Node.Type()
}
// EvalNodeOpFilterable impl.
func (n *EvalOpFilter) IncludeInOp(op walkOperation) bool {
for _, v := range n.Ops {
if v == op {
return true
}
}
return false
}

31
terraform/eval_refresh.go Normal file
View File

@ -0,0 +1,31 @@
package terraform
// EvalRefresh is an EvalNode implementation that does a refresh for
// a resource.
type EvalRefresh struct {
Provider EvalNode
State EvalNode
Info *InstanceInfo
}
func (n *EvalRefresh) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.Provider, n.State},
[]EvalType{EvalTypeResourceProvider, EvalTypeInstanceState}
}
// 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)
}
n.Info.ModulePath = ctx.Path()
return provider.Refresh(n.Info, state)
}
func (n *EvalRefresh) Type() EvalType {
return EvalTypeInstanceState
}

View File

@ -31,3 +31,10 @@ func (n *EvalSequence) Type() EvalType {
return n.Nodes[len(n.Nodes)-1].Type() return n.Nodes[len(n.Nodes)-1].Type()
} }
// EvalNodeFilterable impl.
func (n *EvalSequence) Filter(fn EvalNodeFilterFunc) {
for i, node := range n.Nodes {
n.Nodes[i] = fn(node)
}
}

View File

@ -0,0 +1,9 @@
package terraform
import (
"testing"
)
func TestEvalSequence_impl(t *testing.T) {
var _ EvalNodeFilterable = new(EvalSequence)
}

102
terraform/eval_state.go Normal file
View File

@ -0,0 +1,102 @@
package terraform
import (
"fmt"
)
// EvalReadState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state.
type EvalReadState struct {
Name string
}
func (n *EvalReadState) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalReadState) Eval(
ctx EvalContext, args []interface{}) (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
}
// Return the primary
return rs.Primary, nil
}
func (n *EvalReadState) Type() EvalType {
return EvalTypeInstanceState
}
// 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 EvalNode
}
func (n *EvalWriteState) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.State}, []EvalType{EvalTypeInstanceState}
}
// 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")
}
// 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
// Set the primary state
rs.Primary = instanceState
// Prune because why not, we can clear out old useless entries now
rs.prune()
return nil, nil
}
func (n *EvalWriteState) Type() EvalType {
return EvalTypeNull
}

View File

@ -17,4 +17,5 @@ const (
EvalTypeConfig // *ResourceConfig EvalTypeConfig // *ResourceConfig
EvalTypeResourceProvider // ResourceProvider EvalTypeResourceProvider // ResourceProvider
EvalTypeResourceProvisioner // ResourceProvisioner EvalTypeResourceProvisioner // ResourceProvisioner
EvalTypeInstanceState // *InstanceState
) )

View File

@ -10,6 +10,7 @@ const (
_EvalType_name_2 = "EvalTypeConfig" _EvalType_name_2 = "EvalTypeConfig"
_EvalType_name_3 = "EvalTypeResourceProvider" _EvalType_name_3 = "EvalTypeResourceProvider"
_EvalType_name_4 = "EvalTypeResourceProvisioner" _EvalType_name_4 = "EvalTypeResourceProvisioner"
_EvalType_name_5 = "EvalTypeInstanceState"
) )
var ( var (
@ -18,6 +19,7 @@ var (
_EvalType_index_2 = [...]uint8{0, 14} _EvalType_index_2 = [...]uint8{0, 14}
_EvalType_index_3 = [...]uint8{0, 24} _EvalType_index_3 = [...]uint8{0, 24}
_EvalType_index_4 = [...]uint8{0, 27} _EvalType_index_4 = [...]uint8{0, 27}
_EvalType_index_5 = [...]uint8{0, 21}
) )
func (i EvalType) String() string { func (i EvalType) String() string {
@ -32,6 +34,8 @@ func (i EvalType) String() string {
return _EvalType_name_3 return _EvalType_name_3
case i == 16: case i == 16:
return _EvalType_name_4 return _EvalType_name_4
case i == 32:
return _EvalType_name_5
default: default:
return fmt.Sprintf("EvalType(%d)", i) return fmt.Sprintf("EvalType(%d)", i)
} }

View File

@ -43,6 +43,8 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
Provisioners: w.Context.provisioners, Provisioners: w.Context.provisioners,
ProvisionerCache: w.provisionerCache, ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock, ProvisionerLock: &w.provisionerLock,
StateValue: w.Context.state,
StateLock: &w.Context.stateLock,
Interpolater: &Interpolater{ Interpolater: &Interpolater{
Operation: w.Operation, Operation: w.Operation,
Module: w.Context.module, Module: w.Context.module,

View File

@ -96,10 +96,29 @@ 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{
Provider: &EvalGetProvider{Name: n.ProvidedBy()},
State: &EvalReadState{Name: n.stateId()},
Info: &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type},
},
},
})
return seq return seq
} }
// stateId is the name used for the state key // stateId is the name used for the state key
func (n *graphNodeExpandedResource) stateId() string { func (n *graphNodeExpandedResource) stateId() string {
if n.Index == 0 {
return n.Resource.Id()
}
return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index)
} }