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