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