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.
func EvalRaw(n EvalNode, ctx EvalContext) (interface{}, error) {
path := "unknown"
if ctx != nil {
path = ctx.Path().String()
}
if path == "" {
path = "<root>"
}
// 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
// set.
//if ctx != nil {
// path = ctx.Path().String()
//}
//if path == "" {
// path = "<root>"
//}
log.Printf("[TRACE] %s: eval: %T", path, n)
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
// EvalContext objects for a given configuration.
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 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
// eval context.
Evaluator *Evaluator
@ -70,6 +77,13 @@ type BuiltinEvalContext struct {
// BuiltinEvalContext implements EvalContext
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{} {
// This can happen during tests. During tests, we just block forever.
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 {
if !ctx.pathSet {
panic("context path not set")
}
data := &evaluationStateData{
Evaluator: ctx.Evaluator,
ModulePath: ctx.PathValue,
@ -301,6 +318,9 @@ func (ctx *BuiltinEvalContext) EvaluationScope(self addrs.Referenceable, keyData
}
func (ctx *BuiltinEvalContext) Path() addrs.ModuleInstance {
if !ctx.pathSet {
panic("context path not set")
}
return ctx.PathValue
}
@ -308,6 +328,10 @@ func (ctx *BuiltinEvalContext) SetModuleCallArguments(n addrs.ModuleCallInstance
ctx.VariableValuesLock.Lock()
defer ctx.VariableValuesLock.Unlock()
if !ctx.pathSet {
panic("context path not set")
}
childPath := n.ModuleInstance(ctx.PathValue)
key := childPath.String()

View File

@ -305,6 +305,12 @@ func (c *MockEvalContext) EvaluationScope(self addrs.Referenceable, keyData Inst
return c.EvaluationScopeScope
}
func (c *MockEvalContext) WithPath(path addrs.ModuleInstance) EvalContext {
newC := *c
newC.PathPath = path
return &newC
}
func (c *MockEvalContext) Path() addrs.ModuleInstance {
c.PathCalled = true
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 {
// The callbacks for enter/exiting a graph
ctx := walker.EnterPath(g.Path)
defer walker.ExitPath(g.Path)
ctx := walker.EvalContext()
// Walk the graph.
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
// with a GraphNodeModuleInstance impl.
vertexCtx := ctx
if pn, ok := v.(GraphNodeModuleInstance); ok && len(pn.Path()) > 0 {
if pn, ok := v.(GraphNodeModuleInstance); ok {
vertexCtx = walker.EnterPath(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
// with Graph.Walk will invoke the given callbacks under certain events.
type GraphWalker interface {
EvalContext() EvalContext
EnterPath(addrs.ModuleInstance) EvalContext
ExitPath(addrs.ModuleInstance)
EnterVertex(dag.Vertex)
@ -22,6 +23,7 @@ type GraphWalker interface {
// implementing all the required functions.
type NullGraphWalker struct{}
func (NullGraphWalker) EvalContext() EvalContext { return new(MockEvalContext) }
func (NullGraphWalker) EnterPath(addrs.ModuleInstance) EvalContext { return new(MockEvalContext) }
func (NullGraphWalker) ExitPath(addrs.ModuleInstance) {}
func (NullGraphWalker) EnterVertex(dag.Vertex) {}

View File

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