github.com/trawler/terraform@v0.10.8-0.20171106022149-4b1c7a1d9b48/command/meta.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "bytes" 6 "errors" 7 "flag" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "log" 12 "os" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/hashicorp/terraform/backend" 20 "github.com/hashicorp/terraform/backend/local" 21 "github.com/hashicorp/terraform/command/format" 22 "github.com/hashicorp/terraform/config" 23 "github.com/hashicorp/terraform/config/module" 24 "github.com/hashicorp/terraform/helper/experiment" 25 "github.com/hashicorp/terraform/helper/variables" 26 "github.com/hashicorp/terraform/helper/wrappedstreams" 27 "github.com/hashicorp/terraform/svchost/auth" 28 "github.com/hashicorp/terraform/svchost/disco" 29 "github.com/hashicorp/terraform/terraform" 30 "github.com/hashicorp/terraform/tfdiags" 31 "github.com/mitchellh/cli" 32 "github.com/mitchellh/colorstring" 33 ) 34 35 // Meta are the meta-options that are available on all or most commands. 36 type Meta struct { 37 // The exported fields below should be set by anyone using a 38 // command with a Meta field. These are expected to be set externally 39 // (not from within the command itself). 40 41 Color bool // True if output should be colored 42 GlobalPluginDirs []string // Additional paths to search for plugins 43 PluginOverrides *PluginOverrides // legacy overrides from .terraformrc file 44 Ui cli.Ui // Ui for output 45 46 // ExtraHooks are extra hooks to add to the context. 47 ExtraHooks []terraform.Hook 48 49 // Services provides access to remote endpoint information for 50 // "terraform-native' services running at a specific user-facing hostname. 51 Services *disco.Disco 52 53 // Credentials provides access to credentials for "terraform-native" 54 // services, which are accessed by a service hostname. 55 Credentials auth.CredentialsSource 56 57 // RunningInAutomation indicates that commands are being run by an 58 // automated system rather than directly at a command prompt. 59 // 60 // This is a hint to various command routines that it may be confusing 61 // to print out messages that suggest running specific follow-up 62 // commands, since the user consuming the output will not be 63 // in a position to run such commands. 64 // 65 // The intended use-case of this flag is when Terraform is running in 66 // some sort of workflow orchestration tool which is abstracting away 67 // the specific commands being run. 68 RunningInAutomation bool 69 70 // PluginCacheDir, if non-empty, enables caching of downloaded plugins 71 // into the given directory. 72 PluginCacheDir string 73 74 // OverrideDataDir, if non-empty, overrides the return value of the 75 // DataDir method for situations where the local .terraform/ directory 76 // is not suitable, e.g. because of a read-only filesystem. 77 OverrideDataDir string 78 79 //---------------------------------------------------------- 80 // Protected: commands can set these 81 //---------------------------------------------------------- 82 83 // Modify the data directory location. This should be accessed through the 84 // DataDir method. 85 dataDir string 86 87 // pluginPath is a user defined set of directories to look for plugins. 88 // This is set during init with the `-plugin-dir` flag, saved to a file in 89 // the data directory. 90 // This overrides all other search paths when discovering plugins. 91 pluginPath []string 92 93 ignorePluginChecksum bool 94 95 // Override certain behavior for tests within this package 96 testingOverrides *testingOverrides 97 98 //---------------------------------------------------------- 99 // Private: do not set these 100 //---------------------------------------------------------- 101 102 // backendState is the currently active backend state 103 backendState *terraform.BackendState 104 105 // Variables for the context (private) 106 autoKey string 107 autoVariables map[string]interface{} 108 input bool 109 variables map[string]interface{} 110 111 // Targets for this context (private) 112 targets []string 113 114 // Internal fields 115 color bool 116 oldUi cli.Ui 117 118 // The fields below are expected to be set by the command via 119 // command line flags. See the Apply command for an example. 120 // 121 // statePath is the path to the state file. If this is empty, then 122 // no state will be loaded. It is also okay for this to be a path to 123 // a file that doesn't exist; it is assumed that this means that there 124 // is simply no state. 125 // 126 // stateOutPath is used to override the output path for the state. 127 // If not provided, the StatePath is used causing the old state to 128 // be overriden. 129 // 130 // backupPath is used to backup the state file before writing a modified 131 // version. It defaults to stateOutPath + DefaultBackupExtension 132 // 133 // parallelism is used to control the number of concurrent operations 134 // allowed when walking the graph 135 // 136 // shadow is used to enable/disable the shadow graph 137 // 138 // provider is to specify specific resource providers 139 // 140 // stateLock is set to false to disable state locking 141 // 142 // stateLockTimeout is the optional duration to retry a state locks locks 143 // when it is already locked by another process. 144 // 145 // forceInitCopy suppresses confirmation for copying state data during 146 // init. 147 // 148 // reconfigure forces init to ignore any stored configuration. 149 statePath string 150 stateOutPath string 151 backupPath string 152 parallelism int 153 shadow bool 154 provider string 155 stateLock bool 156 stateLockTimeout time.Duration 157 forceInitCopy bool 158 reconfigure bool 159 160 // errWriter is the write side of a pipe for the FlagSet output. We need to 161 // keep track of this to close previous pipes between tests. Normal 162 // operation never needs to close this. 163 errWriter *io.PipeWriter 164 // done chan to wait for the scanner goroutine 165 errScannerDone chan struct{} 166 167 // Used with the import command to allow import of state when no matching config exists. 168 allowMissingConfig bool 169 } 170 171 type PluginOverrides struct { 172 Providers map[string]string 173 Provisioners map[string]string 174 } 175 176 type testingOverrides struct { 177 ProviderResolver terraform.ResourceProviderResolver 178 Provisioners map[string]terraform.ResourceProvisionerFactory 179 } 180 181 // initStatePaths is used to initialize the default values for 182 // statePath, stateOutPath, and backupPath 183 func (m *Meta) initStatePaths() { 184 if m.statePath == "" { 185 m.statePath = DefaultStateFilename 186 } 187 if m.stateOutPath == "" { 188 m.stateOutPath = m.statePath 189 } 190 if m.backupPath == "" { 191 m.backupPath = m.stateOutPath + DefaultBackupExtension 192 } 193 } 194 195 // StateOutPath returns the true output path for the state file 196 func (m *Meta) StateOutPath() string { 197 return m.stateOutPath 198 } 199 200 // Colorize returns the colorization structure for a command. 201 func (m *Meta) Colorize() *colorstring.Colorize { 202 return &colorstring.Colorize{ 203 Colors: colorstring.DefaultColors, 204 Disable: !m.color, 205 Reset: true, 206 } 207 } 208 209 // DataDir returns the directory where local data will be stored. 210 // Defaults to DefaultDataDir in the current working directory. 211 func (m *Meta) DataDir() string { 212 if m.OverrideDataDir != "" { 213 return m.OverrideDataDir 214 } 215 return DefaultDataDir 216 } 217 218 const ( 219 // InputModeEnvVar is the environment variable that, if set to "false" or 220 // "0", causes terraform commands to behave as if the `-input=false` flag was 221 // specified. 222 InputModeEnvVar = "TF_INPUT" 223 ) 224 225 // InputMode returns the type of input we should ask for in the form of 226 // terraform.InputMode which is passed directly to Context.Input. 227 func (m *Meta) InputMode() terraform.InputMode { 228 if test || !m.input { 229 return 0 230 } 231 232 if envVar := os.Getenv(InputModeEnvVar); envVar != "" { 233 if v, err := strconv.ParseBool(envVar); err == nil { 234 if !v { 235 return 0 236 } 237 } 238 } 239 240 var mode terraform.InputMode 241 mode |= terraform.InputModeProvider 242 mode |= terraform.InputModeVar 243 mode |= terraform.InputModeVarUnset 244 245 return mode 246 } 247 248 // UIInput returns a UIInput object to be used for asking for input. 249 func (m *Meta) UIInput() terraform.UIInput { 250 return &UIInput{ 251 Colorize: m.Colorize(), 252 } 253 } 254 255 // StdinPiped returns true if the input is piped. 256 func (m *Meta) StdinPiped() bool { 257 fi, err := wrappedstreams.Stdin().Stat() 258 if err != nil { 259 // If there is an error, let's just say its not piped 260 return false 261 } 262 263 return fi.Mode()&os.ModeNamedPipe != 0 264 } 265 266 const ( 267 ProviderSkipVerifyEnvVar = "TF_SKIP_PROVIDER_VERIFY" 268 ) 269 270 // contextOpts returns the options to use to initialize a Terraform 271 // context with the settings from this Meta. 272 func (m *Meta) contextOpts() *terraform.ContextOpts { 273 var opts terraform.ContextOpts 274 opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}} 275 opts.Hooks = append(opts.Hooks, m.ExtraHooks...) 276 277 vs := make(map[string]interface{}) 278 for k, v := range opts.Variables { 279 vs[k] = v 280 } 281 for k, v := range m.autoVariables { 282 vs[k] = v 283 } 284 for k, v := range m.variables { 285 vs[k] = v 286 } 287 opts.Variables = vs 288 289 opts.Targets = m.targets 290 opts.UIInput = m.UIInput() 291 opts.Parallelism = m.parallelism 292 opts.Shadow = m.shadow 293 294 // If testingOverrides are set, we'll skip the plugin discovery process 295 // and just work with what we've been given, thus allowing the tests 296 // to provide mock providers and provisioners. 297 if m.testingOverrides != nil { 298 opts.ProviderResolver = m.testingOverrides.ProviderResolver 299 opts.Provisioners = m.testingOverrides.Provisioners 300 } else { 301 opts.ProviderResolver = m.providerResolver() 302 opts.Provisioners = m.provisionerFactories() 303 } 304 305 opts.ProviderSHA256s = m.providerPluginsLock().Read() 306 if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" { 307 opts.SkipProviderVerify = true 308 } 309 310 opts.Meta = &terraform.ContextMeta{ 311 Env: m.Workspace(), 312 } 313 314 return &opts 315 } 316 317 // flags adds the meta flags to the given FlagSet. 318 func (m *Meta) flagSet(n string) *flag.FlagSet { 319 f := flag.NewFlagSet(n, flag.ContinueOnError) 320 f.BoolVar(&m.input, "input", true, "input") 321 f.Var((*variables.Flag)(&m.variables), "var", "variables") 322 f.Var((*variables.FlagFile)(&m.variables), "var-file", "variable file") 323 f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target") 324 325 if m.autoKey != "" { 326 f.Var((*variables.FlagFile)(&m.autoVariables), m.autoKey, "variable file") 327 } 328 329 // Advanced (don't need documentation, or unlikely to be set) 330 f.BoolVar(&m.shadow, "shadow", true, "shadow graph") 331 332 // Experimental features 333 experiment.Flag(f) 334 335 // Create an io.Writer that writes to our Ui properly for errors. 336 // This is kind of a hack, but it does the job. Basically: create 337 // a pipe, use a scanner to break it into lines, and output each line 338 // to the UI. Do this forever. 339 340 // If a previous pipe exists, we need to close that first. 341 // This should only happen in testing. 342 if m.errWriter != nil { 343 m.errWriter.Close() 344 } 345 346 if m.errScannerDone != nil { 347 <-m.errScannerDone 348 } 349 350 errR, errW := io.Pipe() 351 errScanner := bufio.NewScanner(errR) 352 m.errWriter = errW 353 m.errScannerDone = make(chan struct{}) 354 go func() { 355 defer close(m.errScannerDone) 356 for errScanner.Scan() { 357 m.Ui.Error(errScanner.Text()) 358 } 359 }() 360 f.SetOutput(errW) 361 362 // Set the default Usage to empty 363 f.Usage = func() {} 364 365 // command that bypass locking will supply their own flag on this var, but 366 // set the initial meta value to true as a failsafe. 367 m.stateLock = true 368 369 return f 370 } 371 372 // moduleStorage returns the module.Storage implementation used to store 373 // modules for commands. 374 func (m *Meta) moduleStorage(root string, mode module.GetMode) *module.Storage { 375 s := module.NewStorage(filepath.Join(root, "modules"), m.Services, m.Credentials) 376 s.Ui = m.Ui 377 s.Mode = mode 378 return s 379 } 380 381 // process will process the meta-parameters out of the arguments. This 382 // will potentially modify the args in-place. It will return the resulting 383 // slice. 384 // 385 // vars says whether or not we support variables. 386 func (m *Meta) process(args []string, vars bool) ([]string, error) { 387 // We do this so that we retain the ability to technically call 388 // process multiple times, even if we have no plans to do so 389 if m.oldUi != nil { 390 m.Ui = m.oldUi 391 } 392 393 // Set colorization 394 m.color = m.Color 395 for i, v := range args { 396 if v == "-no-color" { 397 m.color = false 398 m.Color = false 399 args = append(args[:i], args[i+1:]...) 400 break 401 } 402 } 403 404 // Set the UI 405 m.oldUi = m.Ui 406 m.Ui = &cli.ConcurrentUi{ 407 Ui: &ColorizeUi{ 408 Colorize: m.Colorize(), 409 ErrorColor: "[red]", 410 WarnColor: "[yellow]", 411 Ui: m.oldUi, 412 }, 413 } 414 415 // If we support vars and the default var file exists, add it to 416 // the args... 417 m.autoKey = "" 418 if vars { 419 var preArgs []string 420 421 if _, err := os.Stat(DefaultVarsFilename); err == nil { 422 m.autoKey = "var-file-default" 423 preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename) 424 } 425 426 if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil { 427 m.autoKey = "var-file-default" 428 preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename+".json") 429 } 430 431 wd, err := os.Getwd() 432 if err != nil { 433 return nil, err 434 } 435 436 fis, err := ioutil.ReadDir(wd) 437 if err != nil { 438 return nil, err 439 } 440 441 // make sure we add the files in order 442 sort.Slice(fis, func(i, j int) bool { 443 return fis[i].Name() < fis[j].Name() 444 }) 445 446 for _, fi := range fis { 447 name := fi.Name() 448 // Ignore directories, non-var-files, and ignored files 449 if fi.IsDir() || !isAutoVarFile(name) || config.IsIgnoredFile(name) { 450 continue 451 } 452 453 m.autoKey = "var-file-default" 454 preArgs = append(preArgs, "-"+m.autoKey, name) 455 } 456 457 args = append(preArgs, args...) 458 } 459 460 return args, nil 461 } 462 463 // uiHook returns the UiHook to use with the context. 464 func (m *Meta) uiHook() *UiHook { 465 return &UiHook{ 466 Colorize: m.Colorize(), 467 Ui: m.Ui, 468 } 469 } 470 471 // confirm asks a yes/no confirmation. 472 func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { 473 if !m.Input() { 474 return false, errors.New("input is disabled") 475 } 476 for { 477 v, err := m.UIInput().Input(opts) 478 if err != nil { 479 return false, fmt.Errorf( 480 "Error asking for confirmation: %s", err) 481 } 482 483 switch strings.ToLower(v) { 484 case "no": 485 return false, nil 486 case "yes": 487 return true, nil 488 } 489 } 490 } 491 492 // showDiagnostics displays error and warning messages in the UI. 493 // 494 // "Diagnostics" here means the Diagnostics type from the tfdiag package, 495 // though as a convenience this function accepts anything that could be 496 // passed to the "Append" method on that type, converting it to Diagnostics 497 // before displaying it. 498 // 499 // Internally this function uses Diagnostics.Append, and so it will panic 500 // if given unsupported value types, just as Append does. 501 func (m *Meta) showDiagnostics(vals ...interface{}) { 502 var diags tfdiags.Diagnostics 503 diags = diags.Append(vals...) 504 505 for _, diag := range diags { 506 // TODO: Actually measure the terminal width and pass it here. 507 // For now, we don't have easy access to the writer that 508 // ui.Error (etc) are writing to and thus can't interrogate 509 // to see if it's a terminal and what size it is. 510 msg := format.Diagnostic(diag, m.Colorize(), 78) 511 switch diag.Severity() { 512 case tfdiags.Error: 513 m.Ui.Error(msg) 514 case tfdiags.Warning: 515 m.Ui.Warn(msg) 516 default: 517 m.Ui.Output(msg) 518 } 519 } 520 } 521 522 const ( 523 // ModuleDepthDefault is the default value for 524 // module depth, which can be overridden by flag 525 // or env var 526 ModuleDepthDefault = -1 527 528 // ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth. 529 ModuleDepthEnvVar = "TF_MODULE_DEPTH" 530 ) 531 532 func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) { 533 flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth") 534 if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" { 535 if md, err := strconv.Atoi(envVar); err == nil { 536 *moduleDepth = md 537 } 538 } 539 } 540 541 // outputShadowError outputs the error from ctx.ShadowError. If the 542 // error is nil then nothing happens. If output is false then it isn't 543 // outputted to the user (you can define logic to guard against outputting). 544 func (m *Meta) outputShadowError(err error, output bool) bool { 545 // Do nothing if no error 546 if err == nil { 547 return false 548 } 549 550 // If not outputting, do nothing 551 if !output { 552 return false 553 } 554 555 // Write the shadow error output to a file 556 path := fmt.Sprintf("terraform-error-%d.log", time.Now().UTC().Unix()) 557 if err := ioutil.WriteFile(path, []byte(err.Error()), 0644); err != nil { 558 // If there is an error writing it, just let it go 559 log.Printf("[ERROR] Error writing shadow error: %s", err) 560 return false 561 } 562 563 // Output! 564 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 565 "[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+ 566 "This is not an error. Your Terraform operation completed successfully.\n"+ 567 "Your real infrastructure is unaffected by this message.\n\n"+ 568 "[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+ 569 "background. These features cannot affect real state and never touch\n"+ 570 "real infrastructure. If the features work properly, you see nothing.\n"+ 571 "If the features fail, this message appears.\n\n"+ 572 "You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+ 573 "The failure was written to %q. Please\n"+ 574 "double check this file contains no sensitive information and report\n"+ 575 "it with your issue.\n\n"+ 576 "This is not an error. Your terraform operation completed successfully\n"+ 577 "and your real infrastructure is unaffected by this message.", 578 path, 579 ))) 580 581 return true 582 } 583 584 // WorkspaceNameEnvVar is the name of the environment variable that can be used 585 // to set the name of the Terraform workspace, overriding the workspace chosen 586 // by `terraform workspace select`. 587 // 588 // Note that this environment variable is ignored by `terraform workspace new` 589 // and `terraform workspace delete`. 590 const WorkspaceNameEnvVar = "TF_WORKSPACE" 591 592 // Workspace returns the name of the currently configured workspace, corresponding 593 // to the desired named state. 594 func (m *Meta) Workspace() string { 595 current, _ := m.WorkspaceOverridden() 596 return current 597 } 598 599 // WorkspaceOverridden returns the name of the currently configured workspace, 600 // corresponding to the desired named state, as well as a bool saying whether 601 // this was set via the TF_WORKSPACE environment variable. 602 func (m *Meta) WorkspaceOverridden() (string, bool) { 603 if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" { 604 return envVar, true 605 } 606 607 envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile)) 608 current := string(bytes.TrimSpace(envData)) 609 if current == "" { 610 current = backend.DefaultStateName 611 } 612 613 if err != nil && !os.IsNotExist(err) { 614 // always return the default if we can't get a workspace name 615 log.Printf("[ERROR] failed to read current workspace: %s", err) 616 } 617 618 return current, false 619 } 620 621 // SetWorkspace saves the given name as the current workspace in the local 622 // filesystem. 623 func (m *Meta) SetWorkspace(name string) error { 624 err := os.MkdirAll(m.DataDir(), 0755) 625 if err != nil { 626 return err 627 } 628 629 err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644) 630 if err != nil { 631 return err 632 } 633 return nil 634 } 635 636 // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json 637 func isAutoVarFile(path string) bool { 638 return strings.HasSuffix(path, ".auto.tfvars") || 639 strings.HasSuffix(path, ".auto.tfvars.json") 640 }