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