terraform: PostStateUpdate hook and EvalUpdateStateHook

This commit is contained in:
Mitchell Hashimoto 2015-02-23 19:09:48 -08:00
parent bfe0edef51
commit 429711b938
7 changed files with 237 additions and 153 deletions

View File

@ -70,155 +70,3 @@ type EvalContext interface {
// be used to modify that state. // be used to modify that state.
State() (*State, *sync.RWMutex) State() (*State, *sync.RWMutex)
} }
// MockEvalContext is a mock version of EvalContext that can be used
// for tests.
type MockEvalContext struct {
HookCalled bool
HookError error
InputCalled bool
InputInput UIInput
InitProviderCalled bool
InitProviderName string
InitProviderProvider ResourceProvider
InitProviderError error
ProviderCalled bool
ProviderName string
ProviderProvider ResourceProvider
ProviderInputCalled bool
ProviderInputName string
ProviderInputConfig map[string]interface{}
SetProviderInputCalled bool
SetProviderInputName string
SetProviderInputConfig map[string]interface{}
ConfigureProviderCalled bool
ConfigureProviderName string
ConfigureProviderConfig *ResourceConfig
ConfigureProviderError error
ParentProviderConfigCalled bool
ParentProviderConfigName string
ParentProviderConfigConfig *ResourceConfig
InitProvisionerCalled bool
InitProvisionerName string
InitProvisionerProvisioner ResourceProvisioner
InitProvisionerError error
ProvisionerCalled bool
ProvisionerName string
ProvisionerProvisioner ResourceProvisioner
InterpolateCalled bool
InterpolateConfig *config.RawConfig
InterpolateResource *Resource
InterpolateConfigResult *ResourceConfig
InterpolateError error
PathCalled bool
PathPath []string
SetVariablesCalled bool
SetVariablesVariables map[string]string
DiffCalled bool
DiffDiff *Diff
DiffLock *sync.RWMutex
StateCalled bool
StateState *State
StateLock *sync.RWMutex
}
func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
c.HookCalled = true
return c.HookError
}
func (c *MockEvalContext) Input() UIInput {
c.InputCalled = true
return c.InputInput
}
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
c.InitProviderCalled = true
c.InitProviderName = n
return c.InitProviderProvider, c.InitProviderError
}
func (c *MockEvalContext) Provider(n string) ResourceProvider {
c.ProviderCalled = true
c.ProviderName = n
return c.ProviderProvider
}
func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error {
c.ConfigureProviderCalled = true
c.ConfigureProviderName = n
c.ConfigureProviderConfig = cfg
return c.ConfigureProviderError
}
func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig {
c.ParentProviderConfigCalled = true
c.ParentProviderConfigName = n
return c.ParentProviderConfigConfig
}
func (c *MockEvalContext) ProviderInput(n string) map[string]interface{} {
c.ProviderInputCalled = true
c.ProviderInputName = n
return c.ProviderInputConfig
}
func (c *MockEvalContext) SetProviderInput(n string, cfg map[string]interface{}) {
c.SetProviderInputCalled = true
c.SetProviderInputName = n
c.SetProviderInputConfig = cfg
}
func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
c.InitProvisionerCalled = true
c.InitProvisionerName = n
return c.InitProvisionerProvisioner, c.InitProvisionerError
}
func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
c.ProvisionerCalled = true
c.ProvisionerName = n
return c.ProvisionerProvisioner
}
func (c *MockEvalContext) Interpolate(
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
c.InterpolateCalled = true
c.InterpolateConfig = config
c.InterpolateResource = resource
return c.InterpolateConfigResult, c.InterpolateError
}
func (c *MockEvalContext) Path() []string {
c.PathCalled = true
return c.PathPath
}
func (c *MockEvalContext) SetVariables(vs map[string]string) {
c.SetVariablesCalled = true
c.SetVariablesVariables = vs
}
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
c.DiffCalled = true
return c.DiffDiff, c.DiffLock
}
func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
c.StateCalled = true
return c.StateState, c.StateLock
}

View File

