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