add EvalContext.WithPath

As the Graph is walked, the current way to set the context path was to
have the walker return a context from EnterPath. This required that
every node know it's absolute path, which can no longer be the case
during plan when modules have not been expanded.

This introduces a new method called WithPath, which returns a copy of
the context with the internal path updated to reflect the method
argument. Any use of the EvalContext that requires knowing the path will
now panic if it wasn't explicitly set to ensure that evaluations always
occur in the correct path.

Add EvalContext to the GraphWalker interface.
EvalContext returns an EvalContext that has not yet set a path. This
will allow us to enforce that all context operations requiring a module
instance path will require that a path be explicitly set rather than
evaluating within the wrong path.
This commit is contained in:
James Bardin 2020-03-17 14:02:46 -04:00
parent 537c1bedcf
commit 0b025d74e5
7 changed files with 56 additions and 13 deletions

View File

@ -46,12 +46,16 @@ func Eval(n EvalNode, ctx EvalContext) (interface{}, error) {
// signal something normal such as EvalEarlyExitError. // signal something normal such as EvalEarlyExitError.
func EvalRaw(n EvalNode, ctx EvalContext) (interface{}, error) { func EvalRaw(n EvalNode, ctx EvalContext) (interface{}, error) {
path := "unknown" path := "unknown"
if ctx != nil {
path = ctx.Path().String() // FIXME: restore the path here somehow or log this in another manner
} // We cannot call Path here, since the context may not yet have the path
if path == "" { // set.
path = "<root>" //if ctx != nil {
} // path = ctx.Path().String()
//}
//if path == "" {
// path = "<root>"
//}
log.Printf("[TRACE] %s: eval: %T", path, n) log.Printf("[TRACE] %s: eval: %T", path, n)
output, err := n.Eval(ctx) output, err := n.Eval(ctx)

View File

@ -161,4 +161,8 @@ type EvalContext interface {
// The InstanceExpander is a global object that is shared across all of the // The InstanceExpander is a global object that is shared across all of the
// EvalContext objects for a given configuration. // EvalContext objects for a given configuration.
InstanceExpander() *instances.Expander InstanceExpander() *instances.Expander
// WithPath returns a copy of the context with the internal path set to the
// path argument.
WithPath(path addrs.ModuleInstance) EvalContext
} }

View File

@ -32,6 +32,13 @@ type BuiltinEvalContext struct {
// PathValue is the Path that this context is operating within. // PathValue is the Path that this context is operating within.
PathValue addrs.ModuleInstance PathValue addrs.ModuleInstance
// pathSet indicates that this context was explicitly created for a
// specific path, and can be safely used for evaluation. This lets us
// differentiate between Pathvalue being unset, and the zero value which is
// equivalent to RootModuleInstance. Path and Evaluation methods will
// panic if this is not set.
pathSet bool
// Evaluator is used for evaluating expressions within the scope of this // Evaluator is used for evaluating expressions within the scope of this
// eval context. // eval context.
Evaluator *Evaluator Evaluator *Evaluator
@ -70,6 +77,13 @@ type BuiltinEvalContext struct {
// BuiltinEvalContext implements EvalContext // BuiltinEvalContext implements EvalContext
var _ EvalContext = (*BuiltinEvalContext)(nil) var _ EvalContext = (*BuiltinEvalContext)(nil)
func (ctx *BuiltinEvalContext) WithPath(path addrs.ModuleInstance) EvalContext {
ctx.pathSet = true
newCtx := *ctx
newCtx.PathValue = path
return &newCtx
}
func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} { func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} {
// This can happen during tests. During tests, we just block forever. // This can happen during tests. During tests, we just block forever.
if ctx.StopContext == nil { if ctx.StopContext == nil {
@ -291,6 +305,9 @@ func (ctx *BuiltinEvalContext) EvaluateExpr(expr hcl.Expression, wantType cty.Ty
} }
func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope { func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData InstanceKeyEvalData) *lang.Scope {
if !ctx.pathSet {
panic("context path not set")
}
data := &evaluationStateData{ data := &evaluationStateData{
Evaluator: ctx.Evaluator, Evaluator: ctx.Evaluator,
ModulePath: ctx.PathValue, ModulePath: ctx.PathValue,
@ -301,6 +318,9 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData
} }
func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance { func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
if !ctx.pathSet {
panic("context path not set")
}
return ctx.PathValue return ctx.PathValue
} }
@ -308,6 +328,10 @@ func (ctx *BuiltinEvalContext) SetModuleCallArguments(n addrs.ModuleCallInstance
ctx.VariableValuesLock.Lock() ctx.VariableValuesLock.Lock()
defer ctx.VariableValuesLock.Unlock() defer ctx.VariableValuesLock.Unlock()
if !ctx.pathSet {
panic("context path not set")
}
childPath := n.ModuleInstance(ctx.PathValue) childPath := n.ModuleInstance(ctx.PathValue)
key := childPath.String() key := childPath.String()

View File

@ -305,6 +305,12 @@ func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, keyData Inst
return c.EvaluationScopeScope return c.EvaluationScopeScope
} }
func (c *MockEvalContext) WithPath(path addrs.ModuleInstance) EvalContext {
newC := *c
newC.PathPath = path
return &newC
}
func (c *MockEvalContext) Path() addrs.ModuleInstance { func (c *MockEvalContext) Path() addrs.ModuleInstance {
c.PathCalled = true c.PathCalled = true
return c.PathPath return c.PathPath

View File

@ -35,8 +35,7 @@ func (g *Graph) Walk(walker GraphWalker) tfdiags.Diagnostics {
func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics { func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
// The callbacks for enter/exiting a graph // The callbacks for enter/exiting a graph
ctx := walker.EnterPath(g.Path) ctx := walker.EvalContext()
defer walker.ExitPath(g.Path)
// Walk the graph. // Walk the graph.
var walkFn dag.WalkFunc var walkFn dag.WalkFunc
@ -54,7 +53,7 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
// is normally the context of our graph but can be overridden // is normally the context of our graph but can be overridden
// with a GraphNodeModuleInstance impl. // with a GraphNodeModuleInstance impl.
vertexCtx := ctx vertexCtx := ctx
if pn, ok := v.(GraphNodeModuleInstance); ok && len(pn.Path()) > 0 { if pn, ok := v.(GraphNodeModuleInstance); ok {
vertexCtx = walker.EnterPath(pn.Path()) vertexCtx = walker.EnterPath(pn.Path())
defer walker.ExitPath(pn.Path()) defer walker.ExitPath(pn.Path())
} }

View File

@ -9,6 +9,7 @@ import (
// GraphWalker is an interface that can be implemented that when used // GraphWalker is an interface that can be implemented that when used
// with Graph.Walk will invoke the given callbacks under certain events. // with Graph.Walk will invoke the given callbacks under certain events.
type GraphWalker interface { type GraphWalker interface {
EvalContext() EvalContext
EnterPath(addrs.ModuleInstance) EvalContext EnterPath(addrs.ModuleInstance) EvalContext
ExitPath(addrs.ModuleInstance) ExitPath(addrs.ModuleInstance)
EnterVertex(dag.Vertex) EnterVertex(dag.Vertex)
@ -22,6 +23,7 @@ type GraphWalker interface {
// implementing all the required functions. // implementing all the required functions.
type NullGraphWalker struct{} type NullGraphWalker struct{}
func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) }
func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) } func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) }
func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {} func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {}
func (NullGraphWalker) EnterVertex(dag.Vertex) {} func (NullGraphWalker) EnterVertex(dag.Vertex) {}

View File

@ -51,8 +51,6 @@ type ContextGraphWalker struct {
} }
func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext { func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
w.once.Do(w.init)
w.contextLock.Lock() w.contextLock.Lock()
defer w.contextLock.Unlock() defer w.contextLock.Unlock()
@ -62,6 +60,14 @@ func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
return ctx return ctx
} }
ctx := w.EvalContext().WithPath(path)
w.contexts[key] = ctx.(*BuiltinEvalContext)
return ctx
}
func (w *ContextGraphWalker) EvalContext() EvalContext {
w.once.Do(w.init)
// Our evaluator shares some locks with the main context and the walker // Our evaluator shares some locks with the main context and the walker
// so that we can safely run multiple evaluations at once across // so that we can safely run multiple evaluations at once across
// different modules. // different modules.
@ -78,7 +84,6 @@ func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
ctx := &BuiltinEvalContext{ ctx := &BuiltinEvalContext{
StopContext: w.StopContext, StopContext: w.StopContext,
PathValue: path,
Hooks: w.Context.hooks, Hooks: w.Context.hooks,
InputValue: w.Context.uiInput, InputValue: w.Context.uiInput,
InstanceExpanderValue: w.InstanceExpander, InstanceExpanderValue: w.InstanceExpander,
@ -96,7 +101,6 @@ func (w *ContextGraphWalker) EnterPath(path addrs.ModuleInstance) EvalContext {
VariableValuesLock: &w.variableValuesLock, VariableValuesLock: &w.variableValuesLock,
} }
w.contexts[key] = ctx
return ctx return ctx
} }