github.com/nevins-b/terraform@v0.3.8-0.20170215184714-bbae22007d5a/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "runtime" 10 "strings" 11 "sync" 12 13 "github.com/hashicorp/go-plugin" 14 "github.com/hashicorp/terraform/helper/logging" 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mattn/go-colorable" 17 "github.com/mattn/go-shellwords" 18 "github.com/mitchellh/cli" 19 "github.com/mitchellh/panicwrap" 20 "github.com/mitchellh/prefixedio" 21 ) 22 23 const ( 24 // EnvCLI is the environment variable name to set additional CLI args. 25 EnvCLI = "TF_CLI_ARGS" 26 ) 27 28 func main() { 29 // Override global prefix set by go-dynect during init() 30 log.SetPrefix("") 31 os.Exit(realMain()) 32 } 33 34 func realMain() int { 35 var wrapConfig panicwrap.WrapConfig 36 37 // don't re-exec terraform as a child process for easier debugging 38 if os.Getenv("TF_FORK") == "0" { 39 return wrappedMain() 40 } 41 42 if !panicwrap.Wrapped(&wrapConfig) { 43 // Determine where logs should go in general (requested by the user) 44 logWriter, err := logging.LogOutput() 45 if err != nil { 46 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 47 return 1 48 } 49 50 // We always send logs to a temporary file that we use in case 51 // there is a panic. Otherwise, we delete it. 52 logTempFile, err := ioutil.TempFile("", "terraform-log") 53 if err != nil { 54 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 55 return 1 56 } 57 defer os.Remove(logTempFile.Name()) 58 defer logTempFile.Close() 59 60 // Setup the prefixed readers that send data properly to 61 // stdout/stderr. 62 doneCh := make(chan struct{}) 63 outR, outW := io.Pipe() 64 go copyOutput(outR, doneCh) 65 66 // Create the configuration for panicwrap and wrap our executable 67 wrapConfig.Handler = panicHandler(logTempFile) 68 wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) 69 wrapConfig.Stdout = outW 70 wrapConfig.IgnoreSignals = ignoreSignals 71 wrapConfig.ForwardSignals = forwardSignals 72 exitStatus, err := panicwrap.Wrap(&wrapConfig) 73 if err != nil { 74 fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err) 75 return 1 76 } 77 78 // If >= 0, we're the parent, so just exit 79 if exitStatus >= 0 { 80 // Close the stdout writer so that our copy process can finish 81 outW.Close() 82 83 // Wait for the output copying to finish 84 <-doneCh 85 86 return exitStatus 87 } 88 89 // We're the child, so just close the tempfile we made in order to 90 // save file handles since the tempfile is only used by the parent. 91 logTempFile.Close() 92 } 93 94 // Call the real main 95 return wrappedMain() 96 } 97 98 func wrappedMain() int { 99 // We always need to close the DebugInfo before we exit. 100 defer terraform.CloseDebugInfo() 101 102 log.SetOutput(os.Stderr) 103 log.Printf( 104 "[INFO] Terraform version: %s %s %s", 105 Version, VersionPrerelease, GitCommit) 106 log.Printf("[INFO] CLI args: %#v", os.Args) 107 108 // Load the configuration 109 config := BuiltinConfig 110 if err := config.Discover(Ui); err != nil { 111 Ui.Error(fmt.Sprintf("Error discovering plugins: %s", err)) 112 return 1 113 } 114 115 // Load the configuration file if we have one, that can be used to 116 // define extra providers and provisioners. 117 clicfgFile, err := cliConfigFile() 118 if err != nil { 119 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 120 return 1 121 } 122 123 if clicfgFile != "" { 124 usrcfg, err := LoadConfig(clicfgFile) 125 if err != nil { 126 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 127 return 1 128 } 129 130 config = *config.Merge(usrcfg) 131 } 132 133 // Run checkpoint 134 go runCheckpoint(&config) 135 136 // Make sure we clean up any managed plugins at the end of this 137 defer plugin.CleanupClients() 138 139 // Get the command line args. 140 args := os.Args[1:] 141 142 // Build the CLI so far, we do this so we can query the subcommand. 143 cliRunner := &cli.CLI{ 144 Args: args, 145 Commands: Commands, 146 HelpFunc: helpFunc, 147 HelpWriter: os.Stdout, 148 } 149 150 // Prefix the args with any args from the EnvCLI 151 args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args) 152 if err != nil { 153 Ui.Error(err.Error()) 154 return 1 155 } 156 157 // Prefix the args with any args from the EnvCLI targeting this command 158 suffix := strings.Replace(strings.Replace( 159 cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1) 160 args, err = mergeEnvArgs( 161 fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args) 162 if err != nil { 163 Ui.Error(err.Error()) 164 return 1 165 } 166 167 // We shortcut "--version" and "-v" to just show the version 168 for _, arg := range args { 169 if arg == "-v" || arg == "-version" || arg == "--version" { 170 newArgs := make([]string, len(args)+1) 171 newArgs[0] = "version" 172 copy(newArgs[1:], args) 173 args = newArgs 174 break 175 } 176 } 177 178 // Rebuild the CLI with any modified args. 179 log.Printf("[INFO] CLI command args: %#v", args) 180 cliRunner = &cli.CLI{ 181 Args: args, 182 Commands: Commands, 183 HelpFunc: helpFunc, 184 HelpWriter: os.Stdout, 185 } 186 187 // Initialize the TFConfig settings for the commands... 188 ContextOpts.Providers = config.ProviderFactories() 189 ContextOpts.Provisioners = config.ProvisionerFactories() 190 191 exitCode, err := cliRunner.Run() 192 if err != nil { 193 Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error())) 194 return 1 195 } 196 197 return exitCode 198 } 199 200 func cliConfigFile() (string, error) { 201 mustExist := true 202 configFilePath := os.Getenv("TERRAFORM_CONFIG") 203 if configFilePath == "" { 204 var err error 205 configFilePath, err = ConfigFile() 206 mustExist = false 207 208 if err != nil { 209 log.Printf( 210 "[ERROR] Error detecting default CLI config file path: %s", 211 err) 212 } 213 } 214 215 log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath) 216 f, err := os.Open(configFilePath) 217 if err == nil { 218 f.Close() 219 return configFilePath, nil 220 } 221 222 if mustExist || !os.IsNotExist(err) { 223 return "", err 224 } 225 226 log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.") 227 return "", nil 228 } 229 230 // copyOutput uses output prefixes to determine whether data on stdout 231 // should go to stdout or stderr. This is due to panicwrap using stderr 232 // as the log and error channel. 233 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 234 defer close(doneCh) 235 236 pr, err := prefixedio.NewReader(r) 237 if err != nil { 238 panic(err) 239 } 240 241 stderrR, err := pr.Prefix(ErrorPrefix) 242 if err != nil { 243 panic(err) 244 } 245 stdoutR, err := pr.Prefix(OutputPrefix) 246 if err != nil { 247 panic(err) 248 } 249 defaultR, err := pr.Prefix("") 250 if err != nil { 251 panic(err) 252 } 253 254 var stdout io.Writer = os.Stdout 255 var stderr io.Writer = os.Stderr 256 257 if runtime.GOOS == "windows" { 258 stdout = colorable.NewColorableStdout() 259 stderr = colorable.NewColorableStderr() 260 } 261 262 var wg sync.WaitGroup 263 wg.Add(3) 264 go func() { 265 defer wg.Done() 266 io.Copy(stderr, stderrR) 267 }() 268 go func() { 269 defer wg.Done() 270 io.Copy(stdout, stdoutR) 271 }() 272 go func() { 273 defer wg.Done() 274 io.Copy(stdout, defaultR) 275 }() 276 277 wg.Wait() 278 } 279 280 func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) { 281 v := os.Getenv(envName) 282 if v == "" { 283 return args, nil 284 } 285 286 log.Printf("[INFO] %s value: %q", envName, v) 287 extra, err := shellwords.Parse(v) 288 if err != nil { 289 return nil, fmt.Errorf( 290 "Error parsing extra CLI args from %s: %s", 291 envName, err) 292 } 293 294 // Find the command to look for in the args. If there is a space, 295 // we need to find the last part. 296 search := cmd 297 if idx := strings.LastIndex(search, " "); idx >= 0 { 298 search = cmd[idx+1:] 299 } 300 301 // Find the index to place the flags. We put them exactly 302 // after the first non-flag arg. 303 idx := -1 304 for i, v := range args { 305 if v == search { 306 idx = i 307 break 308 } 309 } 310 311 // idx points to the exact arg that isn't a flag. We increment 312 // by one so that all the copying below expects idx to be the 313 // insertion point. 314 idx++ 315 316 // Copy the args 317 newArgs := make([]string, len(args)+len(extra)) 318 copy(newArgs, args[:idx]) 319 copy(newArgs[idx:], extra) 320 copy(newArgs[len(extra)+idx:], args[idx:]) 321 return newArgs, nil 322 }