terraform: trying this graphwalker thing
This commit is contained in:
parent
10264a7def
commit
58347617e8
|
@ -1,13 +1,10 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/errwrap"
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/terraform/config/module"
|
"github.com/hashicorp/terraform/config/module"
|
||||||
"github.com/hashicorp/terraform/dag"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContextOpts are the user-configurable options to create a context with
|
// 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.
|
// Validate validates the configuration and returns any warnings or errors.
|
||||||
func (c *Context2) Validate() ([]string, []error) {
|
func (c *Context2) Validate() ([]string, []error) {
|
||||||
var warns []string
|
|
||||||
var errs error
|
var errs error
|
||||||
|
|
||||||
// Validate the configuration itself
|
// Validate the configuration itself
|
||||||
|
@ -85,8 +81,6 @@ func (c *Context2) Validate() ([]string, []error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
evalCtx := c.evalContext(walkValidate)
|
|
||||||
|
|
||||||
// Build the graph
|
// Build the graph
|
||||||
graph, err := c.GraphBuilder().Build(RootModulePath)
|
graph, err := c.GraphBuilder().Build(RootModulePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -94,58 +88,10 @@ func (c *Context2) Validate() ([]string, []error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk the graph
|
// Walk the graph
|
||||||
var lock sync.Mutex
|
walker := &ContextGraphWalker{Context: c, Operation: walkValidate}
|
||||||
graph.Walk(func(v dag.Vertex) {
|
graph.Walk(walker)
|
||||||
ev, ok := v.(GraphNodeEvalable)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tree := ev.EvalTree()
|
// Return the result
|
||||||
if tree == nil {
|
rerrs := multierror.Append(errs, walker.ValidationErrors...)
|
||||||
panic(fmt.Sprintf("%s (%T): nil eval tree", dag.VertexName(v), v))
|
return walker.ValidationWarnings, rerrs.Errors
|
||||||
}
|
|
||||||
|
|
||||||
_, 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,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package terraform
|
package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/terraform/dag"
|
"github.com/hashicorp/terraform/dag"
|
||||||
|
@ -111,12 +112,51 @@ func (g *Graph) Dependable(n string) dag.Vertex {
|
||||||
return nil
|
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() {
|
func (g *Graph) init() {
|
||||||
if g.dependableMap == nil {
|
if g.dependableMap == nil {
|
||||||
g.dependableMap = make(map[string]dag.Vertex)
|
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
|
// GraphNodeDependable is an interface which says that a node can be
|
||||||
// depended on (an edge can be placed between this node and another) according
|
// depended on (an edge can be placed between this node and another) according
|
||||||
// to the well-known name returned by DependableName.
|
// 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