terraform: refresh hooks

This commit is contained in:
Mitchell Hashimoto 2015-02-11 13:43:07 -08:00
parent 5ba52ceac4
commit 379c37dd06
9 changed files with 94 additions and 6 deletions

View File

@ -26,6 +26,7 @@ type ContextOpts struct {
// perform operations on infrastructure. This structure is built using
// NewContext. See the documentation for that.
type Context2 struct {
hooks []Hook
module *module.Tree
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
@ -41,6 +42,7 @@ type Context2 struct {
// the values themselves.
func NewContext2(opts *ContextOpts) *Context2 {
return &Context2{
hooks: opts.Hooks,
module: opts.Module,
providers: opts.Providers,
provisioners: opts.Provisioners,

View File

@ -123,12 +123,11 @@ func TestContext2Refresh_ignoreUncreated(t *testing.T) {
}
}
/*
func TestContextRefresh_hook(t *testing.T) {
func TestContext2Refresh_hook(t *testing.T) {
h := new(MockHook)
p := testProvider("aws")
m := testModule(t, "refresh-basic")
ctx := testContext(t, &ContextOpts{
ctx := testContext2(t, &ContextOpts{
Module: m,
Hooks: []Hook{h},
Providers: map[string]ResourceProviderFactory{
@ -162,6 +161,7 @@ func TestContextRefresh_hook(t *testing.T) {
}
}
/*
func TestContextRefresh_modules(t *testing.T) {
p := testProvider("aws")
m := testModule(t, "refresh-modules")

View File

@ -27,6 +27,12 @@ type GraphNodeEvalable interface {
EvalTree() EvalNode
}
// EvalEarlyExitError is a special error return value that can be returned
// by eval nodes that does an early exit.
type EvalEarlyExitError struct{}
func (EvalEarlyExitError) Error() string { return "early exit" }
// Eval evaluates the given EvalNode with the given context, properly
// evaluating all args in the correct order.
func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {

View File

@ -11,6 +11,10 @@ type EvalContext interface {
// Path is the current module path.
Path() []string
// Hook is used to call hook methods. The callback is called for each
// hook and should return the hook action to take and the error.
Hook(func(Hook) (HookAction, error)) error
// InitProvider initializes the provider with the given name and
// returns the implementation of the resource provider or an error.
//
@ -53,6 +57,9 @@ type EvalContext interface {
// MockEvalContext is a mock version of EvalContext that can be used
// for tests.
type MockEvalContext struct {
HookCalled bool
HookError error
InitProviderCalled bool
InitProviderName string
InitProviderProvider ResourceProvider
@ -94,6 +101,11 @@ type MockEvalContext struct {
StateLock *sync.RWMutex
}
func (c *MockEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
c.HookCalled = true
return c.HookError
}
func (c *MockEvalContext) InitProvider(n string) (ResourceProvider, error) {
c.InitProviderCalled = true
c.InitProviderName = n

View File

@ -14,6 +14,7 @@ import (
type BuiltinEvalContext struct {
PathValue []string
Interpolater *Interpolater
Hooks []Hook
Providers map[string]ResourceProviderFactory
ProviderCache map[string]ResourceProvider
ProviderConfigCache map[string]*ResourceConfig
@ -27,6 +28,25 @@ type BuiltinEvalContext struct {
once sync.Once
}
func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error {
for _, h := range ctx.Hooks {
action, err := fn(h)
if err != nil {
return err
}
switch action {
case HookActionContinue:
continue
case HookActionHalt:
// Return an early exit error to trigger an early exit
return EvalEarlyExitError{}
}
}
return nil
}
func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error) {
ctx.once.Do(ctx.init)

View File

@ -27,8 +27,29 @@ func (n *EvalRefresh) Eval(
return nil, nil
}
n.Info.ModulePath = ctx.Path()
return provider.Refresh(n.Info, state)
// Call pre-refresh hook
err := ctx.Hook(func(h Hook) (HookAction, error) {
return h.PreRefresh(n.Info, state)
})
if err != nil {
return nil, err
}
// Refresh!
state, err = provider.Refresh(n.Info, state)
if err != nil {
return nil, err
}
// Call post-refresh hook
err = ctx.Hook(func(h Hook) (HookAction, error) {
return h.PostRefresh(n.Info, state)
})
if err != nil {
return nil, err
}
return state, nil
}
func (n *EvalRefresh) Type() EvalType {

View File

@ -0,0 +1,22 @@
package terraform
// EvalInstanceInfo is an EvalNode implementation that fills in the
// InstanceInfo as much as it can.
type EvalInstanceInfo struct {
Info *InstanceInfo
}
func (n *EvalInstanceInfo) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalInstanceInfo) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
n.Info.ModulePath = ctx.Path()
return nil, nil
}
func (n *EvalInstanceInfo) Type() EvalType {
return EvalTypeNull
}

View File

@ -36,6 +36,7 @@ func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
return &BuiltinEvalContext{
PathValue: g.Path,
Hooks: w.Context.hooks,
Providers: w.Context.providers,
ProviderCache: w.providerCache,
ProviderConfigCache: w.providerConfigCache,

View File

@ -96,6 +96,10 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
})
}
// Build instance info
info := &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type}
seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})
// Refresh the resource
seq.Nodes = append(seq.Nodes, &EvalOpFilter{
Ops: []walkOperation{walkRefresh},
@ -104,9 +108,9 @@ func (n *graphNodeExpandedResource) EvalTree() EvalNode {
ResourceType: n.Resource.Type,
Dependencies: n.DependentOn(),
State: &EvalRefresh{
Info: info,
Provider: &EvalGetProvider{Name: n.ProvidedBy()},
State: &EvalReadState{Name: n.stateId()},
Info: &InstanceInfo{Id: n.stateId(), Type: n.Resource.Type},
},
},
})