github.com/jd3nn1s/terraform@v0.9.6-0.20170906225847-13878347b7a1/main.go (about) 1 package main 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 "log" 8 "os" 9 "runtime" 10 "strings" 11 "sync" 12 13 "github.com/hashicorp/go-plugin" 14 "github.com/hashicorp/terraform/helper/logging" 15 "github.com/hashicorp/terraform/terraform" 16 "github.com/mattn/go-colorable" 17 "github.com/mattn/go-shellwords" 18 "github.com/mitchellh/cli" 19 "github.com/mitchellh/panicwrap" 20 "github.com/mitchellh/prefixedio" 21 ) 22 23 const ( 24 // EnvCLI is the environment variable name to set additional CLI args. 25 EnvCLI = "TF_CLI_ARGS" 26 ) 27 28 func main() { 29 // Override global prefix set by go-dynect during init() 30 log.SetPrefix("") 31 os.Exit(realMain()) 32 } 33 34 func realMain() int { 35 var wrapConfig panicwrap.WrapConfig 36 37 // don't re-exec terraform as a child process for easier debugging 38 if os.Getenv("TF_FORK") == "0" { 39 return wrappedMain() 40 } 41 42 if !panicwrap.Wrapped(&wrapConfig) { 43 // Determine where logs should go in general (requested by the user) 44 logWriter, err := logging.LogOutput() 45 if err != nil { 46 fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err) 47 return 1 48 } 49 50 // We always send logs to a temporary file that we use in case 51 // there is a panic. Otherwise, we delete it. 52 logTempFile, err := ioutil.TempFile("", "terraform-log") 53 if err != nil { 54 fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err) 55 return 1 56 } 57 defer os.Remove(logTempFile.Name()) 58 defer logTempFile.Close() 59 60 // Setup the prefixed readers that send data properly to 61 // stdout/stderr. 62 doneCh := make(chan struct{}) 63 outR, outW := io.Pipe() 64 go copyOutput(outR, doneCh) 65 66 // Create the configuration for panicwrap and wrap our executable 67 wrapConfig.Handler = panicHandler(logTempFile) 68 wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter) 69 wrapConfig.Stdout = outW 70 wrapConfig.IgnoreSignals = ignoreSignals 71 wrapConfig.ForwardSignals = forwardSignals 72 exitStatus, err := panicwrap.Wrap(&wrapConfig) 73 if err != nil { 74 fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err) 75 return 1 76 } 77 78 // If >= 0, we're the parent, so just exit 79 if exitStatus >= 0 { 80 // Close the stdout writer so that our copy process can finish 81 outW.Close() 82 83 // Wait for the output copying to finish 84 <-doneCh 85 86 return exitStatus 87 } 88 89 // We're the child, so just close the tempfile we made in order to 90 // save file handles since the tempfile is only used by the parent. 91 logTempFile.Close() 92 } 93 94 // Call the real main 95 return wrappedMain() 96 } 97 98 func wrappedMain() int { 99 // We always need to close the DebugInfo before we exit. 100 defer terraform.CloseDebugInfo() 101 102 log.SetOutput(os.Stderr) 103 log.Printf( 104 "[INFO] Terraform version: %s %s %s", 105 Version, VersionPrerelease, GitCommit) 106 log.Printf("[INFO] Go runtime version: %s", runtime.Version()) 107 log.Printf("[INFO] CLI args: %#v", os.Args) 108 109 // Load the configuration 110 config := BuiltinConfig 111 112 // Load the configuration file if we have one, that can be used to 113 // define extra providers and provisioners. 114 clicfgFile, err := cliConfigFile() 115 if err != nil { 116 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 117 return 1 118 } 119 120 if clicfgFile != "" { 121 usrcfg, err := LoadConfig(clicfgFile) 122 if err != nil { 123 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 124 return 1 125 } 126 127 config = *config.Merge(usrcfg) 128 } 129 130 // Run checkpoint 131 go runCheckpoint(&config) 132 133 // Make sure we clean up any managed plugins at the end of this 134 defer plugin.CleanupClients() 135 136 // Get the command line args. 137 args := os.Args[1:] 138 139 // Build the CLI so far, we do this so we can query the subcommand. 140 cliRunner := &cli.CLI{ 141 Args: args, 142 Commands: Commands, 143 HelpFunc: helpFunc, 144 HelpWriter: os.Stdout, 145 } 146 147 // Prefix the args with any args from the EnvCLI 148 args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args) 149 if err != nil { 150 Ui.Error(err.Error()) 151 return 1 152 } 153 154 // Prefix the args with any args from the EnvCLI targeting this command 155 suffix := strings.Replace(strings.Replace( 156 cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1) 157 args, err = mergeEnvArgs( 158 fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args) 159 if err != nil { 160 Ui.Error(err.Error()) 161 return 1 162 } 163 164 // We shortcut "--version" and "-v" to just show the version 165 for _, arg := range args { 166 if arg == "-v" || arg == "-version" || arg == "--version" { 167 newArgs := make([]string, len(args)+1) 168 newArgs[0] = "version" 169 copy(newArgs[1:], args) 170 args = newArgs 171 break 172 } 173 } 174 175 // Rebuild the CLI with any modified args. 176 log.Printf("[INFO] CLI command args: %#v", args) 177 cliRunner = &cli.CLI{ 178 Args: args, 179 Commands: Commands, 180 HelpFunc: helpFunc, 181 HelpWriter: os.Stdout, 182 } 183 184 // Pass in the overriding plugin paths from config 185 PluginOverrides.Providers = config.Providers 186 PluginOverrides.Provisioners = config.Provisioners 187 188 exitCode, err := cliRunner.Run() 189 if err != nil { 190 Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error())) 191 return 1 192 } 193 194 return exitCode 195 } 196 197 func cliConfigFile() (string, error) { 198 mustExist := true 199 configFilePath := os.Getenv("TERRAFORM_CONFIG") 200 if configFilePath == "" { 201 var err error 202 configFilePath, err = ConfigFile() 203 mustExist = false 204 205 if err != nil { 206 log.Printf( 207 "[ERROR] Error detecting default CLI config file path: %s", 208 err) 209 } 210 } 211 212 log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath) 213 f, err := os.Open(configFilePath) 214 if err == nil { 215 f.Close() 216 return configFilePath, nil 217 } 218 219 if mustExist || !os.IsNotExist(err) { 220 return "", err 221 } 222 223 log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.") 224 return "", nil 225 } 226 227 // copyOutput uses output prefixes to determine whether data on stdout 228 // should go to stdout or stderr. This is due to panicwrap using stderr 229 // as the log and error channel. 230 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 231 defer close(doneCh) 232 233 pr, err := prefixedio.NewReader(r) 234 if err != nil { 235 panic(err) 236 } 237 238 stderrR, err := pr.Prefix(ErrorPrefix) 239 if err != nil { 240 panic(err) 241 } 242 stdoutR, err := pr.Prefix(OutputPrefix) 243 if err != nil { 244 panic(err) 245 } 246 defaultR, err := pr.Prefix("") 247 if err != nil { 248 panic(err) 249 } 250 251 var stdout io.Writer = os.Stdout 252 var stderr io.Writer = os.Stderr 253 254 if runtime.GOOS == "windows" { 255 stdout = colorable.NewColorableStdout() 256 stderr = colorable.NewColorableStderr() 257 258 // colorable is not concurrency-safe when stdout and stderr are the 259 // same console, so we need to add some synchronization to ensure that 260 // we can't be concurrently writing to both stderr and stdout at 261 // once, or else we get intermingled writes that create gibberish 262 // in the console. 263 wrapped := synchronizedWriters(stdout, stderr) 264 stdout = wrapped[0] 265 stderr = wrapped[1] 266 } 267 268 var wg sync.WaitGroup 269 wg.Add(3) 270 go func() { 271 defer wg.Done() 272 io.Copy(stderr, stderrR) 273 }() 274 go func() { 275 defer wg.Done() 276 io.Copy(stdout, stdoutR) 277 }() 278 go func() { 279 defer wg.Done() 280 io.Copy(stdout, defaultR) 281 }() 282 283 wg.Wait() 284 } 285 286 func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) { 287 v := os.Getenv(envName) 288 if v == "" { 289 return args, nil 290 } 291 292 log.Printf("[INFO] %s value: %q", envName, v) 293 extra, err := shellwords.Parse(v) 294 if err != nil { 295 return nil, fmt.Errorf( 296 "Error parsing extra CLI args from %s: %s", 297 envName, err) 298 } 299 300 // Find the command to look for in the args. If there is a space, 301 // we need to find the last part. 302 search := cmd 303 if idx := strings.LastIndex(search, " "); idx >= 0 { 304 search = cmd[idx+1:] 305 } 306 307 // Find the index to place the flags. We put them exactly 308 // after the first non-flag arg. 309 idx := -1 310 for i, v := range args { 311 if v == search { 312 idx = i 313 break 314 } 315 } 316 317 // idx points to the exact arg that isn't a flag. We increment 318 // by one so that all the copying below expects idx to be the 319 // insertion point. 320 idx++ 321 322 // Copy the args 323 newArgs := make([]string, len(args)+len(extra)) 324 copy(newArgs, args[:idx]) 325 copy(newArgs[idx:], extra) 326 copy(newArgs[len(extra)+idx:], args[idx:]) 327 return newArgs, nil 328 }