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