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