github.com/justnom/terraform@v0.5.4-0.20180524223219-170a1530d1ca/commands.go (about) 1 package main 2 3 import ( 4 "log" 5 "os" 6 "os/signal" 7 8 "github.com/hashicorp/terraform/command" 9 pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery" 10 "github.com/hashicorp/terraform/svchost" 11 "github.com/hashicorp/terraform/svchost/auth" 12 "github.com/hashicorp/terraform/svchost/disco" 13 "github.com/mitchellh/cli" 14 ) 15 16 // runningInAutomationEnvName gives the name of an environment variable that 17 // can be set to any non-empty value in order to suppress certain messages 18 // that assume that Terraform is being run from a command prompt. 19 const runningInAutomationEnvName = "TF_IN_AUTOMATION" 20 21 // Commands is the mapping of all the available Terraform commands. 22 var Commands map[string]cli.CommandFactory 23 var PlumbingCommands map[string]struct{} 24 25 // Ui is the cli.Ui used for communicating to the outside world. 26 var Ui cli.Ui 27 28 const ( 29 ErrorPrefix = "e:" 30 OutputPrefix = "o:" 31 ) 32 33 func initCommands(config *Config) { 34 var inAutomation bool 35 if v := os.Getenv(runningInAutomationEnvName); v != "" { 36 inAutomation = true 37 } 38 39 credsSrc := credentialsSource(config) 40 services := disco.NewDisco() 41 services.SetCredentialsSource(credsSrc) 42 for userHost, hostConfig := range config.Hosts { 43 host, err := svchost.ForComparison(userHost) 44 if err != nil { 45 // We expect the config was already validated by the time we get 46 // here, so we'll just ignore invalid hostnames. 47 continue 48 } 49 services.ForceHostServices(host, hostConfig.Services) 50 } 51 52 dataDir := os.Getenv("TF_DATA_DIR") 53 54 meta := command.Meta{ 55 Color: true, 56 GlobalPluginDirs: globalPluginDirs(), 57 PluginOverrides: &PluginOverrides, 58 Ui: Ui, 59 60 Services: services, 61 Credentials: credsSrc, 62 63 RunningInAutomation: inAutomation, 64 PluginCacheDir: config.PluginCacheDir, 65 OverrideDataDir: dataDir, 66 67 ShutdownCh: makeShutdownCh(), 68 } 69 70 // The command list is included in the terraform -help 71 // output, which is in turn included in the docs at 72 // website/source/docs/commands/index.html.markdown; if you 73 // add, remove or reclassify commands then consider updating 74 // that to match. 75 76 PlumbingCommands = map[string]struct{}{ 77 "state": struct{}{}, // includes all subcommands 78 "debug": struct{}{}, // includes all subcommands 79 "force-unlock": struct{}{}, 80 } 81 82 Commands = map[string]cli.CommandFactory{ 83 "apply": func() (cli.Command, error) { 84 return &command.ApplyCommand{ 85 Meta: meta, 86 }, nil 87 }, 88 89 "console": func() (cli.Command, error) { 90 return &command.ConsoleCommand{ 91 Meta: meta, 92 }, nil 93 }, 94 95 "destroy": func() (cli.Command, error) { 96 return &command.ApplyCommand{ 97 Meta: meta, 98 Destroy: true, 99 }, nil 100 }, 101 102 "env": func() (cli.Command, error) { 103 return &command.WorkspaceCommand{ 104 Meta: meta, 105 LegacyName: true, 106 }, nil 107 }, 108 109 "env list": func() (cli.Command, error) { 110 return &command.WorkspaceListCommand{ 111 Meta: meta, 112 LegacyName: true, 113 }, nil 114 }, 115 116 "env select": func() (cli.Command, error) { 117 return &command.WorkspaceSelectCommand{ 118 Meta: meta, 119 LegacyName: true, 120 }, nil 121 }, 122 123 "env new": func() (cli.Command, error) { 124 return &command.WorkspaceNewCommand{ 125 Meta: meta, 126 LegacyName: true, 127 }, nil 128 }, 129 130 "env delete": func() (cli.Command, error) { 131 return &command.WorkspaceDeleteCommand{ 132 Meta: meta, 133 LegacyName: true, 134 }, nil 135 }, 136 137 "fmt": func() (cli.Command, error) { 138 return &command.FmtCommand{ 139 Meta: meta, 140 }, nil 141 }, 142 143 "get": func() (cli.Command, error) { 144 return &command.GetCommand{ 145 Meta: meta, 146 }, nil 147 }, 148 149 "graph": func() (cli.Command, error) { 150 return &command.GraphCommand{ 151 Meta: meta, 152 }, nil 153 }, 154 155 "import": func() (cli.Command, error) { 156 return &command.ImportCommand{ 157 Meta: meta, 158 }, nil 159 }, 160 161 "init": func() (cli.Command, error) { 162 return &command.InitCommand{ 163 Meta: meta, 164 }, nil 165 }, 166 167 "internal-plugin": func() (cli.Command, error) { 168 return &command.InternalPluginCommand{ 169 Meta: meta, 170 }, nil 171 }, 172 173 "output": func() (cli.Command, error) { 174 return &command.OutputCommand{ 175 Meta: meta, 176 }, nil 177 }, 178 179 "plan": func() (cli.Command, error) { 180 return &command.PlanCommand{ 181 Meta: meta, 182 }, nil 183 }, 184 185 "providers": func() (cli.Command, error) { 186 return &command.ProvidersCommand{ 187 Meta: meta, 188 }, nil 189 }, 190 191 "push": func() (cli.Command, error) { 192 return &command.PushCommand{ 193 Meta: meta, 194 }, nil 195 }, 196 197 "refresh": func() (cli.Command, error) { 198 return &command.RefreshCommand{ 199 Meta: meta, 200 }, nil 201 }, 202 203 "show": func() (cli.Command, error) { 204 return &command.ShowCommand{ 205 Meta: meta, 206 }, nil 207 }, 208 209 "taint": func() (cli.Command, error) { 210 return &command.TaintCommand{ 211 Meta: meta, 212 }, nil 213 }, 214 215 "validate": func() (cli.Command, error) { 216 return &command.ValidateCommand{ 217 Meta: meta, 218 }, nil 219 }, 220 221 "version": func() (cli.Command, error) { 222 return &command.VersionCommand{ 223 Meta: meta, 224 Revision: GitCommit, 225 Version: Version, 226 VersionPrerelease: VersionPrerelease, 227 CheckFunc: commandVersionCheck, 228 }, nil 229 }, 230 231 "untaint": func() (cli.Command, error) { 232 return &command.UntaintCommand{ 233 Meta: meta, 234 }, nil 235 }, 236 237 "workspace": func() (cli.Command, error) { 238 return &command.WorkspaceCommand{ 239 Meta: meta, 240 }, nil 241 }, 242 243 "workspace list": func() (cli.Command, error) { 244 return &command.WorkspaceListCommand{ 245 Meta: meta, 246 }, nil 247 }, 248 249 "workspace select": func() (cli.Command, error) { 250 return &command.WorkspaceSelectCommand{ 251 Meta: meta, 252 }, nil 253 }, 254 255 "workspace show": func() (cli.Command, error) { 256 return &command.WorkspaceShowCommand{ 257 Meta: meta, 258 }, nil 259 }, 260 261 "workspace new": func() (cli.Command, error) { 262 return &command.WorkspaceNewCommand{ 263 Meta: meta, 264 }, nil 265 }, 266 267 "workspace delete": func() (cli.Command, error) { 268 return &command.WorkspaceDeleteCommand{ 269 Meta: meta, 270 }, nil 271 }, 272 273 //----------------------------------------------------------- 274 // Plumbing 275 //----------------------------------------------------------- 276 277 "debug": func() (cli.Command, error) { 278 return &command.DebugCommand{ 279 Meta: meta, 280 }, nil 281 }, 282 283 "debug json2dot": func() (cli.Command, error) { 284 return &command.DebugJSON2DotCommand{ 285 Meta: meta, 286 }, nil 287 }, 288 289 "force-unlock": func() (cli.Command, error) { 290 return &command.UnlockCommand{ 291 Meta: meta, 292 }, nil 293 }, 294 295 "state": func() (cli.Command, error) { 296 return &command.StateCommand{}, nil 297 }, 298 299 "state list": func() (cli.Command, error) { 300 return &command.StateListCommand{ 301 Meta: meta, 302 }, nil 303 }, 304 305 "state rm": func() (cli.Command, error) { 306 return &command.StateRmCommand{ 307 StateMeta: command.StateMeta{ 308 Meta: meta, 309 }, 310 }, nil 311 }, 312 313 "state mv": func() (cli.Command, error) { 314 return &command.StateMvCommand{ 315 StateMeta: command.StateMeta{ 316 Meta: meta, 317 }, 318 }, nil 319 }, 320 321 "state pull": func() (cli.Command, error) { 322 return &command.StatePullCommand{ 323 Meta: meta, 324 }, nil 325 }, 326 327 "state push": func() (cli.Command, error) { 328 return &command.StatePushCommand{ 329 Meta: meta, 330 }, nil 331 }, 332 333 "state show": func() (cli.Command, error) { 334 return &command.StateShowCommand{ 335 Meta: meta, 336 }, nil 337 }, 338 } 339 } 340 341 // makeShutdownCh creates an interrupt listener and returns a channel. 342 // A message will be sent on the channel for every interrupt received. 343 func makeShutdownCh() <-chan struct{} { 344 resultCh := make(chan struct{}) 345 346 signalCh := make(chan os.Signal, 4) 347 signal.Notify(signalCh, ignoreSignals...) 348 signal.Notify(signalCh, forwardSignals...) 349 go func() { 350 for { 351 <-signalCh 352 resultCh <- struct{}{} 353 } 354 }() 355 356 return resultCh 357 } 358 359 func credentialsSource(config *Config) auth.CredentialsSource { 360 creds := auth.NoCredentials 361 if len(config.Credentials) > 0 { 362 staticTable := map[svchost.Hostname]map[string]interface{}{} 363 for userHost, creds := range config.Credentials { 364 host, err := svchost.ForComparison(userHost) 365 if err != nil { 366 // We expect the config was already validated by the time we get 367 // here, so we'll just ignore invalid hostnames. 368 continue 369 } 370 staticTable[host] = creds 371 } 372 creds = auth.StaticCredentialsSource(staticTable) 373 } 374 375 for helperType, helperConfig := range config.CredentialsHelpers { 376 log.Printf("[DEBUG] Searching for credentials helper named %q", helperType) 377 available := pluginDiscovery.FindPlugins("credentials", globalPluginDirs()) 378 available = available.WithName(helperType) 379 if available.Count() == 0 { 380 log.Printf("[ERROR] Unable to find credentials helper %q; ignoring", helperType) 381 break 382 } 383 384 selected := available.Newest() 385 386 helperSource := auth.HelperProgramCredentialsSource(selected.Path, helperConfig.Args...) 387 creds = auth.Credentials{ 388 creds, 389 auth.CachingCredentialsSource(helperSource), // cached because external operation may be slow/expensive 390 } 391 392 // There should only be zero or one "credentials_helper" blocks. We 393 // assume that the config was validated earlier and so we don't check 394 // for extras here. 395 break 396 } 397 398 return creds 399 }