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