From 379c37dd06a250e08ce12c1863cf03694672c973 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 11 Feb 2015 13:43:07 -0800 Subject: [PATCH] terraform: refresh hooks --- terraform/context.go | 2 ++ terraform/context_test.go | 6 +++--- terraform/eval.go | 6 ++++++ terraform/eval_context.go | 12 ++++++++++++ terraform/eval_context_builtin.go | 20 ++++++++++++++++++++ terraform/eval_refresh.go | 25 +++++++++++++++++++++++-- terraform/eval_resource.go | 22 ++++++++++++++++++++++ terraform/graph_walk_context.go | 1 + terraform/transform_resource.go | 6 +++++- 9 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 terraform/eval_resource.go diff --git a/terraform/context.go b/terraform/context.go index e640454c1..7d1f43648 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -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, diff --git a/terraform/context_test.go b/terraform/context_test.go index 08fc93df1..659b30554 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -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") diff --git a/terraform/eval.go b/terraform/eval.go index 5691303f9..f60e48284 100644 --- a/terraform/eval.go +++ b/terraform/eval.go @@ -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) { diff --git a/terraform/eval_context.go b/terraform/eval_context.go index e4d4b5cee..558993e7f 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -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 diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index a3d727c80..d0a343d09 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -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) diff --git a/terraform/eval_refresh.go b/terraform/eval_refresh.go index a0d9d4b34..34bea90f8 100644 --- a/terraform/eval_refresh.go +++ b/terraform/eval_refresh.go @@ -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 { diff --git a/terraform/eval_resource.go b/terraform/eval_resource.go new file mode 100644 index 000000000..4b87e6794 --- /dev/null +++ b/terraform/eval_resource.go @@ -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 +} diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index 7f8316424..0bd23aa81 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -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, diff --git a/terraform/transform_resource.go b/terraform/transform_resource.go index 5b5af1180..87b7bce87 100644 --- a/terraform/transform_resource.go +++ b/terraform/transform_resource.go @@ -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}, }, }, })