github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-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/muratcelep/terraform/not-internal/addrs" 23 "github.com/muratcelep/terraform/not-internal/backend" 24 "github.com/muratcelep/terraform/not-internal/backend/local" 25 "github.com/muratcelep/terraform/not-internal/command/arguments" 26 "github.com/muratcelep/terraform/not-internal/command/format" 27 "github.com/muratcelep/terraform/not-internal/command/views" 28 "github.com/muratcelep/terraform/not-internal/command/webbrowser" 29 "github.com/muratcelep/terraform/not-internal/command/workdir" 30 "github.com/muratcelep/terraform/not-internal/configs/configload" 31 "github.com/muratcelep/terraform/not-internal/getproviders" 32 legacy "github.com/muratcelep/terraform/not-internal/legacy/terraform" 33 "github.com/muratcelep/terraform/not-internal/providers" 34 "github.com/muratcelep/terraform/not-internal/provisioners" 35 "github.com/muratcelep/terraform/not-internal/terminal" 36 "github.com/muratcelep/terraform/not-internal/terraform" 37 "github.com/muratcelep/terraform/not-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 var providerFactories map[addrs.Provider]providers.Factory 463 providerFactories, err = m.providerFactories() 464 opts.Providers = providerFactories 465 opts.Provisioners = m.provisionerFactories() 466 } 467 468 opts.Meta = &terraform.ContextMeta{ 469 Env: workspace, 470 OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(), 471 } 472 473 return &opts, err 474 } 475 476 // defaultFlagSet creates a default flag set for commands. 477 // See also command/arguments/default.go 478 func (m *Meta) defaultFlagSet(n string) *flag.FlagSet { 479 f := flag.NewFlagSet(n, flag.ContinueOnError) 480 f.SetOutput(ioutil.Discard) 481 482 // Set the default Usage to empty 483 f.Usage = func() {} 484 485 return f 486 } 487 488 // ignoreRemoteVersionFlagSet add the ignore-remote version flag to suppress 489 // the error when the configured Terraform version on the remote workspace 490 // does not match the local Terraform version. 491 func (m *Meta) ignoreRemoteVersionFlagSet(n string) *flag.FlagSet { 492 f := m.defaultFlagSet(n) 493 494 f.BoolVar(&m.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible") 495 496 return f 497 } 498 499 // extendedFlagSet adds custom flags that are mostly used by commands 500 // that are used to run an operation like plan or apply. 501 func (m *Meta) extendedFlagSet(n string) *flag.FlagSet { 502 f := m.defaultFlagSet(n) 503 504 f.BoolVar(&m.input, "input", true, "input") 505 f.Var((*FlagStringSlice)(&m.targetFlags), "target", "resource to target") 506 f.BoolVar(&m.compactWarnings, "compact-warnings", false, "use compact warnings") 507 508 if m.variableArgs.items == nil { 509 m.variableArgs = newRawFlags("-var") 510 } 511 varValues := m.variableArgs.Alias("-var") 512 varFiles := m.variableArgs.Alias("-var-file") 513 f.Var(varValues, "var", "variables") 514 f.Var(varFiles, "var-file", "variable file") 515 516 // commands that bypass locking will supply their own flag on this var, 517 // but set the initial meta value to true as a failsafe. 518 m.stateLock = true 519 520 return f 521 } 522 523 // process will process any -no-color entries out of the arguments. This 524 // will potentially modify the args in-place. It will return the resulting 525 // slice, and update the Meta and Ui. 526 func (m *Meta) process(args []string) []string { 527 // We do this so that we retain the ability to technically call 528 // process multiple times, even if we have no plans to do so 529 if m.oldUi != nil { 530 m.Ui = m.oldUi 531 } 532 533 // Set colorization 534 m.color = m.Color 535 i := 0 // output index 536 for _, v := range args { 537 if v == "-no-color" { 538 m.color = false 539 m.Color = false 540 } else { 541 // copy and increment index 542 args[i] = v 543 i++ 544 } 545 } 546 args = args[:i] 547 548 // Set the UI 549 m.oldUi = m.Ui 550 m.Ui = &cli.ConcurrentUi{ 551 Ui: &ColorizeUi{ 552 Colorize: m.Colorize(), 553 ErrorColor: "[red]", 554 WarnColor: "[yellow]", 555 Ui: m.oldUi, 556 }, 557 } 558 559 // Reconfigure the view. This is necessary for commands which use both 560 // views.View and cli.Ui during the migration phase. 561 if m.View != nil { 562 m.View.Configure(&arguments.View{ 563 CompactWarnings: m.compactWarnings, 564 NoColor: !m.Color, 565 }) 566 } 567 568 return args 569 } 570 571 // uiHook returns the UiHook to use with the context. 572 func (m *Meta) uiHook() *views.UiHook { 573 return views.NewUiHook(m.View) 574 } 575 576 // confirm asks a yes/no confirmation. 577 func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { 578 if !m.Input() { 579 return false, errors.New("input is disabled") 580 } 581 582 for i := 0; i < 2; i++ { 583 v, err := m.UIInput().Input(context.Background(), opts) 584 if err != nil { 585 return false, fmt.Errorf( 586 "Error asking for confirmation: %s", err) 587 } 588 589 switch strings.ToLower(v) { 590 case "no": 591 return false, nil 592 case "yes": 593 return true, nil 594 } 595 } 596 return false, nil 597 } 598 599 // showDiagnostics displays error and warning messages in the UI. 600 // 601 // "Diagnostics" here means the Diagnostics type from the tfdiag package, 602 // though as a convenience this function accepts anything that could be 603 // passed to the "Append" method on that type, converting it to Diagnostics 604 // before displaying it. 605 // 606 // Internally this function uses Diagnostics.Append, and so it will panic 607 // if given unsupported value types, just as Append does. 608 func (m *Meta) showDiagnostics(vals ...interface{}) { 609 var diags tfdiags.Diagnostics 610 diags = diags.Append(vals...) 611 diags.Sort() 612 613 if len(diags) == 0 { 614 return 615 } 616 617 outputWidth := m.ErrorColumns() 618 619 diags = diags.ConsolidateWarnings(1) 620 621 // Since warning messages are generally competing 622 if m.compactWarnings { 623 // If the user selected compact warnings and all of the diagnostics are 624 // warnings then we'll use a more compact representation of the warnings 625 // that only includes their summaries. 626 // We show full warnings if there are also errors, because a warning 627 // can sometimes serve as good context for a subsequent error. 628 useCompact := true 629 for _, diag := range diags { 630 if diag.Severity() != tfdiags.Warning { 631 useCompact = false 632 break 633 } 634 } 635 if useCompact { 636 msg := format.DiagnosticWarningsCompact(diags, m.Colorize()) 637 msg = "\n" + msg + "\nTo see the full warning notes, run Terraform without -compact-warnings.\n" 638 m.Ui.Warn(msg) 639 return 640 } 641 } 642 643 for _, diag := range diags { 644 var msg string 645 if m.Color { 646 msg = format.Diagnostic(diag, m.configSources(), m.Colorize(), outputWidth) 647 } else { 648 msg = format.DiagnosticPlain(diag, m.configSources(), outputWidth) 649 } 650 651 switch diag.Severity() { 652 case tfdiags.Error: 653 m.Ui.Error(msg) 654 case tfdiags.Warning: 655 m.Ui.Warn(msg) 656 default: 657 m.Ui.Output(msg) 658 } 659 } 660 } 661 662 // WorkspaceNameEnvVar is the name of the environment variable that can be used 663 // to set the name of the Terraform workspace, overriding the workspace chosen 664 // by `terraform workspace select`. 665 // 666 // Note that this environment variable is ignored by `terraform workspace new` 667 // and `terraform workspace delete`. 668 const WorkspaceNameEnvVar = "TF_WORKSPACE" 669 670 var errInvalidWorkspaceNameEnvVar = fmt.Errorf("Invalid workspace name set using %s", WorkspaceNameEnvVar) 671 672 // Workspace returns the name of the currently configured workspace, corresponding 673 // to the desired named state. 674 func (m *Meta) Workspace() (string, error) { 675 current, overridden := m.WorkspaceOverridden() 676 if overridden && !validWorkspaceName(current) { 677 return "", errInvalidWorkspaceNameEnvVar 678 } 679 return current, nil 680 } 681 682 // WorkspaceOverridden returns the name of the currently configured workspace, 683 // corresponding to the desired named state, as well as a bool saying whether 684 // this was set via the TF_WORKSPACE environment variable. 685 func (m *Meta) WorkspaceOverridden() (string, bool) { 686 if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" { 687 return envVar, true 688 } 689 690 envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile)) 691 current := string(bytes.TrimSpace(envData)) 692 if current == "" { 693 current = backend.DefaultStateName 694 } 695 696 if err != nil && !os.IsNotExist(err) { 697 // always return the default if we can't get a workspace name 698 log.Printf("[ERROR] failed to read current workspace: %s", err) 699 } 700 701 return current, false 702 } 703 704 // SetWorkspace saves the given name as the current workspace in the local 705 // filesystem. 706 func (m *Meta) SetWorkspace(name string) error { 707 err := os.MkdirAll(m.DataDir(), 0755) 708 if err != nil { 709 return err 710 } 711 712 err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644) 713 if err != nil { 714 return err 715 } 716 return nil 717 } 718 719 // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json 720 func isAutoVarFile(path string) bool { 721 return strings.HasSuffix(path, ".auto.tfvars") || 722 strings.HasSuffix(path, ".auto.tfvars.json") 723 } 724 725 // FIXME: as an interim refactoring step, we apply the contents of the state 726 // arguments directly to the Meta object. Future work would ideally update the 727 // code paths which use these arguments to be passed them directly for clarity. 728 func (m *Meta) applyStateArguments(args *arguments.State) { 729 m.stateLock = args.Lock 730 m.stateLockTimeout = args.LockTimeout 731 m.statePath = args.StatePath 732 m.stateOutPath = args.StateOutPath 733 m.backupPath = args.BackupPath 734 }