github.com/robertreppel/terraform@v0.11.8-0.20180412164320-05291ab9822d/command/meta.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"context"
     7  	"errors"
     8  	"flag"
     9  	"fmt"
    10  	"io"
    11  	"io/ioutil"
    12  	"log"
    13  	"os"
    14  	"path/filepath"
    15  	"sort"
    16  	"strconv"
    17  	"strings"
    18  	"time"
    19  
    20  	"github.com/hashicorp/terraform/backend"
    21  	"github.com/hashicorp/terraform/backend/local"
    22  	"github.com/hashicorp/terraform/command/format"
    23  	"github.com/hashicorp/terraform/config"
    24  	"github.com/hashicorp/terraform/config/module"
    25  	"github.com/hashicorp/terraform/helper/experiment"
    26  	"github.com/hashicorp/terraform/helper/variables"
    27  	"github.com/hashicorp/terraform/helper/wrappedstreams"
    28  	"github.com/hashicorp/terraform/svchost/auth"
    29  	"github.com/hashicorp/terraform/svchost/disco"
    30  	"github.com/hashicorp/terraform/terraform"
    31  	"github.com/hashicorp/terraform/tfdiags"
    32  	"github.com/mitchellh/cli"
    33  	"github.com/mitchellh/colorstring"
    34  )
    35  
    36  // Meta are the meta-options that are available on all or most commands.
    37  type Meta struct {
    38  	// The exported fields below should be set by anyone using a
    39  	// command with a Meta field. These are expected to be set externally
    40  	// (not from within the command itself).
    41  
    42  	Color            bool             // True if output should be colored
    43  	GlobalPluginDirs []string         // Additional paths to search for plugins
    44  	PluginOverrides  *PluginOverrides // legacy overrides from .terraformrc file
    45  	Ui               cli.Ui           // Ui for output
    46  
    47  	// ExtraHooks are extra hooks to add to the context.
    48  	ExtraHooks []terraform.Hook
    49  
    50  	// Services provides access to remote endpoint information for
    51  	// "terraform-native' services running at a specific user-facing hostname.
    52  	Services *disco.Disco
    53  
    54  	// Credentials provides access to credentials for "terraform-native"
    55  	// services, which are accessed by a service hostname.
    56  	Credentials auth.CredentialsSource
    57  
    58  	// RunningInAutomation indicates that commands are being run by an
    59  	// automated system rather than directly at a command prompt.
    60  	//
    61  	// This is a hint to various command routines that it may be confusing
    62  	// to print out messages that suggest running specific follow-up
    63  	// commands, since the user consuming the output will not be
    64  	// in a position to run such commands.
    65  	//
    66  	// The intended use-case of this flag is when Terraform is running in
    67  	// some sort of workflow orchestration tool which is abstracting away
    68  	// the specific commands being run.
    69  	RunningInAutomation bool
    70  
    71  	// PluginCacheDir, if non-empty, enables caching of downloaded plugins
    72  	// into the given directory.
    73  	PluginCacheDir string
    74  
    75  	// OverrideDataDir, if non-empty, overrides the return value of the
    76  	// DataDir method for situations where the local .terraform/ directory
    77  	// is not suitable, e.g. because of a read-only filesystem.
    78  	OverrideDataDir string
    79  
    80  	// When this channel is closed, the command will be cancelled.
    81  	ShutdownCh <-chan struct{}
    82  
    83  	//----------------------------------------------------------
    84  	// Protected: commands can set these
    85  	//----------------------------------------------------------
    86  
    87  	// Modify the data directory location. This should be accessed through the
    88  	// DataDir method.
    89  	dataDir string
    90  
    91  	// pluginPath is a user defined set of directories to look for plugins.
    92  	// This is set during init with the `-plugin-dir` flag, saved to a file in
    93  	// the data directory.
    94  	// This overrides all other search paths when discovering plugins.
    95  	pluginPath []string
    96  
    97  	ignorePluginChecksum bool
    98  
    99  	// Override certain behavior for tests within this package
   100  	testingOverrides *testingOverrides
   101  
   102  	//----------------------------------------------------------
   103  	// Private: do not set these
   104  	//----------------------------------------------------------
   105  
   106  	// backendState is the currently active backend state
   107  	backendState *terraform.BackendState
   108  
   109  	// Variables for the context (private)
   110  	autoKey       string
   111  	autoVariables map[string]interface{}
   112  	input         bool
   113  	variables     map[string]interface{}
   114  
   115  	// Targets for this context (private)
   116  	targets []string
   117  
   118  	// Internal fields
   119  	color bool
   120  	oldUi cli.Ui
   121  
   122  	// The fields below are expected to be set by the command via
   123  	// command line flags. See the Apply command for an example.
   124  	//
   125  	// statePath is the path to the state file. If this is empty, then
   126  	// no state will be loaded. It is also okay for this to be a path to
   127  	// a file that doesn't exist; it is assumed that this means that there
   128  	// is simply no state.
   129  	//
   130  	// stateOutPath is used to override the output path for the state.
   131  	// If not provided, the StatePath is used causing the old state to
   132  	// be overriden.
   133  	//
   134  	// backupPath is used to backup the state file before writing a modified
   135  	// version. It defaults to stateOutPath + DefaultBackupExtension
   136  	//
   137  	// parallelism is used to control the number of concurrent operations
   138  	// allowed when walking the graph
   139  	//
   140  	// shadow is used to enable/disable the shadow graph
   141  	//
   142  	// provider is to specify specific resource providers
   143  	//
   144  	// stateLock is set to false to disable state locking
   145  	//
   146  	// stateLockTimeout is the optional duration to retry a state locks locks
   147  	// when it is already locked by another process.
   148  	//
   149  	// forceInitCopy suppresses confirmation for copying state data during
   150  	// init.
   151  	//
   152  	// reconfigure forces init to ignore any stored configuration.
   153  	statePath        string
   154  	stateOutPath     string
   155  	backupPath       string
   156  	parallelism      int
   157  	shadow           bool
   158  	provider         string
   159  	stateLock        bool
   160  	stateLockTimeout time.Duration
   161  	forceInitCopy    bool
   162  	reconfigure      bool
   163  
   164  	// Used with the import command to allow import of state when no matching config exists.
   165  	allowMissingConfig bool
   166  }
   167  
   168  type PluginOverrides struct {
   169  	Providers    map[string]string
   170  	Provisioners map[string]string
   171  }
   172  
   173  type testingOverrides struct {
   174  	ProviderResolver terraform.ResourceProviderResolver
   175  	Provisioners     map[string]terraform.ResourceProvisionerFactory
   176  }
   177  
   178  // initStatePaths is used to initialize the default values for
   179  // statePath, stateOutPath, and backupPath
   180  func (m *Meta) initStatePaths() {
   181  	if m.statePath == "" {
   182  		m.statePath = DefaultStateFilename
   183  	}
   184  	if m.stateOutPath == "" {
   185  		m.stateOutPath = m.statePath
   186  	}
   187  	if m.backupPath == "" {
   188  		m.backupPath = m.stateOutPath + DefaultBackupExtension
   189  	}
   190  }
   191  
   192  // StateOutPath returns the true output path for the state file
   193  func (m *Meta) StateOutPath() string {
   194  	return m.stateOutPath
   195  }
   196  
   197  // Colorize returns the colorization structure for a command.
   198  func (m *Meta) Colorize() *colorstring.Colorize {
   199  	return &colorstring.Colorize{
   200  		Colors:  colorstring.DefaultColors,
   201  		Disable: !m.color,
   202  		Reset:   true,
   203  	}
   204  }
   205  
   206  // DataDir returns the directory where local data will be stored.
   207  // Defaults to DefaultDataDir in the current working directory.
   208  func (m *Meta) DataDir() string {
   209  	if m.OverrideDataDir != "" {
   210  		return m.OverrideDataDir
   211  	}
   212  	return DefaultDataDir
   213  }
   214  
   215  const (
   216  	// InputModeEnvVar is the environment variable that, if set to "false" or
   217  	// "0", causes terraform commands to behave as if the `-input=false` flag was
   218  	// specified.
   219  	InputModeEnvVar = "TF_INPUT"
   220  )
   221  
   222  // InputMode returns the type of input we should ask for in the form of
   223  // terraform.InputMode which is passed directly to Context.Input.
   224  func (m *Meta) InputMode() terraform.InputMode {
   225  	if test || !m.input {
   226  		return 0
   227  	}
   228  
   229  	if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
   230  		if v, err := strconv.ParseBool(envVar); err == nil {
   231  			if !v {
   232  				return 0
   233  			}
   234  		}
   235  	}
   236  
   237  	var mode terraform.InputMode
   238  	mode |= terraform.InputModeProvider
   239  	mode |= terraform.InputModeVar
   240  	mode |= terraform.InputModeVarUnset
   241  
   242  	return mode
   243  }
   244  
   245  // UIInput returns a UIInput object to be used for asking for input.
   246  func (m *Meta) UIInput() terraform.UIInput {
   247  	return &UIInput{
   248  		Colorize: m.Colorize(),
   249  	}
   250  }
   251  
   252  // StdinPiped returns true if the input is piped.
   253  func (m *Meta) StdinPiped() bool {
   254  	fi, err := wrappedstreams.Stdin().Stat()
   255  	if err != nil {
   256  		// If there is an error, let's just say its not piped
   257  		return false
   258  	}
   259  
   260  	return fi.Mode()&os.ModeNamedPipe != 0
   261  }
   262  
   263  func (m *Meta) RunOperation(b backend.Enhanced, opReq *backend.Operation) (*backend.RunningOperation, error) {
   264  	op, err := b.Operation(context.Background(), opReq)
   265  	if err != nil {
   266  		return nil, fmt.Errorf("error starting operation: %s", err)
   267  	}
   268  
   269  	// Wait for the operation to complete or an interrupt to occur
   270  	select {
   271  	case <-m.ShutdownCh:
   272  		// gracefully stop the operation
   273  		op.Stop()
   274  
   275  		// Notify the user
   276  		m.Ui.Output(outputInterrupt)
   277  
   278  		// Still get the result, since there is still one
   279  		select {
   280  		case <-m.ShutdownCh:
   281  			m.Ui.Error(
   282  				"Two interrupts received. Exiting immediately. Note that data\n" +
   283  					"loss may have occurred.")
   284  
   285  			// cancel the operation completely
   286  			op.Cancel()
   287  
   288  			// the operation should return asap
   289  			// but timeout just in case
   290  			select {
   291  			case <-op.Done():
   292  			case <-time.After(5 * time.Second):
   293  			}
   294  
   295  			return nil, errors.New("operation canceled")
   296  
   297  		case <-op.Done():
   298  			// operation completed after Stop
   299  		}
   300  	case <-op.Done():
   301  		// operation completed normally
   302  	}
   303  
   304  	if op.Err != nil {
   305  		return op, op.Err
   306  	}
   307  
   308  	return op, nil
   309  }
   310  
   311  const (
   312  	ProviderSkipVerifyEnvVar = "TF_SKIP_PROVIDER_VERIFY"
   313  )
   314  
   315  // contextOpts returns the options to use to initialize a Terraform
   316  // context with the settings from this Meta.
   317  func (m *Meta) contextOpts() *terraform.ContextOpts {
   318  	var opts terraform.ContextOpts
   319  	opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}}
   320  	opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
   321  
   322  	vs := make(map[string]interface{})
   323  	for k, v := range opts.Variables {
   324  		vs[k] = v
   325  	}
   326  	for k, v := range m.autoVariables {
   327  		vs[k] = v
   328  	}
   329  	for k, v := range m.variables {
   330  		vs[k] = v
   331  	}
   332  	opts.Variables = vs
   333  
   334  	opts.Targets = m.targets
   335  	opts.UIInput = m.UIInput()
   336  	opts.Parallelism = m.parallelism
   337  	opts.Shadow = m.shadow
   338  
   339  	// If testingOverrides are set, we'll skip the plugin discovery process
   340  	// and just work with what we've been given, thus allowing the tests
   341  	// to provide mock providers and provisioners.
   342  	if m.testingOverrides != nil {
   343  		opts.ProviderResolver = m.testingOverrides.ProviderResolver
   344  		opts.Provisioners = m.testingOverrides.Provisioners
   345  	} else {
   346  		opts.ProviderResolver = m.providerResolver()
   347  		opts.Provisioners = m.provisionerFactories()
   348  	}
   349  
   350  	opts.ProviderSHA256s = m.providerPluginsLock().Read()
   351  	if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" {
   352  		opts.SkipProviderVerify = true
   353  	}
   354  
   355  	opts.Meta = &terraform.ContextMeta{
   356  		Env: m.Workspace(),
   357  	}
   358  
   359  	return &opts
   360  }
   361  
   362  // flags adds the meta flags to the given FlagSet.
   363  func (m *Meta) flagSet(n string) *flag.FlagSet {
   364  	f := flag.NewFlagSet(n, flag.ContinueOnError)
   365  	f.BoolVar(&m.input, "input", true, "input")
   366  	f.Var((*variables.Flag)(&m.variables), "var", "variables")
   367  	f.Var((*variables.FlagFile)(&m.variables), "var-file", "variable file")
   368  	f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target")
   369  
   370  	if m.autoKey != "" {
   371  		f.Var((*variables.FlagFile)(&m.autoVariables), m.autoKey, "variable file")
   372  	}
   373  
   374  	// Advanced (don't need documentation, or unlikely to be set)
   375  	f.BoolVar(&m.shadow, "shadow", true, "shadow graph")
   376  
   377  	// Experimental features
   378  	experiment.Flag(f)
   379  
   380  	// Create an io.Writer that writes to our Ui properly for errors.
   381  	// This is kind of a hack, but it does the job. Basically: create
   382  	// a pipe, use a scanner to break it into lines, and output each line
   383  	// to the UI. Do this forever.
   384  	errR, errW := io.Pipe()
   385  	errScanner := bufio.NewScanner(errR)
   386  	go func() {
   387  		// This only needs to be alive long enough to write the help info if
   388  		// there is a flag error. Kill the scanner after a short duriation to
   389  		// prevent these from accumulating during tests, and cluttering up the
   390  		// stack traces.
   391  		time.AfterFunc(2*time.Second, func() {
   392  			errW.Close()
   393  		})
   394  		for errScanner.Scan() {
   395  			m.Ui.Error(errScanner.Text())
   396  		}
   397  	}()
   398  	f.SetOutput(errW)
   399  
   400  	// Set the default Usage to empty
   401  	f.Usage = func() {}
   402  
   403  	// command that bypass locking will supply their own flag on this var, but
   404  	// set the initial meta value to true as a failsafe.
   405  	m.stateLock = true
   406  
   407  	return f
   408  }
   409  
   410  // moduleStorage returns the module.Storage implementation used to store
   411  // modules for commands.
   412  func (m *Meta) moduleStorage(root string, mode module.GetMode) *module.Storage {
   413  	s := module.NewStorage(filepath.Join(root, "modules"), m.Services, m.Credentials)
   414  	s.Ui = m.Ui
   415  	s.Mode = mode
   416  	return s
   417  }
   418  
   419  // process will process the meta-parameters out of the arguments. This
   420  // will potentially modify the args in-place. It will return the resulting
   421  // slice.
   422  //
   423  // vars says whether or not we support variables.
   424  func (m *Meta) process(args []string, vars bool) ([]string, error) {
   425  	// We do this so that we retain the ability to technically call
   426  	// process multiple times, even if we have no plans to do so
   427  	if m.oldUi != nil {
   428  		m.Ui = m.oldUi
   429  	}
   430  
   431  	// Set colorization
   432  	m.color = m.Color
   433  	for i, v := range args {
   434  		if v == "-no-color" {
   435  			m.color = false
   436  			m.Color = false
   437  			args = append(args[:i], args[i+1:]...)
   438  			break
   439  		}
   440  	}
   441  
   442  	// Set the UI
   443  	m.oldUi = m.Ui
   444  	m.Ui = &cli.ConcurrentUi{
   445  		Ui: &ColorizeUi{
   446  			Colorize:   m.Colorize(),
   447  			ErrorColor: "[red]",
   448  			WarnColor:  "[yellow]",
   449  			Ui:         m.oldUi,
   450  		},
   451  	}
   452  
   453  	// If we support vars and the default var file exists, add it to
   454  	// the args...
   455  	m.autoKey = ""
   456  	if vars {
   457  		var preArgs []string
   458  
   459  		if _, err := os.Stat(DefaultVarsFilename); err == nil {
   460  			m.autoKey = "var-file-default"
   461  			preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename)
   462  		}
   463  
   464  		if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil {
   465  			m.autoKey = "var-file-default"
   466  			preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename+".json")
   467  		}
   468  
   469  		wd, err := os.Getwd()
   470  		if err != nil {
   471  			return nil, err
   472  		}
   473  
   474  		fis, err := ioutil.ReadDir(wd)
   475  		if err != nil {
   476  			return nil, err
   477  		}
   478  
   479  		// make sure we add the files in order
   480  		sort.Slice(fis, func(i, j int) bool {
   481  			return fis[i].Name() < fis[j].Name()
   482  		})
   483  
   484  		for _, fi := range fis {
   485  			name := fi.Name()
   486  			// Ignore directories, non-var-files, and ignored files
   487  			if fi.IsDir() || !isAutoVarFile(name) || config.IsIgnoredFile(name) {
   488  				continue
   489  			}
   490  
   491  			m.autoKey = "var-file-default"
   492  			preArgs = append(preArgs, "-"+m.autoKey, name)
   493  		}
   494  
   495  		args = append(preArgs, args...)
   496  	}
   497  
   498  	return args, nil
   499  }
   500  
   501  // uiHook returns the UiHook to use with the context.
   502  func (m *Meta) uiHook() *UiHook {
   503  	return &UiHook{
   504  		Colorize: m.Colorize(),
   505  		Ui:       m.Ui,
   506  	}
   507  }
   508  
   509  // confirm asks a yes/no confirmation.
   510  func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
   511  	if !m.Input() {
   512  		return false, errors.New("input is disabled")
   513  	}
   514  
   515  	for i := 0; i < 2; i++ {
   516  		v, err := m.UIInput().Input(opts)
   517  		if err != nil {
   518  			return false, fmt.Errorf(
   519  				"Error asking for confirmation: %s", err)
   520  		}
   521  
   522  		switch strings.ToLower(v) {
   523  		case "no":
   524  			return false, nil
   525  		case "yes":
   526  			return true, nil
   527  		}
   528  	}
   529  	return false, nil
   530  }
   531  
   532  // showDiagnostics displays error and warning messages in the UI.
   533  //
   534  // "Diagnostics" here means the Diagnostics type from the tfdiag package,
   535  // though as a convenience this function accepts anything that could be
   536  // passed to the "Append" method on that type, converting it to Diagnostics
   537  // before displaying it.
   538  //
   539  // Internally this function uses Diagnostics.Append, and so it will panic
   540  // if given unsupported value types, just as Append does.
   541  func (m *Meta) showDiagnostics(vals ...interface{}) {
   542  	var diags tfdiags.Diagnostics
   543  	diags = diags.Append(vals...)
   544  
   545  	for _, diag := range diags {
   546  		// TODO: Actually measure the terminal width and pass it here.
   547  		// For now, we don't have easy access to the writer that
   548  		// ui.Error (etc) are writing to and thus can't interrogate
   549  		// to see if it's a terminal and what size it is.
   550  		msg := format.Diagnostic(diag, m.Colorize(), 78)
   551  		switch diag.Severity() {
   552  		case tfdiags.Error:
   553  			m.Ui.Error(msg)
   554  		case tfdiags.Warning:
   555  			m.Ui.Warn(msg)
   556  		default:
   557  			m.Ui.Output(msg)
   558  		}
   559  	}
   560  }
   561  
   562  const (
   563  	// ModuleDepthDefault is the default value for
   564  	// module depth, which can be overridden by flag
   565  	// or env var
   566  	ModuleDepthDefault = -1
   567  
   568  	// ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth.
   569  	ModuleDepthEnvVar = "TF_MODULE_DEPTH"
   570  )
   571  
   572  func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) {
   573  	flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth")
   574  	if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" {
   575  		if md, err := strconv.Atoi(envVar); err == nil {
   576  			*moduleDepth = md
   577  		}
   578  	}
   579  }
   580  
   581  // outputShadowError outputs the error from ctx.ShadowError. If the
   582  // error is nil then nothing happens. If output is false then it isn't
   583  // outputted to the user (you can define logic to guard against outputting).
   584  func (m *Meta) outputShadowError(err error, output bool) bool {
   585  	// Do nothing if no error
   586  	if err == nil {
   587  		return false
   588  	}
   589  
   590  	// If not outputting, do nothing
   591  	if !output {
   592  		return false
   593  	}
   594  
   595  	// Write the shadow error output to a file
   596  	path := fmt.Sprintf("terraform-error-%d.log", time.Now().UTC().Unix())
   597  	if err := ioutil.WriteFile(path, []byte(err.Error()), 0644); err != nil {
   598  		// If there is an error writing it, just let it go
   599  		log.Printf("[ERROR] Error writing shadow error: %s", err)
   600  		return false
   601  	}
   602  
   603  	// Output!
   604  	m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
   605  		"[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+
   606  			"This is not an error. Your Terraform operation completed successfully.\n"+
   607  			"Your real infrastructure is unaffected by this message.\n\n"+
   608  			"[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+
   609  			"background. These features cannot affect real state and never touch\n"+
   610  			"real infrastructure. If the features work properly, you see nothing.\n"+
   611  			"If the features fail, this message appears.\n\n"+
   612  			"You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+
   613  			"The failure was written to %q. Please\n"+
   614  			"double check this file contains no sensitive information and report\n"+
   615  			"it with your issue.\n\n"+
   616  			"This is not an error. Your terraform operation completed successfully\n"+
   617  			"and your real infrastructure is unaffected by this message.",
   618  		path,
   619  	)))
   620  
   621  	return true
   622  }
   623  
   624  // WorkspaceNameEnvVar is the name of the environment variable that can be used
   625  // to set the name of the Terraform workspace, overriding the workspace chosen
   626  // by `terraform workspace select`.
   627  //
   628  // Note that this environment variable is ignored by `terraform workspace new`
   629  // and `terraform workspace delete`.
   630  const WorkspaceNameEnvVar = "TF_WORKSPACE"
   631  
   632  // Workspace returns the name of the currently configured workspace, corresponding
   633  // to the desired named state.
   634  func (m *Meta) Workspace() string {
   635  	current, _ := m.WorkspaceOverridden()
   636  	return current
   637  }
   638  
   639  // WorkspaceOverridden returns the name of the currently configured workspace,
   640  // corresponding to the desired named state, as well as a bool saying whether
   641  // this was set via the TF_WORKSPACE environment variable.
   642  func (m *Meta) WorkspaceOverridden() (string, bool) {
   643  	if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
   644  		return envVar, true
   645  	}
   646  
   647  	envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile))
   648  	current := string(bytes.TrimSpace(envData))
   649  	if current == "" {
   650  		current = backend.DefaultStateName
   651  	}
   652  
   653  	if err != nil && !os.IsNotExist(err) {
   654  		// always return the default if we can't get a workspace name
   655  		log.Printf("[ERROR] failed to read current workspace: %s", err)
   656  	}
   657  
   658  	return current, false
   659  }
   660  
   661  // SetWorkspace saves the given name as the current workspace in the local
   662  // filesystem.
   663  func (m *Meta) SetWorkspace(name string) error {
   664  	err := os.MkdirAll(m.DataDir(), 0755)
   665  	if err != nil {
   666  		return err
   667  	}
   668  
   669  	err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
   670  	if err != nil {
   671  		return err
   672  	}
   673  	return nil
   674  }
   675  
   676  // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
   677  func isAutoVarFile(path string) bool {
   678  	return strings.HasSuffix(path, ".auto.tfvars") ||
   679  		strings.HasSuffix(path, ".auto.tfvars.json")
   680  }