github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/meta.go (about)

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