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