@ -0,0 +1,166 @@
package terraform
import (
"sync"
"github.com/hashicorp/terraform/config"
)
// MockEvalContext is a mock version of EvalContext that can be used
// for tests.
type MockEvalContext struct {
HookCalled bool
HookHook Hook
HookError error
InputCalled bool
InputInput UIInput
InitProviderCalled bool
InitProviderName string
InitProviderProvider ResourceProvider
InitProviderError error
ProviderCalled bool
ProviderName string
ProviderProvider ResourceProvider
ProviderInputCalled bool
ProviderInputName string
ProviderInputConfig map[string]interface{}
SetProviderInputCalled bool
SetProviderInputName string
SetProviderInputConfig map[string]interface{}
ConfigureProviderCalled bool
ConfigureProviderName string
ConfigureProviderConfig *ResourceConfig
ConfigureProviderError error
ParentProviderConfigCalled bool
ParentProviderConfigName string
ParentProviderConfigConfig *ResourceConfig
InitProvisionerCalled bool
InitProvisionerName string
InitProvisionerProvisioner ResourceProvisioner
InitProvisionerError error
ProvisionerCalled bool
ProvisionerName string
ProvisionerProvisioner ResourceProvisioner
InterpolateCalled bool
InterpolateConfig *config.RawConfig
InterpolateResource *Resource
InterpolateConfigResult *ResourceConfig
InterpolateError error
PathCalled bool
PathPath []string
SetVariablesCalled bool
SetVariablesVariables map[string]string
DiffCalled bool
DiffDiff *Diff
DiffLock *sync.RWMutex
StateCalled bool
StateState *State
StateLock *sync.RWMutex
}
func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
c.HookCalled = true
if c.HookHook != nil {
if _, err := fn(c.HookHook); err != nil {
return err
}
}
return c.HookError
}
func (c *MockEvalContext) Input() UIInput {
c.InputCalled = true
return c.InputInput
}
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
c.InitProviderCalled = true
c.InitProviderName = n
return c.InitProviderProvider, c.InitProviderError
}
func (c *MockEvalContext) Provider(n string) ResourceProvider {
c.ProviderCalled = true
c.ProviderName = n
return c.ProviderProvider
}
func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error {
c.ConfigureProviderCalled = true
c.ConfigureProviderName = n
c.ConfigureProviderConfig = cfg
return c.ConfigureProviderError
}
func (c *MockEvalContext) ParentProviderConfig(n string) *ResourceConfig {
c.ParentProviderConfigCalled = true
c.ParentProviderConfigName = n
return c.ParentProviderConfigConfig
}
func (c *MockEvalContext) ProviderInput(n string) map[string]interface{} {
c.ProviderInputCalled = true
c.ProviderInputName = n
return c.ProviderInputConfig
}
func (c *MockEvalContext) SetProviderInput(n string, cfg map[string]interface{}) {
c.SetProviderInputCalled = true
c.SetProviderInputName = n
c.SetProviderInputConfig = cfg
}
func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
c.InitProvisionerCalled = true
c.InitProvisionerName = n
return c.InitProvisionerProvisioner, c.InitProvisionerError
}
func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
c.ProvisionerCalled = true
c.ProvisionerName = n
return c.ProvisionerProvisioner
}
func (c *MockEvalContext) Interpolate(
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
c.InterpolateCalled = true
c.InterpolateConfig = config
c.InterpolateResource = resource
return c.InterpolateConfigResult, c.InterpolateError
}
func (c *MockEvalContext) Path() []string {
c.PathCalled = true
return c.PathPath
}
func (c *MockEvalContext) SetVariables(vs map[string]string) {
c.SetVariablesCalled = true
c.SetVariablesVariables = vs
}
func (c *MockEvalContext) Diff() (*Diff, *sync.RWMutex) {
c.DiffCalled = true
return c.DiffDiff, c.DiffLock
}
func (c *MockEvalContext) State() (*State, *sync.RWMutex) {
c.StateCalled = true
return c.StateState, c.StateLock
}

View File

