Files
opentf/internal/tofu/context_eval.go
Martin Atkins 75c7834ca6 tofu: GraphTransformer.Transform takes context.Context
Most of our transformers are pure compute and so don't really have a strong
need to generate trace spans under our current focus of only exposing
user-facing concepts and external requests in our traces, but unfortunately
some of them indirectly depend on provider schema, which in turn means that
they can potentially be unlucky enough to be the trigger for making all
of the provider requests needed to fill the schema cache and therefore
would end up with provider request spans being reported beneath them.

As usual with these interface updates, this initial change focuses only
on changing the interface and updating its direct callers and implementers
to match, without any further refactoring or attempts to plumb contexts
to or from other functions that don't have them yet. That means there are
a few new context.TODO() calls here that we'll tidy up in a later commit
that hopefully won't involve all of the noise that is caused by changing
an interface API.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-05-08 07:16:09 -07:00

113 lines
4.2 KiB
Go

// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package tofu
import (
"context"
"log"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/lang"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tracing"
)
type EvalOpts struct {
SetVariables InputValues
}
// Eval produces a scope in which expressions can be evaluated for
// the given module path.
//
// This method must first evaluate any ephemeral values (input variables, local
// values, and output values) in the configuration. These ephemeral values are
// not included in the persisted state, so they must be re-computed using other
// values in the state before they can be properly evaluated. The updated
// values are retained in the main state associated with the receiving context.
//
// This function takes no action against remote APIs but it does need access
// to all provider and provisioner instances in order to obtain their schemas
// for type checking.
//
// The result is an evaluation scope that can be used to resolve references
// against the root module. If the returned diagnostics contains errors then
// the returned scope may be nil. If it is not nil then it may still be used
// to attempt expression evaluation or other analysis, but some expressions
// may not behave as expected.
func (c *Context) Eval(ctx context.Context, config *configs.Config, state *states.State, moduleAddr addrs.ModuleInstance, opts *EvalOpts) (*lang.Scope, tfdiags.Diagnostics) {
// This is intended for external callers such as the "tofu console"
// command. Internally, we create an evaluator in c.walk before walking
// the graph, and create scopes in ContextGraphWalker.
var diags tfdiags.Diagnostics
defer c.acquireRun("eval")()
ctx, span := tracing.Tracer().Start(
ctx, "Evaluation phase",
)
defer span.End()
// Start with a copy of state so that we don't affect the instance that
// the caller is holding.
state = state.DeepCopy()
var walker *ContextGraphWalker
variables := opts.SetVariables
// By the time we get here, we should have values defined for all of
// the root module variables, even if some of them are "unknown". It's the
// caller's responsibility to have already handled the decoding of these
// from the various ways the CLI allows them to be set and to produce
// user-friendly error messages if they are not all present, and so
// the error message from checkInputVariables should never be seen and
// includes language asking the user to report a bug.
varDiags := checkInputVariables(config.Module.Variables, variables)
diags = diags.Append(varDiags)
log.Printf("[DEBUG] Building and walking 'eval' graph")
providerFunctionTracker := make(ProviderFunctionMapping)
graph, moreDiags := (&EvalGraphBuilder{
Config: config,
State: state,
RootVariableValues: variables,
Plugins: c.plugins,
ProviderFunctionTracker: providerFunctionTracker,
}).Build(ctx, addrs.RootModuleInstance)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
walkOpts := &graphWalkOpts{
InputState: state,
Config: config,
ProviderFunctionTracker: providerFunctionTracker,
}
walker, moreDiags = c.walk(ctx, graph, walkEval, walkOpts)
diags = diags.Append(moreDiags)
if walker != nil {
diags = diags.Append(walker.NonFatalDiagnostics)
} else {
// If we skipped walking the graph (due to errors) then we'll just
// use a placeholder graph walker here, which'll refer to the
// unmodified state.
walker = c.graphWalker(walkEval, walkOpts)
}
// This is a bit weird since we don't normally evaluate outside of
// the context of a walk, but we'll "re-enter" our desired path here
// just to get hold of an EvalContext for it. ContextGraphWalker
// caches its contexts, so we should get hold of the context that was
// previously used for evaluation here, unless we skipped walking.
evalCtx := walker.EnterPath(moduleAddr)
return evalCtx.EvaluationScope(nil, nil, EvalDataForNoInstanceKey), diags
}