blob: d73ac139304cb3787c22e031e639b439510e2145 [file] [edit]
// Copyright (c) The OpenTofu Authors
// SPDX-License-Identifier: MPL-2.0
// Copyright (c) 2023 HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package command
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"sync"
"github.com/apparentlymart/go-versions/versions"
"github.com/hashicorp/hcl/v2"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configload"
"github.com/opentofu/opentofu/internal/lang/eval"
"github.com/opentofu/opentofu/internal/lang/exprs"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/version"
"github.com/zclconf/go-cty/cty"
)
func (m *Meta) NewRuntimeEnabled() bool {
if !version.IsNightly() && !version.IsDev() {
return false
}
optIn := os.Getenv("TOFU_X_EXPERIMENTAL_RUNTIME")
return optIn != ""
}
func (m *Meta) StaticConfigInstance(ctx context.Context, root *configs.Module, modules eval.ExternalModules) (*eval.ConfigInstance, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
rootCall, moreDiags := m.rootModuleCall(ctx, root.SourceDir)
diags = diags.Append(moreDiags)
if diags.HasErrors() {
return nil, diags
}
workspace, err := m.Workspace(ctx)
if err != nil {
return nil, diags.Append(err)
}
rawInput := map[string]cty.Value{}
varValue := rootCall.Variables()
for _, variable := range root.Variables {
val, hclDiags := varValue(variable)
diags = diags.Append(hclDiags)
if val == cty.NilVal {
val = cty.DynamicVal
}
rawInput[variable.Name] = val
}
inputValues := exprs.ConstantValuer(cty.ObjectVal(rawInput))
owd := m.WorkingDir.OriginalWorkingDir()
// The current working directory should always be absolute, whether we
// just looked it up or whether we were relying on ContextMeta's
// (possibly non-normalized) path.
owd, err = filepath.Abs(owd)
if err != nil {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: `Failed to get working directory`,
Detail: fmt.Sprintf(`The value for the original working directory cannot be determined due to a system error: %s`, err),
})
return nil, diags
}
if modules == nil {
modules = &newRuntimeModules{
loader: m.configLoader(),
root: root,
}
}
staticPlugins := eval.NewStaticPlugins()
evalCtx := &eval.EvalContext{
RootModuleDir: root.SourceDir,
OriginalWorkingDir: owd,
Modules: modules,
Providers: staticPlugins,
Provisioners: staticPlugins,
Workspace: workspace,
}
// The new config-loading system wants to work in terms of module source
// addresses rather than raw local filenames, so we'll ask the
// addrs package to parse the path we were given. We need to adjust
// a little though, because this function was designed for parsing
// the "source" argument in a module block, not a plain filepath.
// We should add a function in package addrs that's actually intended for
// turning arbitrary filesystem paths in to addrs.LocalSource in the long
// run, but this will do for now.
configDir := root.SourceDir
if !filepath.IsAbs(configDir) {
configDir = "." + string(filepath.Separator) + configDir
}
rootModuleSource, err := addrs.ParseModuleSource(configDir)
if err != nil {
diags = diags.Append(fmt.Errorf("invalid root module source address: %w", err))
return nil, diags
}
configCall := &eval.ConfigCall{
RootModuleSource: rootModuleSource,
InputValues: inputValues,
AllowImpureFunctions: false,
EvalContext: evalCtx,
}
configInst, moreDiags := eval.NewConfigInstance(ctx, configCall)
diags = diags.Append(moreDiags)
if moreDiags.HasErrors() {
return nil, diags
}
return configInst, diags
}
// newRuntimeModules is an implementation of [eval.ExternalModules] that makes
// a best effort to shim to OpenTofu's current module loader, even though
// it works in some slightly-different terms than this new API expects.
type newRuntimeModules struct {
loader configload.Loader
root *configs.Module
// configload.Loader is not concurrency-safe because it wraps
// hclparse.Parser functionality that is not concurrency-safe, so we must
// hold this lock whenever we're interacting with the loader object.
mu sync.Mutex
}
var _ eval.ExternalModules = (*newRuntimeModules)(nil)
// ModuleConfig implements evalglue.ExternalModules.
func (n *newRuntimeModules) ModuleConfig(ctx context.Context, source addrs.ModuleSource, allowedVersions versions.Set, forCall *addrs.AbsModuleCall) (eval.UncompiledModule, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
if forCall == nil {
return eval.PrepareTofu2024Module(source, n.root), diags
}
record, hclDiags := n.loader.ModuleLocalPath(ctx, &configs.ModuleRequest{
Name: forCall.Call.Name,
Path: forCall.Module.Module().Child(forCall.Call.Name),
SourceAddr: source,
VersionConstraint: configs.VersionConstraint{},
//SourceAddrRange hcl.Range
//CallRange hcl.Range
Parent: &configs.Config{
Path: forCall.Module.Module(),
},
})
diags = diags.Append(hclDiags)
if diags.HasErrors() {
return nil, diags
}
sourceDir := record.Dir
log.Printf("[TRACE] backend/local: Loading module from %q from local path %q", source, sourceDir)
n.mu.Lock()
mod, hclDiags := n.loader.LoadConfigDir(sourceDir)
n.mu.Unlock()
diags = diags.Append(hclDiags)
if hclDiags.HasErrors() {
return nil, diags
}
return eval.PrepareTofu2024Module(source, mod), diags
}