github.com/lamielle/terraform@v0.3.2-0.20141121070651-81f008ba53d5/command/meta.go (about) 1 package command 2 3 import ( 4 "bufio" 5 "flag" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 11 "github.com/hashicorp/terraform/config/module" 12 "github.com/hashicorp/terraform/terraform" 13 "github.com/mitchellh/cli" 14 "github.com/mitchellh/colorstring" 15 ) 16 17 // Meta are the meta-options that are available on all or most commands. 18 type Meta struct { 19 Color bool 20 ContextOpts *terraform.ContextOpts 21 Ui cli.Ui 22 23 // State read when calling `Context`. This is available after calling 24 // `Context`. 25 state *terraform.State 26 27 // This can be set by the command itself to provide extra hooks. 28 extraHooks []terraform.Hook 29 30 // This can be set by tests to change some directories 31 dataDir string 32 33 // Variables for the context (private) 34 autoKey string 35 autoVariables map[string]string 36 input bool 37 variables map[string]string 38 39 color bool 40 oldUi cli.Ui 41 } 42 43 // Colorize returns the colorization structure for a command. 44 func (m *Meta) Colorize() *colorstring.Colorize { 45 return &colorstring.Colorize{ 46 Colors: colorstring.DefaultColors, 47 Disable: !m.color, 48 Reset: true, 49 } 50 } 51 52 // Context returns a Terraform Context taking into account the context 53 // options used to initialize this meta configuration. 54 func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) { 55 opts := m.contextOpts() 56 57 // First try to just read the plan directly from the path given. 58 f, err := os.Open(copts.Path) 59 if err == nil { 60 plan, err := terraform.ReadPlan(f) 61 f.Close() 62 if err == nil { 63 if len(m.variables) > 0 { 64 return nil, false, fmt.Errorf( 65 "You can't set variables with the '-var' or '-var-file' flag\n" + 66 "when you're applying a plan file. The variables used when\n" + 67 "the plan was created will be used. If you wish to use different\n" + 68 "variable values, create a new plan file.") 69 } 70 71 return plan.Context(opts), true, nil 72 } 73 } 74 75 // Load up the state 76 var state *terraform.State 77 if copts.StatePath != "" { 78 f, err := os.Open(copts.StatePath) 79 if err != nil && os.IsNotExist(err) { 80 // If the state file doesn't exist, it is okay, since it 81 // is probably a new infrastructure. 82 err = nil 83 } else if err == nil { 84 state, err = terraform.ReadState(f) 85 f.Close() 86 } 87 88 if err != nil { 89 return nil, false, fmt.Errorf("Error loading state: %s", err) 90 } 91 } 92 93 // Store the loaded state 94 m.state = state 95 96 // Load the root module 97 mod, err := module.NewTreeModule("", copts.Path) 98 if err != nil { 99 return nil, false, fmt.Errorf("Error loading config: %s", err) 100 } 101 102 dataDir := DefaultDataDirectory 103 if m.dataDir != "" { 104 dataDir = m.dataDir 105 } 106 err = mod.Load(m.moduleStorage(dataDir), copts.GetMode) 107 if err != nil { 108 return nil, false, fmt.Errorf("Error downloading modules: %s", err) 109 } 110 111 opts.Module = mod 112 opts.State = state 113 ctx := terraform.NewContext(opts) 114 return ctx, false, nil 115 } 116 117 // InputMode returns the type of input we should ask for in the form of 118 // terraform.InputMode which is passed directly to Context.Input. 119 func (m *Meta) InputMode() terraform.InputMode { 120 if test || !m.input { 121 return 0 122 } 123 124 var mode terraform.InputMode 125 mode |= terraform.InputModeProvider 126 if len(m.variables) == 0 && m.autoKey == "" { 127 mode |= terraform.InputModeVar 128 } 129 130 return mode 131 } 132 133 // UIInput returns a UIInput object to be used for asking for input. 134 func (m *Meta) UIInput() terraform.UIInput { 135 return &UIInput{ 136 Colorize: m.Colorize(), 137 } 138 } 139 140 // contextOpts returns the options to use to initialize a Terraform 141 // context with the settings from this Meta. 142 func (m *Meta) contextOpts() *terraform.ContextOpts { 143 var opts terraform.ContextOpts = *m.ContextOpts 144 opts.Hooks = make( 145 []terraform.Hook, 146 len(m.ContextOpts.Hooks)+len(m.extraHooks)+1) 147 opts.Hooks[0] = m.uiHook() 148 copy(opts.Hooks[1:], m.ContextOpts.Hooks) 149 copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks) 150 151 vs := make(map[string]string) 152 for k, v := range opts.Variables { 153 vs[k] = v 154 } 155 for k, v := range m.autoVariables { 156 vs[k] = v 157 } 158 for k, v := range m.variables { 159 vs[k] = v 160 } 161 opts.Variables = vs 162 opts.UIInput = m.UIInput() 163 164 return &opts 165 } 166 167 // flags adds the meta flags to the given FlagSet. 168 func (m *Meta) flagSet(n string) *flag.FlagSet { 169 f := flag.NewFlagSet(n, flag.ContinueOnError) 170 f.BoolVar(&m.input, "input", true, "input") 171 f.Var((*FlagVar)(&m.variables), "var", "variables") 172 f.Var((*FlagVarFile)(&m.variables), "var-file", "variable file") 173 174 if m.autoKey != "" { 175 f.Var((*FlagVarFile)(&m.autoVariables), m.autoKey, "variable file") 176 } 177 178 // Create an io.Writer that writes to our Ui properly for errors. 179 // This is kind of a hack, but it does the job. Basically: create 180 // a pipe, use a scanner to break it into lines, and output each line 181 // to the UI. Do this forever. 182 errR, errW := io.Pipe() 183 errScanner := bufio.NewScanner(errR) 184 go func() { 185 for errScanner.Scan() { 186 m.Ui.Error(errScanner.Text()) 187 } 188 }() 189 f.SetOutput(errW) 190 191 return f 192 } 193 194 // moduleStorage returns the module.Storage implementation used to store 195 // modules for commands. 196 func (m *Meta) moduleStorage(root string) module.Storage { 197 return &uiModuleStorage{ 198 Storage: &module.FolderStorage{ 199 StorageDir: filepath.Join(root, "modules"), 200 }, 201 Ui: m.Ui, 202 } 203 } 204 205 // process will process the meta-parameters out of the arguments. This 206 // will potentially modify the args in-place. It will return the resulting 207 // slice. 208 // 209 // vars says whether or not we support variables. 210 func (m *Meta) process(args []string, vars bool) []string { 211 // We do this so that we retain the ability to technically call 212 // process multiple times, even if we have no plans to do so 213 if m.oldUi != nil { 214 m.Ui = m.oldUi 215 } 216 217 // Set colorization 218 m.color = m.Color 219 for i, v := range args { 220 if v == "-no-color" { 221 m.color = false 222 args = append(args[:i], args[i+1:]...) 223 break 224 } 225 } 226 227 // Set the UI 228 m.oldUi = m.Ui 229 m.Ui = &cli.ConcurrentUi{ 230 Ui: &ColorizeUi{ 231 Colorize: m.Colorize(), 232 ErrorColor: "[red]", 233 Ui: m.oldUi, 234 }, 235 } 236 237 // If we support vars and the default var file exists, add it to 238 // the args... 239 m.autoKey = "" 240 if vars { 241 if _, err := os.Stat(DefaultVarsFilename); err == nil { 242 m.autoKey = "var-file-default" 243 args = append(args, "", "") 244 copy(args[2:], args[0:]) 245 args[0] = "-" + m.autoKey 246 args[1] = DefaultVarsFilename 247 } 248 } 249 250 return args 251 } 252 253 // uiHook returns the UiHook to use with the context. 254 func (m *Meta) uiHook() *UiHook { 255 return &UiHook{ 256 Colorize: m.Colorize(), 257 Ui: m.Ui, 258 } 259 } 260 261 // contextOpts are the options used to load a context from a command. 262 type contextOpts struct { 263 // Path to the directory where the root module is. 264 Path string 265 266 // StatePath is the path to the state file. If this is empty, then 267 // no state will be loaded. It is also okay for this to be a path to 268 // a file that doesn't exist; it is assumed that this means that there 269 // is simply no state. 270 StatePath string 271 272 // GetMode is the module.GetMode to use when loading the module tree. 273 GetMode module.GetMode 274 }