package main import ( "fmt" "io" "io/ioutil" "log" "os" "github.com/hashicorp/terraform/plugin" "github.com/mitchellh/cli" "github.com/mitchellh/panicwrap" "github.com/mitchellh/prefixedio" ) func main() { os.Exit(realMain()) } func realMain() int { var wrapConfig panicwrap.WrapConfig if !panicwrap.Wrapped(&wrapConfig) { // Determine where logs should go in general (requested by the user) logWriter, err := logOutput() if err != nil { fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) return 1 } if logWriter == nil { logWriter = ioutil.Discard } // We always send logs to a temporary file that we use in case // there is a panic. Otherwise, we delete it. logTempFile, err := ioutil.TempFile("", "terraform-log") if err != nil { fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) return 1 } defer os.Remove(logTempFile.Name()) defer logTempFile.Close() // Tell the logger to log to this file os.Setenv(EnvLog, "") os.Setenv(EnvLogFile, "") // Setup the prefixed readers that send data properly to // stdout/stderr. outR, outW := io.Pipe() go copyOutput(outR) // Create the configuration for panicwrap and wrap our executable wrapConfig.Handler = panicHandler(logTempFile) wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) wrapConfig.Stdout = outW exitStatus, err := panicwrap.Wrap(&wrapConfig) if err != nil { fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err) return 1 } // If >= 0, we're the parent, so just exit if exitStatus >= 0 { return exitStatus } // We're the child, so just close the tempfile we made in order to // save file handles since the tempfile is only used by the parent. logTempFile.Close() } // Call the real main return wrappedMain() } func wrappedMain() int { log.SetOutput(os.Stderr) // Load the configuration config := BuiltinConfig // Make sure we clean up any managed plugins at the end of this defer plugin.CleanupClients() // Initialize the TFConfig settings for the commands... ContextOpts.Providers = config.ProviderFactories() ContextOpts.Provisioners = config.ProvisionerFactories() // Get the command line args. We shortcut "--version" and "-v" to // just show the version. args := os.Args[1:] for _, arg := range args { if arg == "-v" || arg == "-version" || arg == "--version" { newArgs := make([]string, len(args)+1) newArgs[0] = "version" copy(newArgs[1:], args) args = newArgs break } } cli := &cli.CLI{ Args: args, Commands: Commands, HelpFunc: cli.BasicHelpFunc("terraform"), HelpWriter: os.Stdout, } clicfgFile, err := cliConfigFile() if err != nil { fmt.Fprintf(os.Stderr, "Error loading CLI configuration: \n\n%s\n", err) return 1 } usrcfg, err := LoadConfig(clicfgFile) if err != nil { fmt.Fprintf(os.Stderr, "Error loading CLI configuration: \n\n%s\n", err) return 1 } config = *config.Merge(usrcfg) // Initialize the TFConfig settings for the commands... ContextOpts.Providers = config.ProviderFactories() ContextOpts.Provisioners = config.ProvisionerFactories() exitCode, err := cli.Run() if err != nil { fmt.Fprintf(os.Stderr, "Error executing CLI: %s\n", err.Error()) return 1 } return exitCode } func cliConfigFile() (string, error) { mustExist := true configFilePath := os.Getenv("TERRAFORM_CONFIG") if configFilePath == "" { var err error configFilePath, err = configFile() mustExist = false if err != nil { log.Printf("Error detecting default CLI config file path: %s", err) } } log.Printf("Attempting to open CLI config file: %s", configFilePath) f, err := os.Open(configFilePath) if err != nil { if !os.IsNotExist(err) { return "", err } if mustExist { return "", err } log.Println("File doesn't exist, but doesn't need to. Ignoring.") return "", nil } defer f.Close() return configFilePath, nil } // copyOutput uses output prefixes to determine whether data on stdout // should go to stdout or stderr. This is due to panicwrap using stderr // as the log and error channel. func copyOutput(r io.Reader) { pr, err := prefixedio.NewReader(r) if err != nil { panic(err) } stderrR, err := pr.Prefix(ErrorPrefix) if err != nil { panic(err) } stdoutR, err := pr.Prefix(OutputPrefix) if err != nil { panic(err) } defaultR, err := pr.Prefix("") if err != nil { panic(err) } go io.Copy(os.Stderr, stderrR) go io.Copy(os.Stdout, stdoutR) go io.Copy(os.Stdout, defaultR) }