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