github.com/minamijoyo/terraform@v0.7.8-0.20161029001309-18b3736ba44b/command/meta.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strconv" 11 12 "github.com/hashicorp/go-getter" 13 "github.com/hashicorp/terraform/config/module" 14 "github.com/hashicorp/terraform/helper/experiment" 15 "github.com/hashicorp/terraform/state" 16 "github.com/hashicorp/terraform/terraform" 17 "github.com/mitchellh/cli" 18 "github.com/mitchellh/colorstring" 19 ) 20 21 // Meta are the meta-options that are available on all or most commands. 22 type Meta struct { 23 Color bool 24 ContextOpts *terraform.ContextOpts 25 Ui cli.Ui 26 27 // State read when calling `Context`. This is available after calling 28 // `Context`. 29 state state.State 30 stateResult *StateResult 31 32 // This can be set by the command itself to provide extra hooks. 33 extraHooks []terraform.Hook 34 35 // This can be set by tests to change some directories 36 dataDir string 37 38 // Variables for the context (private) 39 autoKey string 40 autoVariables map[string]interface{} 41 input bool 42 variables map[string]interface{} 43 44 // Targets for this context (private) 45 targets []string 46 47 color bool 48 oldUi cli.Ui 49 50 // The fields below are expected to be set by the command via 51 // command line flags. See the Apply command for an example. 52 // 53 // statePath is the path to the state file. If this is empty, then 54 // no state will be loaded. It is also okay for this to be a path to 55 // a file that doesn't exist; it is assumed that this means that there 56 // is simply no state. 57 // 58 // stateOutPath is used to override the output path for the state. 59 // If not provided, the StatePath is used causing the old state to 60 // be overriden. 61 // 62 // backupPath is used to backup the state file before writing a modified 63 // version. It defaults to stateOutPath + DefaultBackupExtension 64 // 65 // parallelism is used to control the number of concurrent operations 66 // allowed when walking the graph 67 // 68 // shadow is used to enable/disable the shadow graph 69 statePath string 70 stateOutPath string 71 backupPath string 72 parallelism int 73 shadow bool 74 } 75 76 // initStatePaths is used to initialize the default values for 77 // statePath, stateOutPath, and backupPath 78 func (m *Meta) initStatePaths() { 79 if m.statePath == "" { 80 m.statePath = DefaultStateFilename 81 } 82 if m.stateOutPath == "" { 83 m.stateOutPath = m.statePath 84 } 85 if m.backupPath == "" { 86 m.backupPath = m.stateOutPath + DefaultBackupExtension 87 } 88 } 89 90 // StateOutPath returns the true output path for the state file 91 func (m *Meta) StateOutPath() string { 92 return m.stateOutPath 93 } 94 95 // Colorize returns the colorization structure for a command. 96 func (m *Meta) Colorize() *colorstring.Colorize { 97 return &colorstring.Colorize{ 98 Colors: colorstring.DefaultColors, 99 Disable: !m.color, 100 Reset: true, 101 } 102 } 103 104 // Context returns a Terraform Context taking into account the context 105 // options used to initialize this meta configuration. 106 func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { 107 opts := m.contextOpts() 108 109 // First try to just read the plan directly from the path given. 110 f, err := os.Open(copts.Path) 111 if err == nil { 112 plan, err := terraform.ReadPlan(f) 113 f.Close() 114 if err == nil { 115 // Setup our state 116 state, statePath, err := StateFromPlan(m.statePath, m.stateOutPath, plan) 117 if err != nil { 118 return nil, false, fmt.Errorf("Error loading plan: %s", err) 119 } 120 121 // Set our state 122 m.state = state 123 124 // this is used for printing the saved location later 125 if m.stateOutPath == "" { 126 m.stateOutPath = statePath 127 } 128 129 if len(m.variables) > 0 { 130 return nil, false, fmt.Errorf( 131 "You can't set variables with the '-var' or '-var-file' flag\n" + 132 "when you're applying a plan file. The variables used when\n" + 133 "the plan was created will be used. If you wish to use different\n" + 134 "variable values, create a new plan file.") 135 } 136 137 ctx, err := plan.Context(opts) 138 return ctx, true, err 139 } 140 } 141 142 // Load the statePath if not given 143 if copts.StatePath != "" { 144 m.statePath = copts.StatePath 145 } 146 147 // Tell the context if we're in a destroy plan / apply 148 opts.Destroy = copts.Destroy 149 150 // Store the loaded state 151 state, err := m.State() 152 if err != nil { 153 return nil, false, err 154 } 155 156 // Load the root module 157 var mod *module.Tree 158 if copts.Path != "" { 159 mod, err = module.NewTreeModule("", copts.Path) 160 if err != nil { 161 return nil, false, fmt.Errorf("Error loading config: %s", err) 162 } 163 } else { 164 mod = module.NewEmptyTree() 165 } 166 167 err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode) 168 if err != nil { 169 return nil, false, fmt.Errorf("Error downloading modules: %s", err) 170 } 171 172 // Validate the module right away 173 if err := mod.Validate(); err != nil { 174 return nil, false, err 175 } 176 177 opts.Module = mod 178 opts.Parallelism = copts.Parallelism 179 opts.State = state.State() 180 ctx, err := terraform.NewContext(opts) 181 return ctx, false, err 182 } 183 184 // DataDir returns the directory where local data will be stored. 185 func (m *Meta) DataDir() string { 186 dataDir := DefaultDataDir 187 if m.dataDir != "" { 188 dataDir = m.dataDir 189 } 190 191 return dataDir 192 } 193 194 const ( 195 // InputModeEnvVar is the environment variable that, if set to "false" or 196 // "0", causes terraform commands to behave as if the `-input=false` flag was 197 // specified. 198 InputModeEnvVar = "TF_INPUT" 199 ) 200 201 // InputMode returns the type of input we should ask for in the form of 202 // terraform.InputMode which is passed directly to Context.Input. 203 func (m *Meta) InputMode() terraform.InputMode { 204 if test || !m.input { 205 return 0 206 } 207 208 if envVar := os.Getenv(InputModeEnvVar); envVar != "" { 209 if v, err := strconv.ParseBool(envVar); err == nil { 210 if !v { 211 return 0 212 } 213 } 214 } 215 216 var mode terraform.InputMode 217 mode |= terraform.InputModeProvider 218 if len(m.variables) == 0 { 219 mode |= terraform.InputModeVar 220 mode |= terraform.InputModeVarUnset 221 } 222 223 return mode 224 } 225 226 // State returns the state for this meta. 227 func (m *Meta) State() (state.State, error) { 228 if m.state != nil { 229 return m.state, nil 230 } 231 232 result, err := State(m.StateOpts()) 233 if err != nil { 234 return nil, err 235 } 236 237 m.state = result.State 238 m.stateOutPath = result.StatePath 239 m.stateResult = result 240 return m.state, nil 241 } 242 243 // StateRaw is used to setup the state manually. 244 func (m *Meta) StateRaw(opts *StateOpts) (*StateResult, error) { 245 result, err := State(opts) 246 if err != nil { 247 return nil, err 248 } 249 250 m.state = result.State 251 m.stateOutPath = result.StatePath 252 m.stateResult = result 253 return result, nil 254 } 255 256 // StateOpts returns the default state options 257 func (m *Meta) StateOpts() *StateOpts { 258 localPath := m.statePath 259 if localPath == "" { 260 localPath = DefaultStateFilename 261 } 262 remotePath := filepath.Join(m.DataDir(), DefaultStateFilename) 263 264 return &StateOpts{ 265 LocalPath: localPath, 266 LocalPathOut: m.stateOutPath, 267 RemotePath: remotePath, 268 RemoteRefresh: true, 269 BackupPath: m.backupPath, 270 } 271 } 272 273 // UIInput returns a UIInput object to be used for asking for input. 274 func (m *Meta) UIInput() terraform.UIInput { 275 return &UIInput{ 276 Colorize: m.Colorize(), 277 } 278 } 279 280 // PersistState is used to write out the state, handling backup of 281 // the existing state file and respecting path configurations. 282 func (m *Meta) PersistState(s *terraform.State) error { 283 if err := m.state.WriteState(s); err != nil { 284 return err 285 } 286 287 return m.state.PersistState() 288 } 289 290 // Input returns true if we should ask for input for context. 291 func (m *Meta) Input() bool { 292 return !test && m.input && len(m.variables) == 0 293 } 294 295 // contextOpts returns the options to use to initialize a Terraform 296 // context with the settings from this Meta. 297 func (m *Meta) contextOpts() *terraform.ContextOpts { 298 var opts terraform.ContextOpts = *m.ContextOpts 299 opts.Hooks = make( 300 []terraform.Hook, 301 len(m.ContextOpts.Hooks)+len(m.extraHooks)+1) 302 opts.Hooks[0] = m.uiHook() 303 copy(opts.Hooks[1:], m.ContextOpts.Hooks) 304 copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks) 305 306 vs := make(map[string]interface{}) 307 for k, v := range opts.Variables { 308 vs[k] = v 309 } 310 for k, v := range m.autoVariables { 311 vs[k] = v 312 } 313 for k, v := range m.variables { 314 vs[k] = v 315 } 316 opts.Variables = vs 317 opts.Targets = m.targets 318 opts.UIInput = m.UIInput() 319 opts.Shadow = m.shadow 320 321 return &opts 322 } 323 324 // flags adds the meta flags to the given FlagSet. 325 func (m *Meta) flagSet(n string) *flag.FlagSet { 326 f := flag.NewFlagSet(n, flag.ContinueOnError) 327 f.BoolVar(&m.input, "input", true, "input") 328 f.Var((*FlagTypedKV)(&m.variables), "var", "variables") 329 f.Var((*FlagKVFile)(&m.variables), "var-file", "variable file") 330 f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target") 331 332 if m.autoKey != "" { 333 f.Var((*FlagKVFile)(&m.autoVariables), m.autoKey, "variable file") 334 } 335 336 // Advanced (don't need documentation, or unlikely to be set) 337 f.BoolVar(&m.shadow, "shadow", true, "shadow graph") 338 339 // Experimental features 340 experiment.Flag(f) 341 342 // Create an io.Writer that writes to our Ui properly for errors. 343 // This is kind of a hack, but it does the job. Basically: create 344 // a pipe, use a scanner to break it into lines, and output each line 345 // to the UI. Do this forever. 346 errR, errW := io.Pipe() 347 errScanner := bufio.NewScanner(errR) 348 go func() { 349 for errScanner.Scan() { 350 m.Ui.Error(errScanner.Text()) 351 } 352 }() 353 f.SetOutput(errW) 354 355 // Set the default Usage to empty 356 f.Usage = func() {} 357 358 return f 359 } 360 361 // moduleStorage returns the module.Storage implementation used to store 362 // modules for commands. 363 func (m *Meta) moduleStorage(root string) getter.Storage { 364 return &uiModuleStorage{ 365 Storage: &getter.FolderStorage{ 366 StorageDir: filepath.Join(root, "modules"), 367 }, 368 Ui: m.Ui, 369 } 370 } 371 372 // process will process the meta-parameters out of the arguments. This 373 // will potentially modify the args in-place. It will return the resulting 374 // slice. 375 // 376 // vars says whether or not we support variables. 377 func (m *Meta) process(args []string, vars bool) []string { 378 // We do this so that we retain the ability to technically call 379 // process multiple times, even if we have no plans to do so 380 if m.oldUi != nil { 381 m.Ui = m.oldUi 382 } 383 384 // Set colorization 385 m.color = m.Color 386 for i, v := range args { 387 if v == "-no-color" { 388 m.color = false 389 m.Color = false 390 args = append(args[:i], args[i+1:]...) 391 break 392 } 393 } 394 395 // Set the UI 396 m.oldUi = m.Ui 397 m.Ui = &cli.ConcurrentUi{ 398 Ui: &ColorizeUi{ 399 Colorize: m.Colorize(), 400 ErrorColor: "[red]", 401 WarnColor: "[yellow]", 402 Ui: m.oldUi, 403 }, 404 } 405 406 // If we support vars and the default var file exists, add it to 407 // the args... 408 m.autoKey = "" 409 if vars { 410 if _, err := os.Stat(DefaultVarsFilename); err == nil { 411 m.autoKey = "var-file-default" 412 args = append(args, "", "") 413 copy(args[2:], args[0:]) 414 args[0] = "-" + m.autoKey 415 args[1] = DefaultVarsFilename 416 } 417 418 if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil { 419 m.autoKey = "var-file-default" 420 args = append(args, "", "") 421 copy(args[2:], args[0:]) 422 args[0] = "-" + m.autoKey 423 args[1] = DefaultVarsFilename + ".json" 424 } 425 } 426 427 return args 428 } 429 430 // uiHook returns the UiHook to use with the context. 431 func (m *Meta) uiHook() *UiHook { 432 return &UiHook{ 433 Colorize: m.Colorize(), 434 Ui: m.Ui, 435 } 436 } 437 438 const ( 439 // ModuleDepthDefault is the default value for 440 // module depth, which can be overridden by flag 441 // or env var 442 ModuleDepthDefault = -1 443 444 // ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth. 445 ModuleDepthEnvVar = "TF_MODULE_DEPTH" 446 ) 447 448 func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) { 449 flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth") 450 if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" { 451 if md, err := strconv.Atoi(envVar); err == nil { 452 *moduleDepth = md 453 } 454 } 455 } 456 457 // contextOpts are the options used to load a context from a command. 458 type contextOpts struct { 459 // Path to the directory where the root module is. 460 Path string 461 462 // StatePath is the path to the state file. If this is empty, then 463 // no state will be loaded. It is also okay for this to be a path to 464 // a file that doesn't exist; it is assumed that this means that there 465 // is simply no state. 466 StatePath string 467 468 // GetMode is the module.GetMode to use when loading the module tree. 469 GetMode module.GetMode 470 471 // Set to true when running a destroy plan/apply. 472 Destroy bool 473 474 // Number of concurrent operations allowed 475 Parallelism int 476 }