Files
opentf/internal/lang/eval/config.go
Martin Atkins 102449c9ec backend/local: Use experimental runtime for planning, when enabled
This is a relatively uninteresting milestone where it's possible to load
and plan a root module that contains nothing except local values and
output values.

The module loader currently supports only local sources and the plugin
APIs just immediately return errors, so configurations more complicated
than that are likely to just fail immediately with one or more errors.
We'll gradually improve on this in later commits.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2025-11-21 09:20:59 -08:00

139 lines
5.9 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 eval
import (
"context"
"github.com/apparentlymart/go-versions/versions"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/lang/eval/internal/evalglue"
"github.com/opentofu/opentofu/internal/lang/eval/internal/tofu2024"
"github.com/opentofu/opentofu/internal/lang/exprs"
"github.com/opentofu/opentofu/internal/tfdiags"
)
// ConfigInstance represents the combination of a configuration and some
// input variables used to call into its root module, along with related
// context such as available providers and module packages.
type ConfigInstance struct {
rootModuleSource addrs.ModuleSource
inputValues exprs.Valuer
evalContext *evalglue.EvalContext
allowImpureFunctions bool
}
// ConfigCall describes a call to a root module that acts conceptually like
// a "module" block but is instead implied by something outside of the
// module language itself, such as running an OpenTofu CLI command.
type ConfigCall struct {
// RootModuleSource is the source address of the root module.
//
// This must be a source address that can be resolved by the
// [ExternalModules] implementation provided in EvalContext.
RootModuleSource addrs.ModuleSource
// InputValues describes how to obtain values for the input variables
// declared in the root module.
//
// In typical use the InputValues object is assembled based on a combination
// of ".tfvars" files, CLI arguments, and environment variables, but that's
// the responsibility of the Tofu CLI layer and so this package is totally
// unopinionated about how those are provided, so e.g. for .tftest.hcl "run"
// blocks the input values could come from the test scenario configuration
// instead.
//
// In unit tests where the source of input variables is immaterial,
// [InputValuesForTesting] might be useful to build values for this
// field inline in the test code.
InputValues exprs.Valuer
// AllowImpureFunctions controls whether to allow full use of a small
// number of functions that produce different results each time they are
// called, such as "timestamp". This should be set to true only during
// the apply phase and in some more contrived situations such as in the
// "tofu console" command's REPL.
AllowImpureFunctions bool
// EvalContext describes the context where the call is being made, dealing
// with cross-cutting concerns like which providers are available and how
// to load them.
EvalContext *evalglue.EvalContext
}
// NewConfigInstance builds a new [ConfigInstance] based on the information
// in the given [ConfigCall] object.
//
// If the returned diagnostics has errors then the first result is invalid
// and must not be used.
//
// Note that this function focuses only on checking that the call itself seems
// sensible, and does not perform any immediate evaluation of the configuration,
// so success of this function DOES NOT imply that the configuration is valid.
// Use methods of a valid [`ConfigInstance`] produced by this function to
// gather more information about the configuration.
func NewConfigInstance(ctx context.Context, call *ConfigCall) (*ConfigInstance, tfdiags.Diagnostics) {
call.EvalContext.AssertValid()
inst := &ConfigInstance{
rootModuleSource: call.RootModuleSource,
inputValues: call.InputValues,
allowImpureFunctions: call.AllowImpureFunctions,
evalContext: call.EvalContext,
}
// We currently don't do any other early work here and instead just wait
// until we're asked a more specific question using one of the methods
// of the result. If that continues to be true then perhaps we'll drop
// the tfdiags.Diagnostics result from this function to be clearer
// that it's purely a constructor and doesn't do any "real work".
return inst, nil
}
// EvalContext returns the [EvalContext] that the [ConfigInstance] would use
// to interact with its surrounding environment.
//
// This is exposed so that other systems that do work alongside the
// [ConfigInstance] work, such as implementations of [PlanGlue], can guarantee
// that they are interacting with the environment in a consistent way.
func (c *ConfigInstance) EvalContext() *EvalContext {
return c.evalContext
}
// newRootModuleInstance prepares a [configgraph.ModuleInstance] object based
// on the [ConfigInstance] and the given evaluation glue, as a shared building
// block for various exported methods on [ConfigInstance].
//
// This returns diagnostics encountered when loading the root module, but
// does not perform any further checks. Callers must then drive evaluation of
// the resulting configuration tree by calling
// [configgraph.ModuleInstance.CheckBeneath] to ensure that everything in
// the configuration gets a chance to report errors.
func (c *ConfigInstance) newRootModuleInstance(ctx context.Context, glue evalglue.Glue) (evalglue.CompiledModuleInstance, tfdiags.Diagnostics) {
rootModule, diags := c.evalContext.Modules.ModuleConfig(ctx, c.rootModuleSource, versions.All, nil)
if diags.HasErrors() {
return nil, diags
}
ret, moreDiags := rootModule.CompileModuleInstance(ctx, addrs.RootModuleInstance, &evalglue.ModuleCall{
InputValues: c.inputValues,
AllowImpureFunctions: c.allowImpureFunctions,
EvaluationGlue: glue,
EvalContext: c.evalContext,
})
diags = diags.Append(moreDiags)
return ret, diags
}
// PrepareTofu2024Module wraps a module targeting the current HCL-based edition
// of the OpenTofu language (which is called "tofu2024" in this set of packages)
// in an [UncompiledModule] value to return from an [ExternalModules]
// implementation.
func PrepareTofu2024Module(sourceAddr addrs.ModuleSource, mod *configs.Module) UncompiledModule {
return tofu2024.NewUncompiledModule(sourceAddr, mod)
}