From 1e962b868d31e08ad759c1af9d56d098ba7aadf3 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 11 Feb 2015 08:48:45 -0800 Subject: [PATCH] terraform: Refresh, Read/Write state --- terraform/context.go | 2 +- terraform/eval_context.go | 15 +++++ terraform/eval_context_builtin.go | 6 ++ terraform/eval_filter_operation.go | 35 ++++++++++ terraform/eval_refresh.go | 31 +++++++++ terraform/eval_sequence.go | 7 ++ terraform/eval_sequence_test.go | 9 +++ terraform/eval_state.go | 102 +++++++++++++++++++++++++++++ terraform/eval_type.go | 1 + terraform/evaltype_string.go | 4 ++ terraform/graph_walk_context.go | 2 + terraform/transform_resource.go | 19 ++++++ 12 files changed, 232 insertions(+), 1 deletion(-) create mode 100644 terraform/eval_refresh.go create mode 100644 terraform/eval_sequence_test.go create mode 100644 terraform/eval_state.go diff --git a/terraform/context.go b/terraform/context.go index 6fa0ec035..54a1f5b0f 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -83,7 +83,7 @@ func (c *Context2) Refresh() (*State, []error) { return nil, multierror.Append(errs, err).Errors } - return nil, nil + return c.state, nil } // Validate validates the configuration and returns any warnings or errors. diff --git a/terraform/eval_context.go b/terraform/eval_context.go index c1ac47006..e4d4b5cee 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -1,6 +1,8 @@ package terraform import ( + "sync" + "github.com/hashicorp/terraform/config" ) @@ -42,6 +44,10 @@ type EvalContext interface { // The resource argument is optional. If given, it is the resource // that is currently being acted upon. 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 @@ -82,6 +88,10 @@ type MockEvalContext struct { PathCalled bool PathPath []string + + StateCalled bool + StateState *State + StateLock *sync.RWMutex } func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) { @@ -133,3 +143,8 @@ func (c *MockEvalContext) Path() []string { c.PathCalled = true return c.PathPath } + +func (c *MockEvalContext) State() (*State, *sync.RWMutex) { + c.StateCalled = true + return c.StateState, c.StateLock +} diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index f95bea23f..a3d727c80 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -21,6 +21,8 @@ type BuiltinEvalContext struct { Provisioners map[string]ResourceProvisionerFactory ProvisionerCache map[string]ResourceProvisioner ProvisionerLock *sync.Mutex + StateValue *State + StateLock *sync.RWMutex once sync.Once } @@ -155,6 +157,10 @@ func (ctx *BuiltinEvalContext) Path() []string { return ctx.PathValue } +func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) { + return ctx.StateValue, ctx.StateLock +} + func (ctx *BuiltinEvalContext) init() { // We nil-check the things below because they're meant to be configured, // and we just default them to non-nil. diff --git a/terraform/eval_filter_operation.go b/terraform/eval_filter_operation.go index c10e5918a..7f30ea980 100644 --- a/terraform/eval_filter_operation.go +++ b/terraform/eval_filter_operation.go @@ -21,3 +21,38 @@ func EvalNodeFilterOp(op walkOperation) EvalNodeFilterFunc { 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 +} diff --git a/terraform/eval_refresh.go b/terraform/eval_refresh.go new file mode 100644 index 000000000..cf5876acc --- /dev/null +++ b/terraform/eval_refresh.go @@ -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 +} diff --git a/terraform/eval_sequence.go b/terraform/eval_sequence.go index e475e6bdd..fc2828f5d 100644 --- a/terraform/eval_sequence.go +++ b/terraform/eval_sequence.go @@ -31,3 +31,10 @@ func (n *EvalSequence) Type() EvalType { 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) + } +} diff --git a/terraform/eval_sequence_test.go b/terraform/eval_sequence_test.go new file mode 100644 index 000000000..972f0cd6f --- /dev/null +++ b/terraform/eval_sequence_test.go @@ -0,0 +1,9 @@ +package terraform + +import ( + "testing" +) + +func TestEvalSequence_impl(t *testing.T) { + var _ EvalNodeFilterable = new(EvalSequence) +} diff --git a/terraform/eval_state.go b/terraform/eval_state.go new file mode 100644 index 000000000..6a7f98840 --- /dev/null +++ b/terraform/eval_state.go @@ -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 +} diff --git a/terraform/eval_type.go b/terraform/eval_type.go index 62c2a8515..9a951fdca 100644 --- a/terraform/eval_type.go +++ b/terraform/eval_type.go @@ -17,4 +17,5 @@ const ( EvalTypeConfig // *ResourceConfig EvalTypeResourceProvider // ResourceProvider EvalTypeResourceProvisioner // ResourceProvisioner + EvalTypeInstanceState // *InstanceState ) diff --git a/terraform/evaltype_string.go b/terraform/evaltype_string.go index d2efcabde..c6126790d 100644 --- a/terraform/evaltype_string.go +++ b/terraform/evaltype_string.go @@ -10,6 +10,7 @@ const ( _EvalType_name_2 = "EvalTypeConfig" _EvalType_name_3 = "EvalTypeResourceProvider" _EvalType_name_4 = "EvalTypeResourceProvisioner" + _EvalType_name_5 = "EvalTypeInstanceState" ) var ( @@ -18,6 +19,7 @@ var ( _EvalType_index_2 = [...]uint8{0, 14} _EvalType_index_3 = [...]uint8{0, 24} _EvalType_index_4 = [...]uint8{0, 27} + _EvalType_index_5 = [...]uint8{0, 21} ) func (i EvalType) String() string { @@ -32,6 +34,8 @@ func (i EvalType) String() string { return _EvalType_name_3 case i == 16: return _EvalType_name_4 + case i == 32: + return _EvalType_name_5 default: return fmt.Sprintf("EvalType(%d)", i) } diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index abc8948d4..7f8316424 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -43,6 +43,8 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext { Provisioners: w.Context.provisioners, ProvisionerCache: w.provisionerCache, ProvisionerLock: &w.provisionerLock, + StateValue: w.Context.state, + StateLock: &w.Context.stateLock, Interpolater: &Interpolater{ Operation: w.Operation, Module: w.Context.module, diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 9d1d6708d..5b5af1180 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -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 } // stateId is the name used for the state key func (n *graphNodeExpandedResource) stateId() string { + if n.Index == 0 { + return n.Resource.Id() + } + return fmt.Sprintf("%s.%d", n.Resource.Id(), n.Index) }