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