Files
opentf/internal/command/meta.go
Martin Atkins afdfaf390d Warn when relying on non-default GODEBUG values
"GODEBUG" is a mechanism that the Go runtime forces on OpenTofu that allows
dynamic changes to various nuances of Go standard library behavior.

We cannot control when GODEBUG workarounds are added and removed from Go,
so if someone chooses to rely on them then they are likely to get broken
by a later release of OpenTofu that uses a newer version of Go. Therefore
we'd like anyone relying on these to tell us so we can try to find a more
sustainable solution to their problem early on while the workaround is
still available to them.

We also occasionally force certain GODEBUG workarounds on by default in our
official builds in anticipation of breakage caused by upstream Go changes.
In that case we'd still like to know if anyone is relying on them so that
we can remove those workarounds in a future release, but we'd prefer to
get the reports about it in an issue or discussion we'd already created
for that purpose.

This therefore introduces warning diagnostics any time something in the
"init", "plan", or "apply" commands relies on non-default GODEBUG settings.
For ones we enabled intentionally we provide a specific URL to report usage
as part of the diagnostic message, but we also have a generic message for
any GODEBUG settings the user chose to enable for themselves for reasons
we probably don't know about yet but would like to learn more about.

This is included in only that subset of commands as a compromise because
those are the commands most likely to interact with Go library features
that are affected by these workarounds -- they most commonly affect
functionality related to networking, TLS, etc -- and additional diagnostics
in these locations should not to cause breakage for anyone consuming the
machine-readable output, but there are other commands where additional
diagnostics are more likely to cause problems.

Unfortunately it isn't really possible to unit-test this because it relies
directly on information reported by the Go metrics system and there's no
facility to produce fake metrics for testing. However, Go 1.26 (which we're
currently using) there is a GODEBUG setting "netedns0=0" which causes a
warning any time OpenTofu makes a DNS request, so I've tested this manually
by running "tofu init" with that workaround enabled in a configuration that
depends on at least one provider (causing DNS lookups for the provider
registry) and seen it generate the expected warning message referring to
"netedns0".

These new warnings replace our previous quieter messages about this in
the TF_LOG=warn output, because those were only useful in situations where
GODEBUG was causing a problem that led to someone opening a bug report, but
we actually want to know of situations where GODEBUG was needed in order
for OpenTofu to _succeed_ and so for that we need more prominent messages
to let the operator know there's something they ought to report to us even
though there wasn't an error.

Signed-off-by: Martin Atkins <mart@degeneration.co.uk>
2026-04-22 17:08:25 -07:00

