terraform: trying this graphwalker thing

This commit is contained in:
Mitchell Hashimoto 2015-02-07 13:29:55 -08:00
parent 10264a7def
commit 58347617e8
5 changed files with 142 additions and 59 deletions

View File

@ -1,13 +1,10 @@
package terraform
import (
"fmt"
"sync"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
// ContextOpts are the user-configurable options to create a context with
@ -68,7 +65,6 @@ func (c *Context2) GraphBuilder() GraphBuilder {
// Validate validates the configuration and returns any warnings or errors.
func (c *Context2) Validate() ([]string, []error) {
var warns []string
var errs error
// Validate the configuration itself
@ -85,8 +81,6 @@ func (c *Context2) Validate() ([]string, []error) {
}
}
evalCtx := c.evalContext(walkValidate)
// Build the graph
graph, err := c.GraphBuilder().Build(RootModulePath)
if err != nil {
@ -94,58 +88,10 @@ func (c *Context2) Validate() ([]string, []error) {
}
// Walk the graph
var lock sync.Mutex
graph.Walk(func(v dag.Vertex) {
ev, ok := v.(GraphNodeEvalable)
if !ok {
return
}
walker := &ContextGraphWalker{Context: c, Operation: walkValidate}
graph.Walk(walker)
tree := ev.EvalTree()
if tree == nil {
panic(fmt.Sprintf("%s (%T): nil eval tree", dag.VertexName(v), v))
}
_, err := Eval(tree, evalCtx)
if err == nil {
return
}
lock.Lock()
defer lock.Unlock()
verr, ok := err.(*EvalValidateError)
if !ok {
errs = multierror.Append(errs, err)
return
}
warns = append(warns, verr.Warnings...)
errs = multierror.Append(errs, verr.Errors...)
})
// Get the actual list of errors out. We do this by checking if the
// error is a error wrapper (multierror is one) and grabbing all the
// wrapped errors.
var rerrs []error
if w, ok := errs.(errwrap.Wrapper); ok {
rerrs = w.WrappedErrors()
}
return warns, rerrs
}
func (c *Context2) evalContext(op walkOperation) *BuiltinEvalContext {
return &BuiltinEvalContext{
Path: RootModulePath,
Providers: c.providers,
Interpolater: &Interpolater{
Operation: op,
Module: c.module,
State: c.state,
StateLock: &c.stateLock,
Variables: nil,
},
}
// Return the result
rerrs := multierror.Append(errs, walker.ValidationErrors...)
return walker.ValidationWarnings, rerrs.Errors
}

View File

@ -1,6 +1,7 @@
package terraform
import (
"fmt"
"sync"
"github.com/hashicorp/terraform/dag"
@ -111,12 +112,51 @@ func (g *Graph) Dependable(n string) dag.Vertex {
return nil
}
// Walk walks the graph with the given walker for callbacks. The graph
// will be walked with full parallelism, so the walker should expect
// to be called in concurrently.
//
// There is no way to tell the walker to halt the walk. If you want to
// halt the walk, you should set a flag in your GraphWalker to ignore
// future callbacks.
func (g *Graph) Walk(walker GraphWalker) {
// TODO: test
g.walk(walker)
}
func (g *Graph) init() {
if g.dependableMap == nil {
g.dependableMap = make(map[string]dag.Vertex)
}
}
func (g *Graph) walk(walker GraphWalker) {
// The callbacks for enter/exiting a graph
ctx := walker.EnterGraph(g)
defer walker.ExitGraph(g)
// Walk the graph
g.AcyclicGraph.Walk(func(v dag.Vertex) {
walker.EnterVertex(v)
defer walker.ExitVertex(v)
// If the node is eval-able, then evaluate it.
if ev, ok := v.(GraphNodeEvalable); ok {
tree := ev.EvalTree()
if tree == nil {
panic(fmt.Sprintf(
"%s (%T): nil eval tree", dag.VertexName(v), v))
}
// Allow the walker to change our tree if needed. Eval,
// then callback with the output.
tree = walker.EnterEvalTree(v, tree)
output, err := Eval(tree, ctx)
walker.ExitEvalTree(v, output, err)
}
})
}
// GraphNodeDependable is an interface which says that a node can be
// depended on (an edge can be placed between this node and another) according
// to the well-known name returned by DependableName.

28
terraform/graph_walk.go Normal file
View File

@ -0,0 +1,28 @@
package terraform
import (
"github.com/hashicorp/terraform/dag"
)
// GraphWalker is an interface that can be implemented that when used
// with Graph.Walk will invoke the given callbacks under certain events.
type GraphWalker interface {
EnterGraph(*Graph) EvalContext
ExitGraph(*Graph)
EnterVertex(dag.Vertex)
ExitVertex(dag.Vertex)
EnterEvalTree(dag.Vertex, EvalNode) EvalNode
ExitEvalTree(dag.Vertex, interface{}, error)
}
// NullGraphWalker is a GraphWalker implementation that does nothing.
// This can be embedded within other GraphWalker implementations for easily
// implementing all the required functions.
type NullGraphWalker struct{}
func (NullGraphWalker) EnterGraph(*Graph) EvalContext { return nil }
func (NullGraphWalker) ExitGraph(*Graph) {}
func (NullGraphWalker) EnterVertex(dag.Vertex) {}
func (NullGraphWalker) ExitVertex(dag.Vertex) {}
func (NullGraphWalker) EnterEvalTree(v dag.Vertex, n EvalNode) EvalNode { return n }
func (NullGraphWalker) ExitEvalTree(dag.Vertex, interface{}, error) {}

View File

@ -0,0 +1,60 @@
package terraform
import (
"sync"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/dag"
)
// ContextGraphWalker is the GraphWalker implementation used with the
// Context struct to walk and evaluate the graph.
type ContextGraphWalker struct {
NullGraphWalker
Context *Context2
Operation walkOperation
ErrorLock sync.Mutex
EvalError error
ValidationWarnings []string
ValidationErrors []error
}
func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
return &BuiltinEvalContext{
Path: g.Path,
Providers: w.Context.providers,
Interpolater: &Interpolater{
Operation: w.Operation,
Module: w.Context.module,
State: w.Context.state,
StateLock: &w.Context.stateLock,
Variables: nil,
},
}
}
func (w *ContextGraphWalker) ExitEvalTree(
v dag.Vertex, output interface{}, err error) {
if err == nil {
return
}
// Acquire the lock because anything is going to require a lock.
w.ErrorLock.Lock()
defer w.ErrorLock.Unlock()
// Try to get a validation error out of it. If its not a validation
// error, then just record the normal error.
verr, ok := err.(*EvalValidateError)
if !ok {
// Some other error, record it
w.EvalError = multierror.Append(w.EvalError, err)
return
}
// Record the validation error
w.ValidationWarnings = append(w.ValidationWarnings, verr.Warnings...)
w.ValidationErrors = append(w.ValidationErrors, verr.Errors...)
}

View File

@ -0,0 +1,9 @@
package terraform
import (
"testing"
)
func TestNullGraphWalker_impl(t *testing.T) {
var _ GraphWalker = NullGraphWalker{}
}