github.com/opentofu/opentofu@v1.7.1/internal/command/meta.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package command
     7  
     8  import (
     9  	"bytes"
    10  	"context"
    11  	"errors"
    12  	"flag"
    13  	"fmt"
    14  	"io"
    15  	"log"
    16  	"os"
    17  	"path/filepath"
    18  	"strconv"
    19  	"strings"
    20  	"time"
    21  
    22  	"github.com/hashicorp/go-plugin"
    23  	"github.com/hashicorp/terraform-svchost/disco"
    24  	"github.com/mitchellh/cli"
    25  	"github.com/mitchellh/colorstring"
    26  
    27  	"github.com/opentofu/opentofu/internal/addrs"
    28  	"github.com/opentofu/opentofu/internal/backend"
    29  	"github.com/opentofu/opentofu/internal/backend/local"
    30  	"github.com/opentofu/opentofu/internal/command/arguments"
    31  	"github.com/opentofu/opentofu/internal/command/format"
    32  	"github.com/opentofu/opentofu/internal/command/views"
    33  	"github.com/opentofu/opentofu/internal/command/webbrowser"
    34  	"github.com/opentofu/opentofu/internal/command/workdir"
    35  	"github.com/opentofu/opentofu/internal/configs"
    36  	"github.com/opentofu/opentofu/internal/configs/configload"
    37  	"github.com/opentofu/opentofu/internal/getproviders"
    38  	legacy "github.com/opentofu/opentofu/internal/legacy/tofu"
    39  	"github.com/opentofu/opentofu/internal/providers"
    40  	"github.com/opentofu/opentofu/internal/provisioners"
    41  	"github.com/opentofu/opentofu/internal/states"
    42  	"github.com/opentofu/opentofu/internal/terminal"
    43  	"github.com/opentofu/opentofu/internal/tfdiags"
    44  	"github.com/opentofu/opentofu/internal/tofu"
    45  )
    46  
    47  // Meta are the meta-options that are available on all or most commands.
    48  type Meta struct {
    49  	// The exported fields below should be set by anyone using a
    50  	// command with a Meta field. These are expected to be set externally
    51  	// (not from within the command itself).
    52  
    53  	// WorkingDir is an object representing the "working directory" where we're
    54  	// running commands. In the normal case this literally refers to the
    55  	// working directory of the OpenTofu process, though this can take on
    56  	// a more symbolic meaning when the user has overridden default behavior
    57  	// to specify a different working directory or to override the special
    58  	// data directory where we'll persist settings that must survive between
    59  	// consecutive commands.
    60  	//
    61  	// We're currently gradually migrating the various bits of state that
    62  	// must persist between consecutive commands in a session to be encapsulated
    63  	// in here, but we're not there yet and so there are also some methods on
    64  	// Meta which directly read and modify paths inside the data directory.
    65  	WorkingDir *workdir.Dir
    66  
    67  	// Streams tracks the raw Stdout, Stderr, and Stdin handles along with
    68  	// some basic metadata about them, such as whether each is connected to
    69  	// a terminal, how wide the possible terminal is, etc.
    70  	//
    71  	// For historical reasons this might not be set in unit test code, and
    72  	// so functions working with this field must check if it's nil and
    73  	// do some default behavior instead if so, rather than panicking.
    74  	Streams *terminal.Streams
    75  
    76  	View *views.View
    77  
    78  	Color            bool     // True if output should be colored
    79  	GlobalPluginDirs []string // Additional paths to search for plugins
    80  	Ui               cli.Ui   // Ui for output
    81  
    82  	// Services provides access to remote endpoint information for
    83  	// 'tofu-native' services running at a specific user-facing hostname.
    84  	Services *disco.Disco
    85  
    86  	// RunningInAutomation indicates that commands are being run by an
    87  	// automated system rather than directly at a command prompt.
    88  	//
    89  	// This is a hint to various command routines that it may be confusing
    90  	// to print out messages that suggest running specific follow-up
    91  	// commands, since the user consuming the output will not be
    92  	// in a position to run such commands.
    93  	//
    94  	// The intended use-case of this flag is when OpenTofu is running in
    95  	// some sort of workflow orchestration tool which is abstracting away
    96  	// the specific commands being run.
    97  	RunningInAutomation bool
    98  
    99  	// CLIConfigDir is the directory from which CLI configuration files were
   100  	// read by the caller and the directory where any changes to CLI
   101  	// configuration files by commands should be made.
   102  	//
   103  	// If this is empty then no configuration directory is available and
   104  	// commands which require one cannot proceed.
   105  	CLIConfigDir string
   106  
   107  	// PluginCacheDir, if non-empty, enables caching of downloaded plugins
   108  	// into the given directory.
   109  	PluginCacheDir string
   110  
   111  	// PluginCacheMayBreakDependencyLockFile is a temporary CLI configuration-based
   112  	// opt out for the behavior of only using the plugin cache dir if its
   113  	// contents match checksums recorded in the dependency lock file.
   114  	//
   115  	// This is an accommodation for those who currently essentially ignore the
   116  	// dependency lock file -- treating it only as transient working directory
   117  	// state -- and therefore don't care if the plugin cache dir causes the
   118  	// checksums inside to only be sufficient for the computer where OpenTofu
   119  	// is currently running.
   120  	//
   121  	// We intend to remove this exception again (making the CLI configuration
   122  	// setting a silent no-op) in future once we've improved the dependency
   123  	// lock file mechanism so that it's usable for everyone and there are no
   124  	// longer any compelling reasons for folks to not lock their dependencies.
   125  	PluginCacheMayBreakDependencyLockFile bool
   126  
   127  	// ProviderSource allows determining the available versions of a provider
   128  	// and determines where a distribution package for a particular
   129  	// provider version can be obtained.
   130  	ProviderSource getproviders.Source
   131  
   132  	// BrowserLauncher is used by commands that need to open a URL in a
   133  	// web browser.
   134  	BrowserLauncher webbrowser.Launcher
   135  
   136  	// A context.Context provided by the caller -- typically "package main" --
   137  	// which might be carrying telemetry-related metadata and so should be
   138  	// used when creating downstream traces, etc.
   139  	//
   140  	// This isn't guaranteed to be set, so use [Meta.CommandContext] to
   141  	// safely create a context for the entire execution of a command, which
   142  	// will be connected to this parent context if it's present.
   143  	CallerContext context.Context
   144  
   145  	// When this channel is closed, the command will be cancelled.
   146  	ShutdownCh <-chan struct{}
   147  
   148  	// ProviderDevOverrides are providers where we ignore the lock file, the
   149  	// configured version constraints, and the local cache directory and just
   150  	// always use exactly the path specified. This is intended to allow
   151  	// provider developers to easily test local builds without worrying about
   152  	// what version number they might eventually be released as, or what
   153  	// checksums they have.
   154  	ProviderDevOverrides map[addrs.Provider]getproviders.PackageLocalDir
   155  
   156  	// UnmanagedProviders are a set of providers that exist as processes
   157  	// predating OpenTofu, which OpenTofu should use but not worry about the
   158  	// lifecycle of.
   159  	//
   160  	// This is essentially a more extreme version of ProviderDevOverrides where
   161  	// OpenTofu doesn't even worry about how the provider server gets launched,
   162  	// just trusting that someone else did it before running OpenTofu.
   163  	UnmanagedProviders map[addrs.Provider]*plugin.ReattachConfig
   164  
   165  	// AllowExperimentalFeatures controls whether a command that embeds this
   166  	// Meta is permitted to make use of experimental OpenTofu features.
   167  	//
   168  	// Set this field only during the initial creation of Meta. If you change
   169  	// this field after calling methods of type Meta then the resulting
   170  	// behavior is undefined.
   171  	//
   172  	// In normal code this would be set by package main only in builds
   173  	// explicitly marked as being alpha releases or development snapshots,
   174  	// making experimental features unavailable otherwise. Test code may
   175  	// choose to set this if it needs to exercise experimental features.
   176  	//
   177  	// Some experiments predated the addition of this setting, and may
   178  	// therefore still be available even if this flag is false. Our intent
   179  	// is that all/most _future_ experiments will be unavailable unless this
   180  	// flag is set, to reinforce that experiments are not for production use.
   181  	AllowExperimentalFeatures bool
   182  
   183  	//----------------------------------------------------------
   184  	// Protected: commands can set these
   185  	//----------------------------------------------------------
   186  
   187  	// pluginPath is a user defined set of directories to look for plugins.
   188  	// This is set during init with the `-plugin-dir` flag, saved to a file in
   189  	// the data directory.
   190  	// This overrides all other search paths when discovering plugins.
   191  	pluginPath []string
   192  
   193  	// Override certain behavior for tests within this package
   194  	testingOverrides *testingOverrides
   195  
   196  	//----------------------------------------------------------
   197  	// Private: do not set these
   198  	//----------------------------------------------------------
   199  
   200  	// configLoader is a shared configuration loader that is used by
   201  	// LoadConfig and other commands that access configuration files.
   202  	// It is initialized on first use.
   203  	configLoader *configload.Loader
   204  
   205  	// backendState is the currently active backend state
   206  	backendState *legacy.BackendState
   207  
   208  	// Variables for the context (private)
   209  	variableArgs rawFlags
   210  	input        bool
   211  
   212  	// Targets for this context (private)
   213  	targets     []addrs.Targetable
   214  	targetFlags []string
   215  
   216  	// Internal fields
   217  	color bool
   218  	oldUi cli.Ui
   219  
   220  	// The fields below are expected to be set by the command via
   221  	// command line flags. See the Apply command for an example.
   222  	//
   223  	// statePath is the path to the state file. If this is empty, then
   224  	// no state will be loaded. It is also okay for this to be a path to
   225  	// a file that doesn't exist; it is assumed that this means that there
   226  	// is simply no state.
   227  	//
   228  	// stateOutPath is used to override the output path for the state.
   229  	// If not provided, the StatePath is used causing the old state to
   230  	// be overridden.
   231  	//
   232  	// backupPath is used to backup the state file before writing a modified
   233  	// version. It defaults to stateOutPath + DefaultBackupExtension
   234  	//
   235  	// parallelism is used to control the number of concurrent operations
   236  	// allowed when walking the graph
   237  	//
   238  	// provider is to specify specific resource providers
   239  	//
   240  	// stateLock is set to false to disable state locking
   241  	//
   242  	// stateLockTimeout is the optional duration to retry a state locks locks
   243  	// when it is already locked by another process.
   244  	//
   245  	// forceInitCopy suppresses confirmation for copying state data during
   246  	// init.
   247  	//
   248  	// reconfigure forces init to ignore any stored configuration.
   249  	//
   250  	// migrateState confirms the user wishes to migrate from the prior backend
   251  	// configuration to a new configuration.
   252  	//
   253  	// compactWarnings (-compact-warnings) selects a more compact presentation
   254  	// of warnings in the output when they are not accompanied by errors.
   255  	statePath        string
   256  	stateOutPath     string
   257  	backupPath       string
   258  	parallelism      int
   259  	stateLock        bool
   260  	stateLockTimeout time.Duration
   261  	forceInitCopy    bool
   262  	reconfigure      bool
   263  	migrateState     bool
   264  	compactWarnings  bool
   265  
   266  	// Used with commands which write state to allow users to write remote
   267  	// state even if the remote and local OpenTofu versions don't match.
   268  	ignoreRemoteVersion bool
   269  
   270  	outputInJSON bool
   271  }
   272  
   273  type testingOverrides struct {
   274  	Providers    map[addrs.Provider]providers.Factory
   275  	Provisioners map[string]provisioners.Factory
   276  }
   277  
   278  // initStatePaths is used to initialize the default values for
   279  // statePath, stateOutPath, and backupPath
   280  func (m *Meta) initStatePaths() {
   281  	if m.statePath == "" {
   282  		m.statePath = DefaultStateFilename
   283  	}
   284  	if m.stateOutPath == "" {
   285  		m.stateOutPath = m.statePath
   286  	}
   287  	if m.backupPath == "" {
   288  		m.backupPath = m.stateOutPath + DefaultBackupExtension
   289  	}
   290  }
   291  
   292  // StateOutPath returns the true output path for the state file
   293  func (m *Meta) StateOutPath() string {
   294  	return m.stateOutPath
   295  }
   296  
   297  // Colorize returns the colorization structure for a command.
   298  func (m *Meta) Colorize() *colorstring.Colorize {
   299  	colors := make(map[string]string)
   300  	for k, v := range colorstring.DefaultColors {
   301  		colors[k] = v
   302  	}
   303  	colors["purple"] = "38;5;57"
   304  
   305  	return &colorstring.Colorize{
   306  		Colors:  colors,
   307  		Disable: !m.color,
   308  		Reset:   true,
   309  	}
   310  }
   311  
   312  // fixupMissingWorkingDir is a compensation for various existing tests which
   313  // directly construct incomplete "Meta" objects. Specifically, it deals with
   314  // a test that omits a WorkingDir value by constructing one just-in-time.
   315  //
   316  // We shouldn't ever rely on this in any real codepath, because it doesn't
   317  // take into account the various ways users can override our default
   318  // directory selection behaviors.
   319  func (m *Meta) fixupMissingWorkingDir() {
   320  	if m.WorkingDir == nil {
   321  		log.Printf("[WARN] This 'Meta' object is missing its WorkingDir, so we're creating a default one suitable only for tests")
   322  		m.WorkingDir = workdir.NewDir(".")
   323  	}
   324  }
   325  
   326  // DataDir returns the directory where local data will be stored.
   327  // Defaults to DefaultDataDir in the current working directory.
   328  func (m *Meta) DataDir() string {
   329  	m.fixupMissingWorkingDir()
   330  	return m.WorkingDir.DataDir()
   331  }
   332  
   333  const (
   334  	// InputModeEnvVar is the environment variable that, if set to "false" or
   335  	// "0", causes tofu commands to behave as if the `-input=false` flag was
   336  	// specified.
   337  	InputModeEnvVar = "TF_INPUT"
   338  )
   339  
   340  // InputMode returns the type of input we should ask for in the form of
   341  // tofu.InputMode which is passed directly to Context.Input.
   342  func (m *Meta) InputMode() tofu.InputMode {
   343  	if test || !m.input {
   344  		return 0
   345  	}
   346  
   347  	if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
   348  		if v, err := strconv.ParseBool(envVar); err == nil {
   349  			if !v {
   350  				return 0
   351  			}
   352  		}
   353  	}
   354  
   355  	var mode tofu.InputMode
   356  	mode |= tofu.InputModeProvider
   357  
   358  	return mode
   359  }
   360  
   361  // UIInput returns a UIInput object to be used for asking for input.
   362  func (m *Meta) UIInput() tofu.UIInput {
   363  	return &UIInput{
   364  		Colorize: m.Colorize(),
   365  	}
   366  }
   367  
   368  // OutputColumns returns the number of columns that normal (non-error) UI
   369  // output should be wrapped to fill.
   370  //
   371  // This is the column count to use if you'll be printing your message via
   372  // the Output or Info methods of m.Ui.
   373  func (m *Meta) OutputColumns() int {
   374  	if m.Streams == nil {
   375  		// A default for unit tests that don't populate Meta fully.
   376  		return 78
   377  	}
   378  	return m.Streams.Stdout.Columns()
   379  }
   380  
   381  // ErrorColumns returns the number of columns that error UI output should be
   382  // wrapped to fill.
   383  //
   384  // This is the column count to use if you'll be printing your message via
   385  // the Error or Warn methods of m.Ui.
   386  func (m *Meta) ErrorColumns() int {
   387  	if m.Streams == nil {
   388  		// A default for unit tests that don't populate Meta fully.
   389  		return 78
   390  	}
   391  	return m.Streams.Stderr.Columns()
   392  }
   393  
   394  // StdinPiped returns true if the input is piped.
   395  func (m *Meta) StdinPiped() bool {
   396  	if m.Streams == nil {
   397  		// If we don't have m.Streams populated then we're presumably in a unit
   398  		// test that doesn't properly populate Meta, so we'll just say the
   399  		// output _isn't_ piped because that's the common case and so most likely
   400  		// to be useful to a unit test.
   401  		return false
   402  	}
   403  	return !m.Streams.Stdin.IsTerminal()
   404  }
   405  
   406  // InterruptibleContext returns a context.Context that will be cancelled
   407  // if the process is interrupted by a platform-specific interrupt signal.
   408  //
   409  // The typical way to use this is to pass the result of [Meta.CommandContext]
   410  // as the base context, but that's appropriate only if the interruptible
   411  // context is being created directly inside the "Run" method of a particular
   412  // command, to create a context representing the entire remaining runtime of
   413  // that command:
   414  //
   415  // As usual with cancelable contexts, the caller must always call the given
   416  // cancel function once all operations are complete in order to make sure
   417  // that the context resources will still be freed even if there is no
   418  // interruption.
   419  //
   420  //	// This example is only for when using this function very early in
   421  //	// the "Run" method of a Command implementation. If you already have
   422  //	// an active context, pass that in as base instead.
   423  //	ctx, done := c.InterruptibleContext(c.CommandContext())
   424  //	defer done()
   425  func (m *Meta) InterruptibleContext(base context.Context) (context.Context, context.CancelFunc) {
   426  	if m.ShutdownCh == nil {
   427  		// If we're running in a unit testing context without a shutdown
   428  		// channel populated then we'll return an uncancelable channel.
   429  		return base, func() {}
   430  	}
   431  
   432  	ctx, cancel := context.WithCancel(base)
   433  	go func() {
   434  		select {
   435  		case <-m.ShutdownCh:
   436  			cancel()
   437  		case <-ctx.Done():
   438  			// finished without being interrupted
   439  		}
   440  	}()
   441  	return ctx, cancel
   442  }
   443  
   444  // CommandContext returns the "root context" to use in the main Run function
   445  // of a command.
   446  //
   447  // This method is just a substitute for passing a context directly to the
   448  // "Run" method of a command, which we can't do because that API is owned by
   449  // mitchellh/cli rather than by OpenTofu. Use this only in situations
   450  // comparable to the context having been passed in as an argument to Run.
   451  //
   452  // If the caller (e.g. "package main") provided a context when it instantiated
   453  // the Meta then the returned context will inherit all of its values, deadlines,
   454  // etc. If the caller did not provide a context then the result is an inert
   455  // background context ready to be passed to other functions.
   456  func (m *Meta) CommandContext() context.Context {
   457  	if m.CallerContext == nil {
   458  		return context.Background()
   459  	}
   460  	// We just return the caller context directly for now, since we don't
   461  	// have anything to add to it.
   462  	return m.CallerContext
   463  }
   464  
   465  // RunOperation executes the given operation on the given backend, blocking
   466  // until that operation completes or is interrupted, and then returns
   467  // the RunningOperation object representing the completed or
   468  // aborted operation that is, despite the name, no longer running.
   469  //
   470  // An error is returned if the operation either fails to start or is cancelled.
   471  // If the operation runs to completion then no error is returned even if the
   472  // operation itself is unsuccessful. Use the "Result" field of the
   473  // returned operation object to recognize operation-level failure.
   474  func (m *Meta) RunOperation(b backend.Enhanced, opReq *backend.Operation) (*backend.RunningOperation, error) {
   475  	if opReq.View == nil {
   476  		panic("RunOperation called with nil View")
   477  	}
   478  	if opReq.ConfigDir != "" {
   479  		opReq.ConfigDir = m.normalizePath(opReq.ConfigDir)
   480  	}
   481  
   482  	op, err := b.Operation(context.Background(), opReq)
   483  	if err != nil {
   484  		return nil, fmt.Errorf("error starting operation: %w", err)
   485  	}
   486  
   487  	// Wait for the operation to complete or an interrupt to occur
   488  	select {
   489  	case <-m.ShutdownCh:
   490  		// gracefully stop the operation
   491  		op.Stop()
   492  
   493  		// Notify the user
   494  		opReq.View.Interrupted()
   495  
   496  		// Still get the result, since there is still one
   497  		select {
   498  		case <-m.ShutdownCh:
   499  			opReq.View.FatalInterrupt()
   500  
   501  			// cancel the operation completely
   502  			op.Cancel()
   503  
   504  			// the operation should return asap
   505  			// but timeout just in case
   506  			select {
   507  			case <-op.Done():
   508  			case <-time.After(5 * time.Second):
   509  			}
   510  
   511  			return nil, errors.New("operation canceled")
   512  
   513  		case <-op.Done():
   514  			// operation completed after Stop
   515  		}
   516  	case <-op.Done():
   517  		// operation completed normally
   518  	}
   519  
   520  	return op, nil
   521  }
   522  
   523  // contextOpts returns the options to use to initialize a OpenTofu
   524  // context with the settings from this Meta.
   525  func (m *Meta) contextOpts() (*tofu.ContextOpts, error) {
   526  	workspace, err := m.Workspace()
   527  	if err != nil {
   528  		return nil, err
   529  	}
   530  
   531  	var opts tofu.ContextOpts
   532  
   533  	opts.UIInput = m.UIInput()
   534  	opts.Parallelism = m.parallelism
   535  
   536  	// If testingOverrides are set, we'll skip the plugin discovery process
   537  	// and just work with what we've been given, thus allowing the tests
   538  	// to provide mock providers and provisioners.
   539  	if m.testingOverrides != nil {
   540  		opts.Providers = m.testingOverrides.Providers
   541  		opts.Provisioners = m.testingOverrides.Provisioners
   542  	} else {
   543  		var providerFactories map[addrs.Provider]providers.Factory
   544  		providerFactories, err = m.providerFactories()
   545  		opts.Providers = providerFactories
   546  		opts.Provisioners = m.provisionerFactories()
   547  	}
   548  
   549  	opts.Meta = &tofu.ContextMeta{
   550  		Env:                workspace,
   551  		OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
   552  	}
   553  
   554  	return &opts, err
   555  }
   556  
   557  // defaultFlagSet creates a default flag set for commands.
   558  // See also command/arguments/default.go
   559  func (m *Meta) defaultFlagSet(n string) *flag.FlagSet {
   560  	f := flag.NewFlagSet(n, flag.ContinueOnError)
   561  	f.SetOutput(io.Discard)
   562  
   563  	// Set the default Usage to empty
   564  	f.Usage = func() {}
   565  
   566  	return f
   567  }
   568  
   569  // ignoreRemoteVersionFlagSet add the ignore-remote version flag to suppress
   570  // the error when the configured OpenTofu version on the remote workspace
   571  // does not match the local OpenTofu version.
   572  func (m *Meta) ignoreRemoteVersionFlagSet(n string) *flag.FlagSet {
   573  	f := m.defaultFlagSet(n)
   574  
   575  	f.BoolVar(&m.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local OpenTofu versions are incompatible")
   576  
   577  	return f
   578  }
   579  
   580  // extendedFlagSet adds custom flags that are mostly used by commands
   581  // that are used to run an operation like plan or apply.
   582  func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {
   583  	f := m.defaultFlagSet(n)
   584  
   585  	f.BoolVar(&m.input, "input", true, "input")
   586  	f.Var((*FlagStringSlice)(&m.targetFlags), "target", "resource to target")
   587  	f.BoolVar(&m.compactWarnings, "compact-warnings", false, "use compact warnings")
   588  
   589  	if m.variableArgs.items == nil {
   590  		m.variableArgs = newRawFlags("-var")
   591  	}
   592  	varValues := m.variableArgs.Alias("-var")
   593  	varFiles := m.variableArgs.Alias("-var-file")
   594  	f.Var(varValues, "var", "variables")
   595  	f.Var(varFiles, "var-file", "variable file")
   596  
   597  	// commands that bypass locking will supply their own flag on this var,
   598  	// but set the initial meta value to true as a failsafe.
   599  	m.stateLock = true
   600  
   601  	return f
   602  }
   603  
   604  // process will process any -no-color entries out of the arguments. This
   605  // will potentially modify the args in-place. It will return the resulting
   606  // slice, and update the Meta and Ui.
   607  func (m *Meta) process(args []string) []string {
   608  	// We do this so that we retain the ability to technically call
   609  	// process multiple times, even if we have no plans to do so
   610  	if m.oldUi != nil {
   611  		m.Ui = m.oldUi
   612  	}
   613  
   614  	// Set colorization
   615  	m.color = m.Color
   616  	i := 0 // output index
   617  	for _, v := range args {
   618  		if v == "-no-color" {
   619  			m.color = false
   620  			m.Color = false
   621  		} else {
   622  			// copy and increment index
   623  			args[i] = v
   624  			i++
   625  		}
   626  	}
   627  	args = args[:i]
   628  
   629  	// Set the UI
   630  	m.oldUi = m.Ui
   631  	m.Ui = &cli.ConcurrentUi{
   632  		Ui: &ColorizeUi{
   633  			Colorize:   m.Colorize(),
   634  			ErrorColor: "[red]",
   635  			WarnColor:  "[yellow]",
   636  			Ui:         m.oldUi,
   637  		},
   638  	}
   639  
   640  	// Reconfigure the view. This is necessary for commands which use both
   641  	// views.View and cli.Ui during the migration phase.
   642  	if m.View != nil {
   643  		m.View.Configure(&arguments.View{
   644  			CompactWarnings: m.compactWarnings,
   645  			NoColor:         !m.Color,
   646  		})
   647  	}
   648  
   649  	return args
   650  }
   651  
   652  // uiHook returns the UiHook to use with the context.
   653  func (m *Meta) uiHook() *views.UiHook {
   654  	return views.NewUiHook(m.View)
   655  }
   656  
   657  // confirm asks a yes/no confirmation.
   658  func (m *Meta) confirm(opts *tofu.InputOpts) (bool, error) {
   659  	if !m.Input() {
   660  		return false, errors.New("input is disabled")
   661  	}
   662  
   663  	for i := 0; i < 2; i++ {
   664  		v, err := m.UIInput().Input(context.Background(), opts)
   665  		if err != nil {
   666  			return false, fmt.Errorf(
   667  				"Error asking for confirmation: %w", err)
   668  		}
   669  
   670  		switch strings.ToLower(v) {
   671  		case "no":
   672  			return false, nil
   673  		case "yes":
   674  			return true, nil
   675  		}
   676  	}
   677  	return false, nil
   678  }
   679  
   680  // showDiagnostics displays error and warning messages in the UI.
   681  //
   682  // "Diagnostics" here means the Diagnostics type from the tfdiag package,
   683  // though as a convenience this function accepts anything that could be
   684  // passed to the "Append" method on that type, converting it to Diagnostics
   685  // before displaying it.
   686  //
   687  // Internally this function uses Diagnostics.Append, and so it will panic
   688  // if given unsupported value types, just as Append does.
   689  func (m *Meta) showDiagnostics(vals ...interface{}) {
   690  	var diags tfdiags.Diagnostics
   691  	diags = diags.Append(vals...)
   692  	diags.Sort()
   693  
   694  	if len(diags) == 0 {
   695  		return
   696  	}
   697  
   698  	if m.outputInJSON {
   699  		jsonView := views.NewJSONView(m.View)
   700  		jsonView.Diagnostics(diags)
   701  		return
   702  	}
   703  
   704  	outputWidth := m.ErrorColumns()
   705  
   706  	diags = diags.ConsolidateWarnings(1)
   707  
   708  	// Since warning messages are generally competing
   709  	if m.compactWarnings {
   710  		// If the user selected compact warnings and all of the diagnostics are
   711  		// warnings then we'll use a more compact representation of the warnings
   712  		// that only includes their summaries.
   713  		// We show full warnings if there are also errors, because a warning
   714  		// can sometimes serve as good context for a subsequent error.
   715  		useCompact := true
   716  		for _, diag := range diags {
   717  			if diag.Severity() != tfdiags.Warning {
   718  				useCompact = false
   719  				break
   720  			}
   721  		}
   722  		if useCompact {
   723  			msg := format.DiagnosticWarningsCompact(diags, m.Colorize())
   724  			msg = "\n" + msg + "\nTo see the full warning notes, run OpenTofu without -compact-warnings.\n"
   725  			m.Ui.Warn(msg)
   726  			return
   727  		}
   728  	}
   729  
   730  	for _, diag := range diags {
   731  		var msg string
   732  		if m.Color {
   733  			msg = format.Diagnostic(diag, m.configSources(), m.Colorize(), outputWidth)
   734  		} else {
   735  			msg = format.DiagnosticPlain(diag, m.configSources(), outputWidth)
   736  		}
   737  
   738  		switch diag.Severity() {
   739  		case tfdiags.Error:
   740  			m.Ui.Error(msg)
   741  		case tfdiags.Warning:
   742  			m.Ui.Warn(msg)
   743  		default:
   744  			m.Ui.Output(msg)
   745  		}
   746  	}
   747  }
   748  
   749  // WorkspaceNameEnvVar is the name of the environment variable that can be used
   750  // to set the name of the OpenTofu workspace, overriding the workspace chosen
   751  // by `tofu workspace select`.
   752  //
   753  // Note that this environment variable is ignored by `tofu workspace new`
   754  // and `tofu workspace delete`.
   755  const WorkspaceNameEnvVar = "TF_WORKSPACE"
   756  
   757  var errInvalidWorkspaceNameEnvVar = fmt.Errorf("Invalid workspace name set using %s", WorkspaceNameEnvVar)
   758  
   759  // Workspace returns the name of the currently configured workspace, corresponding
   760  // to the desired named state.
   761  func (m *Meta) Workspace() (string, error) {
   762  	current, overridden := m.WorkspaceOverridden()
   763  	if overridden && !validWorkspaceName(current) {
   764  		return "", errInvalidWorkspaceNameEnvVar
   765  	}
   766  	return current, nil
   767  }
   768  
   769  // WorkspaceOverridden returns the name of the currently configured workspace,
   770  // corresponding to the desired named state, as well as a bool saying whether
   771  // this was set via the TF_WORKSPACE environment variable.
   772  func (m *Meta) WorkspaceOverridden() (string, bool) {
   773  	if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
   774  		return envVar, true
   775  	}
   776  
   777  	envData, err := os.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile))
   778  	current := string(bytes.TrimSpace(envData))
   779  	if current == "" {
   780  		current = backend.DefaultStateName
   781  	}
   782  
   783  	if err != nil && !os.IsNotExist(err) {
   784  		// always return the default if we can't get a workspace name
   785  		log.Printf("[ERROR] failed to read current workspace: %s", err)
   786  	}
   787  
   788  	return current, false
   789  }
   790  
   791  // SetWorkspace saves the given name as the current workspace in the local
   792  // filesystem.
   793  func (m *Meta) SetWorkspace(name string) error {
   794  	err := os.MkdirAll(m.DataDir(), 0755)
   795  	if err != nil {
   796  		return err
   797  	}
   798  
   799  	err = os.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
   800  	if err != nil {
   801  		return err
   802  	}
   803  	return nil
   804  }
   805  
   806  // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
   807  func isAutoVarFile(path string) bool {
   808  	return strings.HasSuffix(path, ".auto.tfvars") ||
   809  		strings.HasSuffix(path, ".auto.tfvars.json")
   810  }
   811  
   812  // FIXME: as an interim refactoring step, we apply the contents of the state
   813  // arguments directly to the Meta object. Future work would ideally update the
   814  // code paths which use these arguments to be passed them directly for clarity.
   815  func (m *Meta) applyStateArguments(args *arguments.State) {
   816  	m.stateLock = args.Lock
   817  	m.stateLockTimeout = args.LockTimeout
   818  	m.statePath = args.StatePath
   819  	m.stateOutPath = args.StateOutPath
   820  	m.backupPath = args.BackupPath
   821  }
   822  
   823  // checkRequiredVersion loads the config and check if the
   824  // core version requirements are satisfied.
   825  func (m *Meta) checkRequiredVersion() tfdiags.Diagnostics {
   826  	var diags tfdiags.Diagnostics
   827  
   828  	loader, err := m.initConfigLoader()
   829  	if err != nil {
   830  		diags = diags.Append(err)
   831  		return diags
   832  	}
   833  
   834  	pwd, err := os.Getwd()
   835  	if err != nil {
   836  		diags = diags.Append(fmt.Errorf("Error getting pwd: %w", err))
   837  		return diags
   838  	}
   839  
   840  	config, configDiags := loader.LoadConfig(pwd)
   841  	if configDiags.HasErrors() {
   842  		diags = diags.Append(configDiags)
   843  		return diags
   844  	}
   845  
   846  	versionDiags := tofu.CheckCoreVersionRequirements(config)
   847  	if versionDiags.HasErrors() {
   848  		diags = diags.Append(versionDiags)
   849  		return diags
   850  	}
   851  
   852  	return nil
   853  }
   854  
   855  // MaybeGetSchemas attempts to load and return the schemas
   856  // If there is not enough information to return the schemas,
   857  // it could potentially return nil without errors. It is the
   858  // responsibility of the caller to handle the lack of schema
   859  // information accordingly
   860  func (c *Meta) MaybeGetSchemas(state *states.State, config *configs.Config) (*tofu.Schemas, tfdiags.Diagnostics) {
   861  	var diags tfdiags.Diagnostics
   862  
   863  	path, err := os.Getwd()
   864  	if err != nil {
   865  		diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
   866  		return nil, diags
   867  	}
   868  
   869  	if config == nil {
   870  		config, diags = c.loadConfig(path)
   871  		if diags.HasErrors() {
   872  			diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
   873  			return nil, diags
   874  		}
   875  	}
   876  
   877  	if config != nil || state != nil {
   878  		opts, err := c.contextOpts()
   879  		if err != nil {
   880  			diags = diags.Append(err)
   881  			return nil, diags
   882  		}
   883  		tfCtx, ctxDiags := tofu.NewContext(opts)
   884  		diags = diags.Append(ctxDiags)
   885  		if ctxDiags.HasErrors() {
   886  			return nil, diags
   887  		}
   888  		var schemaDiags tfdiags.Diagnostics
   889  		schemas, schemaDiags := tfCtx.Schemas(config, state)
   890  		diags = diags.Append(schemaDiags)
   891  		if schemaDiags.HasErrors() {
   892  			return nil, diags
   893  		}
   894  		return schemas, diags
   895  
   896  	}
   897  	return nil, diags
   898  }