terraform: trying this graphwalker thing
This commit is contained in:
parent
10264a7def
commit
58347617e8
|
@ -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
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
walker := &ContextGraphWalker{Context: c, Operation: walkValidate}
|
||||
graph.Walk(walker)
|
||||
|
||||
// Return the result
|
||||
rerrs := multierror.Append(errs, walker.ValidationErrors...)
|
||||
return walker.ValidationWarnings, rerrs.Errors
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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) {}
|
|
@ -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...)
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNullGraphWalker_impl(t *testing.T) {
|
||||
var _ GraphWalker = NullGraphWalker{}
|
||||
}
|
Loading…
Reference in New Issue