@ -58,6 +58,28 @@ func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) {
return result, nil return result, nil
} }
// EvalUpdateStateHook is an EvalNode implementation that calls the
// PostStateUpdate hook with the current state.
type EvalUpdateStateHook struct{}
func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) {
state, lock := ctx.State()
// Get a read lock so it doesn't change while we're calling this
lock.RLock()
defer lock.RUnlock()
// Call the hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostStateUpdate(state)
})
if err != nil {
return nil, err
}
return nil, nil
}
// EvalWriteState is an EvalNode implementation that reads the // EvalWriteState is an EvalNode implementation that reads the
// InstanceState for a specific resource out of the state. // InstanceState for a specific resource out of the state.
type EvalWriteState struct { type EvalWriteState struct {
@ -111,7 +133,6 @@ func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) {
// Set the primary state // Set the primary state
rs.Primary = *n.State rs.Primary = *n.State
} }
println(fmt.Sprintf("%#v", rs))
return nil, nil return nil, nil
} }

View File

@ -0,0 +1,27 @@
package terraform
import (
"sync"
"testing"
)
func TestEvalUpdateStateHook(t *testing.T) {
mockHook := new(MockHook)
ctx := new(MockEvalContext)
ctx.HookHook = mockHook
ctx.StateState = &State{Serial: 42}
ctx.StateLock = new(sync.RWMutex)
node := &EvalUpdateStateHook{}
if _, err := node.Eval(ctx); err != nil {
t.Fatalf("err: %s", err)
}
if !mockHook.PostStateUpdateCalled {
t.Fatal("should call PostStateUpdate")
}
if mockHook.PostStateUpdateState.Serial != 42 {
t.Fatalf("bad: %#v", mockHook.PostStateUpdateState)
}
}

View File

@ -49,6 +49,9 @@ type Hook interface {
// resource state is refreshed, respectively. // resource state is refreshed, respectively.
PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error) PreRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error) PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error)
// PostStateUpdate is called after the state is updated.
PostStateUpdate(*State) (HookAction, error)
} }
// NilHook is a Hook implementation that does nothing. It exists only to // NilHook is a Hook implementation that does nothing. It exists only to
@ -100,6 +103,10 @@ func (*NilHook) PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error) {
return HookActionContinue, nil return HookActionContinue, nil
} }
func (*NilHook) PostStateUpdate(*State) (HookAction, error) {
return HookActionContinue, nil
}
// handleHook turns hook actions into panics. This lets you use the // handleHook turns hook actions into panics. This lets you use the
// panic/recover mechanism in Go as a flow control mechanism for hook // panic/recover mechanism in Go as a flow control mechanism for hook
// actions. // actions.

View File

@ -69,6 +69,11 @@ type MockHook struct {
PreRefreshState *InstanceState PreRefreshState *InstanceState
PreRefreshReturn HookAction PreRefreshReturn HookAction
PreRefreshError error PreRefreshError error
PostStateUpdateCalled bool
PostStateUpdateState *State
PostStateUpdateReturn HookAction
PostStateUpdateError error
} }
func (h *MockHook) PreApply(n *InstanceInfo, s *InstanceState, d *InstanceDiff) (HookAction, error) { func (h *MockHook) PreApply(n *InstanceInfo, s *InstanceState, d *InstanceDiff) (HookAction, error) {
@ -152,3 +157,9 @@ func (h *MockHook) PostRefresh(n *InstanceInfo, s *InstanceState) (HookAction, e
h.PostRefreshState = s h.PostRefreshState = s
return h.PostRefreshReturn, h.PostRefreshError return h.PostRefreshReturn, h.PostRefreshError
} }
func (h *MockHook) PostStateUpdate(s *State) (HookAction, error) {
h.PostStateUpdateCalled = true
h.PostStateUpdateState = s
return h.PostStateUpdateReturn, h.PostStateUpdateError
}

View File

@ -53,6 +53,10 @@ func (h *stopHook) PostRefresh(*InstanceInfo, *InstanceState) (HookAction, error
return h.hook() return h.hook()
} }
func (h *stopHook) PostStateUpdate(*State) (HookAction, error) {
return h.hook()
}
func (h *stopHook) hook() (HookAction, error) { func (h *stopHook) hook() (HookAction, error) {
if h.Stopped() { if h.Stopped() {
return HookActionHalt, nil return HookActionHalt, nil