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