github.com/andresvia/terraform@v0.6.15-0.20160412045437-d51c75946785/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "runtime" 10 "sync" 11 12 "github.com/hashicorp/terraform/helper/logging" 13 "github.com/hashicorp/terraform/plugin" 14 "github.com/mattn/go-colorable" 15 "github.com/mitchellh/cli" 16 "github.com/mitchellh/panicwrap" 17 "github.com/mitchellh/prefixedio" 18 ) 19 20 func main() { 21 os.Exit(realMain()) 22 } 23 24 func realMain() int { 25 var wrapConfig panicwrap.WrapConfig 26 27 if !panicwrap.Wrapped(&wrapConfig) { 28 // Determine where logs should go in general (requested by the user) 29 logWriter, err := logging.LogOutput() 30 if err != nil { 31 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 32 return 1 33 } 34 35 // We always send logs to a temporary file that we use in case 36 // there is a panic. Otherwise, we delete it. 37 logTempFile, err := ioutil.TempFile("", "terraform-log") 38 if err != nil { 39 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 40 return 1 41 } 42 defer os.Remove(logTempFile.Name()) 43 defer logTempFile.Close() 44 45 // Setup the prefixed readers that send data properly to 46 // stdout/stderr. 47 doneCh := make(chan struct{}) 48 outR, outW := io.Pipe() 49 go copyOutput(outR, doneCh) 50 51 // Create the configuration for panicwrap and wrap our executable 52 wrapConfig.Handler = panicHandler(logTempFile) 53 wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) 54 wrapConfig.Stdout = outW 55 exitStatus, err := panicwrap.Wrap(&wrapConfig) 56 if err != nil { 57 fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err) 58 return 1 59 } 60 61 // If >= 0, we're the parent, so just exit 62 if exitStatus >= 0 { 63 // Close the stdout writer so that our copy process can finish 64 outW.Close() 65 66 // Wait for the output copying to finish 67 <-doneCh 68 69 return exitStatus 70 } 71 72 // We're the child, so just close the tempfile we made in order to 73 // save file handles since the tempfile is only used by the parent. 74 logTempFile.Close() 75 } 76 77 // Call the real main 78 return wrappedMain() 79 } 80 81 func wrappedMain() int { 82 log.SetOutput(os.Stderr) 83 log.Printf( 84 "[INFO] Terraform version: %s %s %s", 85 Version, VersionPrerelease, GitCommit) 86 87 // Load the configuration 88 config := BuiltinConfig 89 if err := config.Discover(); err != nil { 90 Ui.Error(fmt.Sprintf("Error discovering plugins: %s", err)) 91 return 1 92 } 93 94 // Run checkpoint 95 go runCheckpoint(&config) 96 97 // Make sure we clean up any managed plugins at the end of this 98 defer plugin.CleanupClients() 99 100 // Get the command line args. We shortcut "--version" and "-v" to 101 // just show the version. 102 args := os.Args[1:] 103 for _, arg := range args { 104 if arg == "-v" || arg == "-version" || arg == "--version" { 105 newArgs := make([]string, len(args)+1) 106 newArgs[0] = "version" 107 copy(newArgs[1:], args) 108 args = newArgs 109 break 110 } 111 } 112 113 cli := &cli.CLI{ 114 Args: args, 115 Commands: Commands, 116 HelpFunc: cli.BasicHelpFunc("terraform"), 117 HelpWriter: os.Stdout, 118 } 119 120 // Load the configuration file if we have one, that can be used to 121 // define extra providers and provisioners. 122 clicfgFile, err := cliConfigFile() 123 if err != nil { 124 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 125 return 1 126 } 127 128 if clicfgFile != "" { 129 usrcfg, err := LoadConfig(clicfgFile) 130 if err != nil { 131 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 132 return 1 133 } 134 135 config = *config.Merge(usrcfg) 136 } 137 138 // Initialize the TFConfig settings for the commands... 139 ContextOpts.Providers = config.ProviderFactories() 140 ContextOpts.Provisioners = config.ProvisionerFactories() 141 142 exitCode, err := cli.Run() 143 if err != nil { 144 Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error())) 145 return 1 146 } 147 148 return exitCode 149 } 150 151 func cliConfigFile() (string, error) { 152 mustExist := true 153 configFilePath := os.Getenv("TERRAFORM_CONFIG") 154 if configFilePath == "" { 155 var err error 156 configFilePath, err = ConfigFile() 157 mustExist = false 158 159 if err != nil { 160 log.Printf( 161 "[ERROR] Error detecting default CLI config file path: %s", 162 err) 163 } 164 } 165 166 log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath) 167 f, err := os.Open(configFilePath) 168 if err == nil { 169 f.Close() 170 return configFilePath, nil 171 } 172 173 if mustExist || !os.IsNotExist(err) { 174 return "", err 175 } 176 177 log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.") 178 return "", nil 179 } 180 181 // copyOutput uses output prefixes to determine whether data on stdout 182 // should go to stdout or stderr. This is due to panicwrap using stderr 183 // as the log and error channel. 184 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 185 defer close(doneCh) 186 187 pr, err := prefixedio.NewReader(r) 188 if err != nil { 189 panic(err) 190 } 191 192 stderrR, err := pr.Prefix(ErrorPrefix) 193 if err != nil { 194 panic(err) 195 } 196 stdoutR, err := pr.Prefix(OutputPrefix) 197 if err != nil { 198 panic(err) 199 } 200 defaultR, err := pr.Prefix("") 201 if err != nil { 202 panic(err) 203 } 204 205 var stdout io.Writer = os.Stdout 206 var stderr io.Writer = os.Stderr 207 208 if runtime.GOOS == "windows" { 209 stdout = colorable.NewColorableStdout() 210 stderr = colorable.NewColorableStderr() 211 } 212 213 var wg sync.WaitGroup 214 wg.Add(3) 215 go func() { 216 defer wg.Done() 217 io.Copy(stderr, stderrR) 218 }() 219 go func() { 220 defer wg.Done() 221 io.Copy(stdout, stdoutR) 222 }() 223 go func() { 224 defer wg.Done() 225 io.Copy(stdout, defaultR) 226 }() 227 228 wg.Wait() 229 }