github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/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/cycloidio/terraform/addrs"
    23  	"github.com/cycloidio/terraform/backend"
    24  	"github.com/cycloidio/terraform/backend/local"
    25  	"github.com/cycloidio/terraform/command/arguments"
    26  	"github.com/cycloidio/terraform/command/format"
    27  	"github.com/cycloidio/terraform/command/views"
    28  	"github.com/cycloidio/terraform/command/webbrowser"
    29  	"github.com/cycloidio/terraform/command/workdir"
    30  	"github.com/cycloidio/terraform/configs/configload"
    31  	"github.com/cycloidio/terraform/getproviders"
    32  	legacy "github.com/cycloidio/terraform/legacy/terraform"
    33  	"github.com/cycloidio/terraform/providers"
    34  	"github.com/cycloidio/terraform/provisioners"
    35  	"github.com/cycloidio/terraform/terminal"
    36  	"github.com/cycloidio/terraform/terraform"
    37  	"github.com/cycloidio/terraform/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  		var providerFactories map[addrs.Provider]providers.Factory
   463  		providerFactories, err = m.providerFactories()
   464  		opts.Providers = providerFactories
   465  		opts.Provisioners = m.provisionerFactories()
   466  	}
   467  
   468  	opts.Meta = &terraform.ContextMeta{
   469  		Env:                workspace,
   470  		OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
   471  	}
   472  
   473  	return &opts, err
   474  }
   475  
   476  // defaultFlagSet creates a default flag set for commands.
   477  // See also command/arguments/default.go
   478  func (m *Meta) defaultFlagSet(n string) *flag.FlagSet {
   479  	f := flag.NewFlagSet(n, flag.ContinueOnError)
   480  	f.SetOutput(ioutil.Discard)
   481  
   482  	// Set the default Usage to empty
   483  	f.Usage = func() {}
   484  
   485  	return f
   486  }
   487  
   488  // ignoreRemoteVersionFlagSet add the ignore-remote version flag to suppress
   489  // the error when the configured Terraform version on the remote workspace
   490  // does not match the local Terraform version.
   491  func (m *Meta) ignoreRemoteVersionFlagSet(n string) *flag.FlagSet {
   492  	f := m.defaultFlagSet(n)
   493  
   494  	f.BoolVar(&m.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
   495  
   496  	return f
   497  }
   498  
   499  // extendedFlagSet adds custom flags that are mostly used by commands
   500  // that are used to run an operation like plan or apply.
   501  func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {
   502  	f := m.defaultFlagSet(n)
   503  
   504  	f.BoolVar(&m.input, "input", true, "input")
   505  	f.Var((*FlagStringSlice)(&m.targetFlags), "target", "resource to target")
   506  	f.BoolVar(&m.compactWarnings, "compact-warnings", false, "use compact warnings")
   507  
   508  	if m.variableArgs.items == nil {
   509  		m.variableArgs = newRawFlags("-var")
   510  	}
   511  	varValues := m.variableArgs.Alias("-var")
   512  	varFiles := m.variableArgs.Alias("-var-file")
   513  	f.Var(varValues, "var", "variables")
   514  	f.Var(varFiles, "var-file", "variable file")
   515  
   516  	// commands that bypass locking will supply their own flag on this var,
   517  	// but set the initial meta value to true as a failsafe.
   518  	m.stateLock = true
   519  
   520  	return f
   521  }
   522  
   523  // process will process any -no-color entries out of the arguments. This
   524  // will potentially modify the args in-place. It will return the resulting
   525  // slice, and update the Meta and Ui.
   526  func (m *Meta) process(args []string) []string {
   527  	// We do this so that we retain the ability to technically call
   528  	// process multiple times, even if we have no plans to do so
   529  	if m.oldUi != nil {
   530  		m.Ui = m.oldUi
   531  	}
   532  
   533  	// Set colorization
   534  	m.color = m.Color
   535  	i := 0 // output index
   536  	for _, v := range args {
   537  		if v == "-no-color" {
   538  			m.color = false
   539  			m.Color = false
   540  		} else {
   541  			// copy and increment index
   542  			args[i] = v
   543  			i++
   544  		}
   545  	}
   546  	args = args[:i]
   547  
   548  	// Set the UI
   549  	m.oldUi = m.Ui
   550  	m.Ui = &cli.ConcurrentUi{
   551  		Ui: &ColorizeUi{
   552  			Colorize:   m.Colorize(),
   553  			ErrorColor: "[red]",
   554  			WarnColor:  "[yellow]",
   555  			Ui:         m.oldUi,
   556  		},
   557  	}
   558  
   559  	// Reconfigure the view. This is necessary for commands which use both
   560  	// views.View and cli.Ui during the migration phase.
   561  	if m.View != nil {
   562  		m.View.Configure(&arguments.View{
   563  			CompactWarnings: m.compactWarnings,
   564  			NoColor:         !m.Color,
   565  		})
   566  	}
   567  
   568  	return args
   569  }
   570  
   571  // uiHook returns the UiHook to use with the context.
   572  func (m *Meta) uiHook() *views.UiHook {
   573  	return views.NewUiHook(m.View)
   574  }
   575  
   576  // confirm asks a yes/no confirmation.
   577  func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
   578  	if !m.Input() {
   579  		return false, errors.New("input is disabled")
   580  	}
   581  
   582  	for i := 0; i < 2; i++ {
   583  		v, err := m.UIInput().Input(context.Background(), opts)
   584  		if err != nil {
   585  			return false, fmt.Errorf(
   586  				"Error asking for confirmation: %s", err)
   587  		}
   588  
   589  		switch strings.ToLower(v) {
   590  		case "no":
   591  			return false, nil
   592  		case "yes":
   593  			return true, nil
   594  		}
   595  	}
   596  	return false, nil
   597  }
   598  
   599  // showDiagnostics displays error and warning messages in the UI.
   600  //
   601  // "Diagnostics" here means the Diagnostics type from the tfdiag package,
   602  // though as a convenience this function accepts anything that could be
   603  // passed to the "Append" method on that type, converting it to Diagnostics
   604  // before displaying it.
   605  //
   606  // Internally this function uses Diagnostics.Append, and so it will panic
   607  // if given unsupported value types, just as Append does.
   608  func (m *Meta) showDiagnostics(vals ...interface{}) {
   609  	var diags tfdiags.Diagnostics
   610  	diags = diags.Append(vals...)
   611  	diags.Sort()
   612  
   613  	if len(diags) == 0 {
   614  		return
   615  	}
   616  
   617  	outputWidth := m.ErrorColumns()
   618  
   619  	diags = diags.ConsolidateWarnings(1)
   620  
   621  	// Since warning messages are generally competing
   622  	if m.compactWarnings {
   623  		// If the user selected compact warnings and all of the diagnostics are
   624  		// warnings then we'll use a more compact representation of the warnings
   625  		// that only includes their summaries.
   626  		// We show full warnings if there are also errors, because a warning
   627  		// can sometimes serve as good context for a subsequent error.
   628  		useCompact := true
   629  		for _, diag := range diags {
   630  			if diag.Severity() != tfdiags.Warning {
   631  				useCompact = false
   632  				break
   633  			}
   634  		}
   635  		if useCompact {
   636  			msg := format.DiagnosticWarningsCompact(diags, m.Colorize())
   637  			msg = "\n" + msg + "\nTo see the full warning notes, run Terraform without -compact-warnings.\n"
   638  			m.Ui.Warn(msg)
   639  			return
   640  		}
   641  	}
   642  
   643  	for _, diag := range diags {
   644  		var msg string
   645  		if m.Color {
   646  			msg = format.Diagnostic(diag, m.configSources(), m.Colorize(), outputWidth)
   647  		} else {
   648  			msg = format.DiagnosticPlain(diag, m.configSources(), outputWidth)
   649  		}
   650  
   651  		switch diag.Severity() {
   652  		case tfdiags.Error:
   653  			m.Ui.Error(msg)
   654  		case tfdiags.Warning:
   655  			m.Ui.Warn(msg)
   656  		default:
   657  			m.Ui.Output(msg)
   658  		}
   659  	}
   660  }
   661  
   662  // WorkspaceNameEnvVar is the name of the environment variable that can be used
   663  // to set the name of the Terraform workspace, overriding the workspace chosen
   664  // by `terraform workspace select`.
   665  //
   666  // Note that this environment variable is ignored by `terraform workspace new`
   667  // and `terraform workspace delete`.
   668  const WorkspaceNameEnvVar = "TF_WORKSPACE"
   669  
   670  var errInvalidWorkspaceNameEnvVar = fmt.Errorf("Invalid workspace name set using %s", WorkspaceNameEnvVar)
   671  
   672  // Workspace returns the name of the currently configured workspace, corresponding
   673  // to the desired named state.
   674  func (m *Meta) Workspace() (string, error) {
   675  	current, overridden := m.WorkspaceOverridden()
   676  	if overridden && !validWorkspaceName(current) {
   677  		return "", errInvalidWorkspaceNameEnvVar
   678  	}
   679  	return current, nil
   680  }
   681  
   682  // WorkspaceOverridden returns the name of the currently configured workspace,
   683  // corresponding to the desired named state, as well as a bool saying whether
   684  // this was set via the TF_WORKSPACE environment variable.
   685  func (m *Meta) WorkspaceOverridden() (string, bool) {
   686  	if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
   687  		return envVar, true
   688  	}
   689  
   690  	envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile))
   691  	current := string(bytes.TrimSpace(envData))
   692  	if current == "" {
   693  		current = backend.DefaultStateName
   694  	}
   695  
   696  	if err != nil && !os.IsNotExist(err) {
   697  		// always return the default if we can't get a workspace name
   698  		log.Printf("[ERROR] failed to read current workspace: %s", err)
   699  	}
   700  
   701  	return current, false
   702  }
   703  
   704  // SetWorkspace saves the given name as the current workspace in the local
   705  // filesystem.
   706  func (m *Meta) SetWorkspace(name string) error {
   707  	err := os.MkdirAll(m.DataDir(), 0755)
   708  	if err != nil {
   709  		return err
   710  	}
   711  
   712  	err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
   713  	if err != nil {
   714  		return err
   715  	}
   716  	return nil
   717  }
   718  
   719  // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
   720  func isAutoVarFile(path string) bool {
   721  	return strings.HasSuffix(path, ".auto.tfvars") ||
   722  		strings.HasSuffix(path, ".auto.tfvars.json")
   723  }
   724  
   725  // FIXME: as an interim refactoring step, we apply the contents of the state
   726  // arguments directly to the Meta object. Future work would ideally update the
   727  // code paths which use these arguments to be passed them directly for clarity.
   728  func (m *Meta) applyStateArguments(args *arguments.State) {
   729  	m.stateLock = args.Lock
   730  	m.stateLockTimeout = args.LockTimeout
   731  	m.statePath = args.StatePath
   732  	m.stateOutPath = args.StateOutPath
   733  	m.backupPath = args.BackupPath
   734  }