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