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 }