github.com/ggriffiths/terraform@v0.9.0-beta1.0.20170222213024-79c4935604cb/command/meta.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "log" 10 "os" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/hashicorp/go-getter" 17 "github.com/hashicorp/terraform/helper/experiment" 18 "github.com/hashicorp/terraform/helper/variables" 19 "github.com/hashicorp/terraform/helper/wrappedstreams" 20 "github.com/hashicorp/terraform/terraform" 21 "github.com/mitchellh/cli" 22 "github.com/mitchellh/colorstring" 23 ) 24 25 // Meta are the meta-options that are available on all or most commands. 26 type Meta struct { 27 // The exported fields below should be set by anyone using a 28 // command with a Meta field. These are expected to be set externally 29 // (not from within the command itself). 30 31 Color bool // True if output should be colored 32 ContextOpts *terraform.ContextOpts // Opts copied to initialize 33 Ui cli.Ui // Ui for output 34 35 // ExtraHooks are extra hooks to add to the context. 36 ExtraHooks []terraform.Hook 37 38 //---------------------------------------------------------- 39 // Protected: commands can set these 40 //---------------------------------------------------------- 41 42 // Modify the data directory location. Defaults to DefaultDataDir 43 dataDir string 44 45 //---------------------------------------------------------- 46 // Private: do not set these 47 //---------------------------------------------------------- 48 49 // backendState is the currently active backend state 50 backendState *terraform.BackendState 51 52 // Variables for the context (private) 53 autoKey string 54 autoVariables map[string]interface{} 55 input bool 56 variables map[string]interface{} 57 58 // Targets for this context (private) 59 targets []string 60 61 // Internal fields 62 color bool 63 oldUi cli.Ui 64 65 // The fields below are expected to be set by the command via 66 // command line flags. See the Apply command for an example. 67 // 68 // statePath is the path to the state file. If this is empty, then 69 // no state will be loaded. It is also okay for this to be a path to 70 // a file that doesn't exist; it is assumed that this means that there 71 // is simply no state. 72 // 73 // stateOutPath is used to override the output path for the state. 74 // If not provided, the StatePath is used causing the old state to 75 // be overriden. 76 // 77 // backupPath is used to backup the state file before writing a modified 78 // version. It defaults to stateOutPath + DefaultBackupExtension 79 // 80 // parallelism is used to control the number of concurrent operations 81 // allowed when walking the graph 82 // 83 // shadow is used to enable/disable the shadow graph 84 // 85 // provider is to specify specific resource providers 86 // 87 // lockState is set to false to disable state locking 88 statePath string 89 stateOutPath string 90 backupPath string 91 parallelism int 92 shadow bool 93 provider string 94 stateLock bool 95 } 96 97 // initStatePaths is used to initialize the default values for 98 // statePath, stateOutPath, and backupPath 99 func (m *Meta) initStatePaths() { 100 if m.statePath == "" { 101 m.statePath = DefaultStateFilename 102 } 103 if m.stateOutPath == "" { 104 m.stateOutPath = m.statePath 105 } 106 if m.backupPath == "" { 107 m.backupPath = m.stateOutPath + DefaultBackupExtension 108 } 109 } 110 111 // StateOutPath returns the true output path for the state file 112 func (m *Meta) StateOutPath() string { 113 return m.stateOutPath 114 } 115 116 // Colorize returns the colorization structure for a command. 117 func (m *Meta) Colorize() *colorstring.Colorize { 118 return &colorstring.Colorize{ 119 Colors: colorstring.DefaultColors, 120 Disable: !m.color, 121 Reset: true, 122 } 123 } 124 125 // DataDir returns the directory where local data will be stored. 126 func (m *Meta) DataDir() string { 127 dataDir := DefaultDataDir 128 if m.dataDir != "" { 129 dataDir = m.dataDir 130 } 131 132 return dataDir 133 } 134 135 const ( 136 // InputModeEnvVar is the environment variable that, if set to "false" or 137 // "0", causes terraform commands to behave as if the `-input=false` flag was 138 // specified. 139 InputModeEnvVar = "TF_INPUT" 140 ) 141 142 // InputMode returns the type of input we should ask for in the form of 143 // terraform.InputMode which is passed directly to Context.Input. 144 func (m *Meta) InputMode() terraform.InputMode { 145 if test || !m.input { 146 return 0 147 } 148 149 if envVar := os.Getenv(InputModeEnvVar); envVar != "" { 150 if v, err := strconv.ParseBool(envVar); err == nil { 151 if !v { 152 return 0 153 } 154 } 155 } 156 157 var mode terraform.InputMode 158 mode |= terraform.InputModeProvider 159 mode |= terraform.InputModeVar 160 mode |= terraform.InputModeVarUnset 161 162 return mode 163 } 164 165 // UIInput returns a UIInput object to be used for asking for input. 166 func (m *Meta) UIInput() terraform.UIInput { 167 return &UIInput{ 168 Colorize: m.Colorize(), 169 } 170 } 171 172 // StdinPiped returns true if the input is piped. 173 func (m *Meta) StdinPiped() bool { 174 fi, err := wrappedstreams.Stdin().Stat() 175 if err != nil { 176 // If there is an error, let's just say its not piped 177 return false 178 } 179 180 return fi.Mode()&os.ModeNamedPipe != 0 181 } 182 183 // contextOpts returns the options to use to initialize a Terraform 184 // context with the settings from this Meta. 185 func (m *Meta) contextOpts() *terraform.ContextOpts { 186 var opts terraform.ContextOpts 187 if v := m.ContextOpts; v != nil { 188 opts = *v 189 } 190 191 opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}} 192 if m.ContextOpts != nil { 193 opts.Hooks = append(opts.Hooks, m.ContextOpts.Hooks...) 194 } 195 opts.Hooks = append(opts.Hooks, m.ExtraHooks...) 196 197 vs := make(map[string]interface{}) 198 for k, v := range opts.Variables { 199 vs[k] = v 200 } 201 for k, v := range m.autoVariables { 202 vs[k] = v 203 } 204 for k, v := range m.variables { 205 vs[k] = v 206 } 207 opts.Variables = vs 208 opts.Targets = m.targets 209 opts.UIInput = m.UIInput() 210 opts.Parallelism = m.parallelism 211 opts.Shadow = m.shadow 212 213 return &opts 214 } 215 216 // flags adds the meta flags to the given FlagSet. 217 func (m *Meta) flagSet(n string) *flag.FlagSet { 218 f := flag.NewFlagSet(n, flag.ContinueOnError) 219 f.BoolVar(&m.input, "input", true, "input") 220 f.Var((*variables.Flag)(&m.variables), "var", "variables") 221 f.Var((*variables.FlagFile)(&m.variables), "var-file", "variable file") 222 f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target") 223 224 if m.autoKey != "" { 225 f.Var((*variables.FlagFile)(&m.autoVariables), m.autoKey, "variable file") 226 } 227 228 // Advanced (don't need documentation, or unlikely to be set) 229 f.BoolVar(&m.shadow, "shadow", true, "shadow graph") 230 231 // Experimental features 232 experiment.Flag(f) 233 234 // Create an io.Writer that writes to our Ui properly for errors. 235 // This is kind of a hack, but it does the job. Basically: create 236 // a pipe, use a scanner to break it into lines, and output each line 237 // to the UI. Do this forever. 238 errR, errW := io.Pipe() 239 errScanner := bufio.NewScanner(errR) 240 go func() { 241 for errScanner.Scan() { 242 m.Ui.Error(errScanner.Text()) 243 } 244 }() 245 f.SetOutput(errW) 246 247 // Set the default Usage to empty 248 f.Usage = func() {} 249 250 return f 251 } 252 253 // moduleStorage returns the module.Storage implementation used to store 254 // modules for commands. 255 func (m *Meta) moduleStorage(root string) getter.Storage { 256 return &uiModuleStorage{ 257 Storage: &getter.FolderStorage{ 258 StorageDir: filepath.Join(root, "modules"), 259 }, 260 Ui: m.Ui, 261 } 262 } 263 264 // process will process the meta-parameters out of the arguments. This 265 // will potentially modify the args in-place. It will return the resulting 266 // slice. 267 // 268 // vars says whether or not we support variables. 269 func (m *Meta) process(args []string, vars bool) []string { 270 // We do this so that we retain the ability to technically call 271 // process multiple times, even if we have no plans to do so 272 if m.oldUi != nil { 273 m.Ui = m.oldUi 274 } 275 276 // Set colorization 277 m.color = m.Color 278 for i, v := range args { 279 if v == "-no-color" { 280 m.color = false 281 m.Color = false 282 args = append(args[:i], args[i+1:]...) 283 break 284 } 285 } 286 287 // Set the UI 288 m.oldUi = m.Ui 289 m.Ui = &cli.ConcurrentUi{ 290 Ui: &ColorizeUi{ 291 Colorize: m.Colorize(), 292 ErrorColor: "[red]", 293 WarnColor: "[yellow]", 294 Ui: m.oldUi, 295 }, 296 } 297 298 // If we support vars and the default var file exists, add it to 299 // the args... 300 m.autoKey = "" 301 if vars { 302 if _, err := os.Stat(DefaultVarsFilename); err == nil { 303 m.autoKey = "var-file-default" 304 args = append(args, "", "") 305 copy(args[2:], args[0:]) 306 args[0] = "-" + m.autoKey 307 args[1] = DefaultVarsFilename 308 } 309 310 if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil { 311 m.autoKey = "var-file-default" 312 args = append(args, "", "") 313 copy(args[2:], args[0:]) 314 args[0] = "-" + m.autoKey 315 args[1] = DefaultVarsFilename + ".json" 316 } 317 } 318 319 return args 320 } 321 322 // uiHook returns the UiHook to use with the context. 323 func (m *Meta) uiHook() *UiHook { 324 return &UiHook{ 325 Colorize: m.Colorize(), 326 Ui: m.Ui, 327 } 328 } 329 330 // confirm asks a yes/no confirmation. 331 func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) { 332 for { 333 v, err := m.UIInput().Input(opts) 334 if err != nil { 335 return false, fmt.Errorf( 336 "Error asking for confirmation: %s", err) 337 } 338 339 switch strings.ToLower(v) { 340 case "no": 341 return false, nil 342 case "yes": 343 return true, nil 344 } 345 } 346 } 347 348 const ( 349 // ModuleDepthDefault is the default value for 350 // module depth, which can be overridden by flag 351 // or env var 352 ModuleDepthDefault = -1 353 354 // ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth. 355 ModuleDepthEnvVar = "TF_MODULE_DEPTH" 356 ) 357 358 func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) { 359 flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth") 360 if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" { 361 if md, err := strconv.Atoi(envVar); err == nil { 362 *moduleDepth = md 363 } 364 } 365 } 366 367 // outputShadowError outputs the error from ctx.ShadowError. If the 368 // error is nil then nothing happens. If output is false then it isn't 369 // outputted to the user (you can define logic to guard against outputting). 370 func (m *Meta) outputShadowError(err error, output bool) bool { 371 // Do nothing if no error 372 if err == nil { 373 return false 374 } 375 376 // If not outputting, do nothing 377 if !output { 378 return false 379 } 380 381 // Write the shadow error output to a file 382 path := fmt.Sprintf("terraform-error-%d.log", time.Now().UTC().Unix()) 383 if err := ioutil.WriteFile(path, []byte(err.Error()), 0644); err != nil { 384 // If there is an error writing it, just let it go 385 log.Printf("[ERROR] Error writing shadow error: %s", err) 386 return false 387 } 388 389 // Output! 390 m.Ui.Output(m.Colorize().Color(fmt.Sprintf( 391 "[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+ 392 "This is not an error. Your Terraform operation completed successfully.\n"+ 393 "Your real infrastructure is unaffected by this message.\n\n"+ 394 "[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+ 395 "background. These features cannot affect real state and never touch\n"+ 396 "real infrastructure. If the features work properly, you see nothing.\n"+ 397 "If the features fail, this message appears.\n\n"+ 398 "You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+ 399 "The failure was written to %q. Please\n"+ 400 "double check this file contains no sensitive information and report\n"+ 401 "it with your issue.\n\n"+ 402 "This is not an error. Your terraform operation completed successfully\n"+ 403 "and your real infrastructure is unaffected by this message.", 404 path, 405 ))) 406 407 return true 408 }