733 lines
26 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 command
import (
"bytes"
"context"
"errors"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-retryablehttp"
"github.com/opentofu/svchost/disco"
"github.com/opentofu/opentofu/internal/addrs"
"github.com/opentofu/opentofu/internal/backend"
"github.com/opentofu/opentofu/internal/backend/local"
"github.com/opentofu/opentofu/internal/command/arguments"
"github.com/opentofu/opentofu/internal/command/clistate"
"github.com/opentofu/opentofu/internal/command/flags"
"github.com/opentofu/opentofu/internal/command/views"
"github.com/opentofu/opentofu/internal/command/webbrowser"
"github.com/opentofu/opentofu/internal/command/workdir"
"github.com/opentofu/opentofu/internal/configs"
"github.com/opentofu/opentofu/internal/configs/configload"
"github.com/opentofu/opentofu/internal/getmodules"
"github.com/opentofu/opentofu/internal/getproviders"
"github.com/opentofu/opentofu/internal/plugins"
"github.com/opentofu/opentofu/internal/providers"
"github.com/opentofu/opentofu/internal/provisioners"
"github.com/opentofu/opentofu/internal/states"
"github.com/opentofu/opentofu/internal/tfdiags"
"github.com/opentofu/opentofu/internal/tofu"
"github.com/opentofu/opentofu/version"
)
// Meta are the meta-options that are available on all or most commands.
type Meta struct {
// The exported fields below should be set by anyone using a
// command with a Meta field. These are expected to be set externally
// (not from within the command itself).
// WorkingDir is an object representing the "working directory" where we're
// running commands. In the normal case this literally refers to the
// working directory of the OpenTofu process, though this can take on
// a more symbolic meaning when the user has overridden default behavior
// to specify a different working directory or to override the special
// data directory where we'll persist settings that must survive between
// consecutive commands.
//
// We're currently gradually migrating the various bits of state that
// must persist between consecutive commands in a session to be encapsulated
// in here, but we're not there yet and so there are also some methods on
// Meta which directly read and modify paths inside the data directory.
WorkingDir *workdir.Dir
View *views.View
GlobalPluginDirs []string // Additional paths to search for plugins
// Services provides access to remote endpoint information for
// 'tofu-native' services running at a specific user-facing hostname.
Services *disco.Disco
// RunningInAutomation indicates that commands are being run by an
// automated system rather than directly at a command prompt.
//
// This is a hint to various command routines that it may be confusing
// to print out messages that suggest running specific follow-up
// commands, since the user consuming the output will not be
// in a position to run such commands.
//
// The intended use-case of this flag is when OpenTofu is running in
// some sort of workflow orchestration tool which is abstracting away
// the specific commands being run.
RunningInAutomation bool
// CLIConfigDir is the directory from which CLI configuration files were
// read by the caller and the directory where any changes to CLI
// configuration files by commands should be made.
//
// If this is empty then no configuration directory is available and
// commands which require one cannot proceed.
CLIConfigDir string
// PluginCacheDir, if non-empty, enables caching of downloaded plugins
// into the given directory.
PluginCacheDir string
// PluginCacheMayBreakDependencyLockFile is a temporary CLI configuration-based
// opt out for the behavior of only using the plugin cache dir if its
// contents match checksums recorded in the dependency lock file.
//
// This is an accommodation for those who currently essentially ignore the
// dependency lock file -- treating it only as transient working directory
// state -- and therefore don't care if the plugin cache dir causes the
// checksums inside to only be sufficient for the computer where OpenTofu
// is currently running.
//
// We intend to remove this exception again (making the CLI configuration
// setting a silent no-op) in future once we've improved the dependency
// lock file mechanism so that it's usable for everyone and there are no
// longer any compelling reasons for folks to not lock their dependencies.
PluginCacheMayBreakDependencyLockFile bool
// ProviderSource allows determining the available versions of a provider
// and determines where a distribution package for a particular
// provider version can be obtained.
ProviderSource getproviders.Source
// ModulePackageFetcher is the client to use when fetching module packages
// from remote locations. This object effectively represents the policy
// for how to fetch remote module packages, which is decided by the caller.
//
// Leaving this nil means that only local modules (using relative paths
// in the source address) are supported, which is only reasonable for
// unit testing.
ModulePackageFetcher *getmodules.PackageFetcher
// MakeRegistryHTTPClient is a function called each time a command needs
// an HTTP client that will be used to make requests to a module or
// provider registry.
//
// This is used by package main to deal with some operator-configurable
// settings for retries and timeouts. If this isn't set then a new client
// with reasonable defaults for tests will be used instead.
MakeRegistryHTTPClient func() *retryablehttp.Client
// BrowserLauncher is used by commands that need to open a URL in a
// web browser.
BrowserLauncher webbrowser.Launcher
// A context.Context provided by the caller -- typically "package main" --
// which might be carrying telemetry-related metadata and so should be
// used when creating downstream traces, etc.
//
// This isn't guaranteed to be set, so use [Meta.CommandContext] to
// safely create a context for the entire execution of a command, which
// will be connected to this parent context if it's present.
CallerContext context.Context
// When this channel is closed, the command will be cancelled.
ShutdownCh <-chan struct{}
// ProviderDevOverrides are providers where we ignore the lock file, the
// configured version constraints, and the local cache directory and just
// always use exactly the path specified. This is intended to allow
// provider developers to easily test local builds without worrying about
// what version number they might eventually be released as, or what
// checksums they have.
ProviderDevOverrides map[addrs.Provider]getproviders.PackageLocalDir
// UnmanagedProviders are a set of providers that exist as processes
// predating OpenTofu, which OpenTofu should use but not worry about the
// lifecycle of.
//
// This is essentially a more extreme version of ProviderDevOverrides where
// OpenTofu doesn't even worry about how the provider server gets launched,
// just trusting that someone else did it before running OpenTofu.
UnmanagedProviders map[addrs.Provider]*plugin.ReattachConfig
// AllowExperimentalFeatures controls whether a command that embeds this
// Meta is permitted to make use of experimental OpenTofu features.
//
// Set this field only during the initial creation of Meta. If you change
// this field after calling methods of type Meta then the resulting
// behavior is undefined.
//
// In normal code this would be set by package main only in builds
// explicitly marked as being alpha releases or development snapshots,
// making experimental features unavailable otherwise. Test code may
// choose to set this if it needs to exercise experimental features.
//
// Some experiments predated the addition of this setting, and may
// therefore still be available even if this flag is false. Our intent
// is that all/most _future_ experiments will be unavailable unless this
// flag is set, to reinforce that experiments are not for production use.
AllowExperimentalFeatures bool
// ----------------------------------------------------------
// Protected: commands can set these
// ----------------------------------------------------------
// pluginPath is a user defined set of directories to look for plugins.
// This is set during init with the `-plugin-dir` flag, saved to a file in
// the data directory.
// This overrides all other search paths when discovering plugins.
pluginPath []string
// Override certain behavior for tests within this package
testingOverrides *testingOverrides
// ----------------------------------------------------------
// Private: do not set these
// ----------------------------------------------------------
// configLoader is a shared configuration loader that is used by
// LoadConfig and other commands that access configuration files.
// It is initialized on first use.
configLoader *configload.Loader
// backendState is the currently active backend state
backendState *clistate.BackendState
// Variables for the context (private)
variableArgs []flags.RawFlag
input bool
// The fields below are expected to be set by the command via
// command line flags. See the Apply command for an example.
//
// statePath is the path to the state file. If this is empty, then
// no state will be loaded. It is also okay for this to be a path to
// a file that doesn't exist; it is assumed that this means that there
// is simply no state.
//
// stateOutPath is used to override the output path for the state.
// If not provided, the StatePath is used causing the old state to
// be overridden.
//
// backupPath is used to backup the state file before writing a modified
// version. It defaults to stateOutPath + DefaultBackupExtension
//
// parallelism is used to control the number of concurrent operations
// allowed when walking the graph
//
// provider is to specify specific resource providers
//
// stateLock is set to false to disable state locking
//
// stateLockTimeout is the optional duration to retry a state locks locks
// when it is already locked by another process.
//
// forceInitCopy suppresses confirmation for copying state data during
// init.
//
// reconfigure forces init to ignore any stored configuration.
//
// migrateState confirms the user wishes to migrate from the prior backend
// configuration to a new configuration.
statePath string
stateOutPath string
backupPath string
parallelism int
stateLock bool
stateLockTimeout time.Duration
forceInitCopy bool
reconfigure bool
migrateState bool
// Used with commands which write state to allow users to write remote
// state even if the remote and local OpenTofu versions don't match.
ignoreRemoteVersion bool
// Used to cache the root module rootModuleCallCache and known variables.
// This helps prevent duplicate errors/warnings.
rootModuleCallCache *configs.StaticModuleCall
inputVariableCache map[string]backend.UnparsedVariableValue
// Since `tofu providers lock` and `tofu providers mirror` have their own
// logic to create the source to fetch providers through, we had to
// plumb this configuration through the [Meta] type to reach that part too.
// In any other cases, this configuration is built and used directly in `realMain`
// when the providers sources are built.
ProviderSourceLocationConfig getproviders.LocationConfig
}
type testingOverrides struct {
Providers map[addrs.Provider]providers.Factory
Provisioners map[string]provisioners.Factory
}
// initStatePaths is used to initialize the default values for
// statePath, stateOutPath, and backupPath
func (m *Meta) initStatePaths() {
if m.statePath == "" {
m.statePath = arguments.DefaultStateFilename
}
if m.stateOutPath == "" {
m.stateOutPath = m.statePath
}
if m.backupPath == "" {
m.backupPath = m.stateOutPath + DefaultBackupExtension
}
}
// StateOutPath returns the true output path for the state file
func (m *Meta) StateOutPath() string {
return m.stateOutPath
}
const (
// InputModeEnvVar is the environment variable that, if set to "false" or
// "0", causes tofu commands to behave as if the `-input=false` flag was
// specified.
InputModeEnvVar = "TF_INPUT"
)
// InputMode returns the type of input we should ask for in the form of
// tofu.InputMode which is passed directly to Context.Input.
func (m *Meta) InputMode() tofu.InputMode {
if test || !m.input {
return 0
}
if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
if v, err := strconv.ParseBool(envVar); err == nil {
if !v {
return 0
}
}
}
var mode tofu.InputMode
mode |= tofu.InputModeProvider
return mode
}
// UIInput returns a UIInput object to be used for asking for input.
func (m *Meta) UIInput() tofu.UIInput {
return &UIInput{
Colorize: m.View.Colorize(),
}
}
// InterruptibleContext returns a context.Context that will be cancelled
// if the process is interrupted by a platform-specific interrupt signal.
//
// The typical way to use this is to pass the result of [Meta.CommandContext]
// as the base context, but that's appropriate only if the interruptible
// context is being created directly inside the "Run" method of a particular
// command, to create a context representing the entire remaining runtime of
// that command:
//
// As usual with cancelable contexts, the caller must always call the given
// cancel function once all operations are complete in order to make sure
// that the context resources will still be freed even if there is no
// interruption.
//
// // This example is only for when using this function very early in
// // the "Run" method of a Command implementation. If you already have
// // an active context, pass that in as base instead.
// ctx, done := c.InterruptibleContext(c.CommandContext())
// defer done()
func (m *Meta) InterruptibleContext(base context.Context) (context.Context, context.CancelFunc) {
if m.ShutdownCh == nil {
// If we're running in a unit testing context without a shutdown
// channel populated then we'll return an uncancelable channel.
return base, func() {}
}
ctx, cancel := context.WithCancel(base)
go func() {
select {
case <-m.ShutdownCh:
cancel()
case <-ctx.Done():
// finished without being interrupted
}
}()
return ctx, cancel
}
// CommandContext returns the "root context" to use in the main Run function
// of a command.
//
// This method is just a substitute for passing a context directly to the
// "Run" method of a command, which we can't do because that API is owned by
// mitchellh/cli rather than by OpenTofu. Use this only in situations
// comparable to the context having been passed in as an argument to Run.
//
// If the caller (e.g. "package main") provided a context when it instantiated
// the Meta then the returned context will inherit all of its values, deadlines,
// etc. If the caller did not provide a context then the result is an inert
// background context ready to be passed to other functions.
func (m *Meta) CommandContext() context.Context {
if m.CallerContext == nil {
return context.Background()
}
// We just return the caller context directly for now, since we don't
// have anything to add to it.
return m.CallerContext
}
// RunOperation executes the given operation on the given backend, blocking
// until that operation completes or is interrupted, and then returns
// the RunningOperation object representing the completed or
// aborted operation that is, despite the name, no longer running.
//
// An error is returned if the operation either fails to start or is cancelled.
// If the operation runs to completion then no error is returned even if the
// operation itself is unsuccessful. Use the "Result" field of the
// returned operation object to recognize operation-level failure.
func (m *Meta) RunOperation(ctx context.Context, b backend.Enhanced, opReq *backend.Operation) (*backend.RunningOperation, tfdiags.Diagnostics) {
if opReq.View == nil {
panic("RunOperation called with nil View")
}
if opReq.ConfigDir != "" {
opReq.ConfigDir = m.WorkingDir.NormalizePath(opReq.ConfigDir)
}
// Inject variables and root module call
var diags, callDiags tfdiags.Diagnostics
opReq.Variables, diags = m.collectVariableValues()
opReq.RootCall, callDiags = m.rootModuleCall(ctx, opReq.ConfigDir)
diags = diags.Append(callDiags)
if diags.HasErrors() {
return nil, diags
}
op, err := b.Operation(ctx, opReq)
if err != nil {
return nil, diags.Append(fmt.Errorf("error starting operation: %w", err))
}
// Wait for the operation to complete or an interrupt to occur
select {
case <-m.ShutdownCh:
// gracefully stop the operation
op.Stop()
// Notify the user
opReq.View.Interrupted()
// Still get the result, since there is still one
select {
case <-m.ShutdownCh:
opReq.View.FatalInterrupt()
// cancel the operation completely
op.Cancel()
// the operation should return asap
// but timeout just in case
select {
case <-op.Done():
case <-time.After(5 * time.Second):
}
return nil, diags.Append(errors.New("operation canceled"))
case <-op.Done():
// operation completed after Stop
}
case <-op.Done():
// operation completed normally
}
return op, diags
}
// contextOpts returns the options to use to initialize a OpenTofu
// context with the settings from this Meta.
func (m *Meta) contextOpts(ctx context.Context) (*tofu.ContextOpts, error) {
workspace, err := m.Workspace(ctx)
if err != nil {
return nil, err
}
var opts tofu.ContextOpts
opts.UIInput = m.UIInput()
opts.Parallelism = m.parallelism
// If testingOverrides are set, we'll skip the plugin discovery process
// and just work with what we've been given, thus allowing the tests
// to provide mock providers and provisioners.
if m.testingOverrides != nil {
opts.Plugins = plugins.NewLibrary(
m.testingOverrides.Providers,
m.testingOverrides.Provisioners,
)
} else {
var providerFactories map[addrs.Provider]providers.Factory
providerFactories, err = m.providerFactories()
opts.Plugins = plugins.NewLibrary(
providerFactories,
m.provisionerFactories(),
)
}
opts.Meta = &tofu.ContextMeta{
Env: workspace,
OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
}
return &opts, err
}
// confirm asks a yes/no confirmation.
func (m *Meta) confirm(opts *tofu.InputOpts) (bool, error) {
if !m.Input() {
return false, errors.New("input is disabled")
}
for range 2 {
v, err := m.UIInput().Input(context.Background(), opts)
if err != nil {
return false, fmt.Errorf(
"Error asking for confirmation: %w", err)
}
switch strings.ToLower(v) {
case "no":
return false, nil
case "yes":
return true, nil
}
}
return false, nil
}
// godebugUsageWarnings returns zero or more warning diagnostics describing
// non-default GODEBUG settings that have been previously relied on in earlier
// execution before calling this function.
//
// These warnings encourage the reader to report to the OpenTofu team if they
// find themselves relying on any GODEBUG settings, because the availability
// and behavior of GODEBUG settings is subject to change outside our control
// as we upgrade to newer versions of Go and so we want to deal with any
// reliance on them early before it becomes a breaking change for the user in
// a future version.
//
// Note that this can only report about the subset of GODEBUG keys for which
// the runtime generates usage metrics. Some GODEBUG keys instead just directly
// modify the runtime behavior without attempting the unmodified behavior first,
// and those ones will never be mentioned here. The ones that cannot report
// metrics are classified as "Opaque" in the table of possible godebug settings:
//
// https://github.com/golang/go/blob/master/src/internal/godebugs/table.go
func (m *Meta) godebugUsageWarnings() tfdiags.Diagnostics {
const summary = "Relying on unsupported Go runtime behavior"
var diags tfdiags.Diagnostics
for name, reportURL := range version.GodebugActivations() {
if reportURL != "" {
// This particular key name is one we had intentionally enabled
// to defer a breaking change from upstream Go, and so we have
// a dedicated place set aside to discuss reliance of it that
// we'll mention in the diagnostic message.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
summary,
fmt.Sprintf(
"This execution of OpenTofu relied on a Go runtime workaround named %q, which is not officially supported and may be removed without notice in a future version.\n\nIf you cannot avoid relying on this workaround, please tell us more about your situation at:\n %s",
name, reportURL,
),
))
continue
}
// This is _not_ a GODEBUG key we're expecting to encounter, and so
// presumably the end-user opted in to this themselves by setting
// the GODEBUG environment variable. Since we're never testing OpenTofu
// with non-default GODEBUG settings and they change outside of our
// control we won't be able to guarantee preserving that behavior in
// future releases and so we'll just make a general request for the
// operator to tell us what's going on for them so we can hopefully
// give them an officially-supported solution to their problem.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
summary,
fmt.Sprintf(
"This execution of OpenTofu relied on a Go runtime workaround named %q, which is not officially supported and may be removed without notice in a future version.\n\nIf you cannot avoid relying on this workaround, please report an issue to the OpenTofu project describing your situation so we can find a more sustainable solution to this problem.",
name,
),
))
}
return diags
}
// WorkspaceNameEnvVar is the name of the environment variable that can be used
// to set the name of the OpenTofu workspace, overriding the workspace chosen
// by `tofu workspace select`.
//
// Note that this environment variable is ignored by `tofu workspace new`
// and `tofu workspace delete`.
const WorkspaceNameEnvVar = "TF_WORKSPACE"
var errInvalidWorkspaceNameEnvVar = fmt.Errorf("Invalid workspace name set using %s", WorkspaceNameEnvVar)
// Workspace returns the name of the currently configured workspace, corresponding
// to the desired named state.
func (m *Meta) Workspace(ctx context.Context) (string, error) {
current, overridden := m.WorkspaceOverridden(ctx)
if overridden && !validWorkspaceName(current) {
return "", errInvalidWorkspaceNameEnvVar
}
return current, nil
}
// WorkspaceOverridden returns the name of the currently configured workspace,
// corresponding to the desired named state, as well as a bool saying whether
// this was set via the TF_WORKSPACE environment variable.
func (m *Meta) WorkspaceOverridden(_ context.Context) (string, bool) {
if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
return envVar, true
}
envData, err := os.ReadFile(filepath.Join(m.WorkingDir.DataDir(), local.DefaultWorkspaceFile))
current := string(bytes.TrimSpace(envData))
if current == "" {
current = backend.DefaultStateName
}
if err != nil && !os.IsNotExist(err) {
// always return the default if we can't get a workspace name
log.Printf("[ERROR] failed to read current workspace: %s", err)
}
return current, false
}
// SetWorkspace saves the given name as the current workspace in the local
// filesystem.
func (m *Meta) SetWorkspace(name string) error {
err := os.MkdirAll(m.WorkingDir.DataDir(), 0755)
if err != nil {
return err
}
err = os.WriteFile(filepath.Join(m.WorkingDir.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
if err != nil {
return err
}
return nil
}
// isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
func isAutoVarFile(path string) bool {
return strings.HasSuffix(path, ".auto.tfvars") ||
strings.HasSuffix(path, ".auto.tfvars.json")
}
// FIXME: as an interim refactoring step, we apply the contents of the state
// arguments directly to the Meta object. Future work would ideally update the
// code paths which use these arguments to be passed them directly for clarity.
func (m *Meta) applyStateArguments(args *arguments.State) {
m.stateLock = args.Lock
m.stateLockTimeout = args.LockTimeout
m.statePath = args.StatePath
m.stateOutPath = args.StateOutPath
m.backupPath = args.BackupPath
}
// checkRequiredVersion loads the config and check if the
// core version requirements are satisfied.
func (m *Meta) checkRequiredVersion(ctx context.Context) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
loader, err := m.initConfigLoader()
if err != nil {
diags = diags.Append(err)
return diags
}
pwd, err := os.Getwd()
if err != nil {
diags = diags.Append(fmt.Errorf("Error getting pwd: %w", err))
return diags
}
call, callDiags := m.rootModuleCall(ctx, pwd)
if callDiags.HasErrors() {
diags = diags.Append(callDiags)
return diags
}
_, configDiags := loader.LoadConfig(ctx, pwd, call)
if configDiags.HasErrors() {
diags = diags.Append(configDiags)
return diags
}
// If there were any OpenTofu-version-related errors then they would've
// already been detected by loader.LoadConfig above.
return nil
}
// MaybeGetSchemas attempts to load and return the schemas
// If there is not enough information to return the schemas,
// it could potentially return nil without errors. It is the
// responsibility of the caller to handle the lack of schema
// information accordingly
func (c *Meta) MaybeGetSchemas(ctx context.Context, state *states.State, config *configs.Config) (*tofu.Schemas, tfdiags.Diagnostics) {
var diags tfdiags.Diagnostics
path, err := os.Getwd()
if err != nil {
diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
return nil, diags
}
if config == nil {
config, diags = c.loadConfig(ctx, path)
if diags.HasErrors() {
diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
return nil, diags
}
}
if config != nil || state != nil {
opts, err := c.contextOpts(ctx)
if err != nil {
diags = diags.Append(err)
return nil, diags
}
tfCtx, ctxDiags := tofu.NewContext(opts)
diags = diags.Append(ctxDiags)
if ctxDiags.HasErrors() {
return nil, diags
}
var schemaDiags tfdiags.Diagnostics
schemas, schemaDiags := tfCtx.Schemas(ctx, config, state)
diags = diags.Append(schemaDiags)
if schemaDiags.HasErrors() {
return nil, diags
}
return schemas, diags
}
return nil, diags
}