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