github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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 if err := config.Discover(Ui); err != nil { 112 Ui.Error(fmt.Sprintf("Error discovering plugins: %s", err)) 113 return 1 114 } 115 116 // Load the configuration file if we have one, that can be used to 117 // define extra providers and provisioners. 118 clicfgFile, err := cliConfigFile() 119 if err != nil { 120 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 121 return 1 122 } 123 124 if clicfgFile != "" { 125 usrcfg, err := LoadConfig(clicfgFile) 126 if err != nil { 127 Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) 128 return 1 129 } 130 131 config = *config.Merge(usrcfg) 132 } 133 134 // Run checkpoint 135 go runCheckpoint(&config) 136 137 // Make sure we clean up any managed plugins at the end of this 138 defer plugin.CleanupClients() 139 140 // Get the command line args. 141 args := os.Args[1:] 142 143 // Build the CLI so far, we do this so we can query the subcommand. 144 cliRunner := &cli.CLI{ 145 Args: args, 146 Commands: Commands, 147 HelpFunc: helpFunc, 148 HelpWriter: os.Stdout, 149 } 150 151 // Prefix the args with any args from the EnvCLI 152 args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args) 153 if err != nil { 154 Ui.Error(err.Error()) 155 return 1 156 } 157 158 // Prefix the args with any args from the EnvCLI targeting this command 159 suffix := strings.Replace(strings.Replace( 160 cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1) 161 args, err = mergeEnvArgs( 162 fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args) 163 if err != nil { 164 Ui.Error(err.Error()) 165 return 1 166 } 167 168 // We shortcut "--version" and "-v" to just show the version 169 for _, arg := range args { 170 if arg == "-v" || arg == "-version" || arg == "--version" { 171 newArgs := make([]string, len(args)+1) 172 newArgs[0] = "version" 173 copy(newArgs[1:], args) 174 args = newArgs 175 break 176 } 177 } 178 179 // Rebuild the CLI with any modified args. 180 log.Printf("[INFO] CLI command args: %#v", args) 181 cliRunner = &cli.CLI{ 182 Args: args, 183 Commands: Commands, 184 HelpFunc: helpFunc, 185 HelpWriter: os.Stdout, 186 } 187 188 // Initialize the TFConfig settings for the commands... 189 ContextOpts.Providers = config.ProviderFactories() 190 ContextOpts.Provisioners = config.ProvisionerFactories() 191 192 exitCode, err := cliRunner.Run() 193 if err != nil { 194 Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error())) 195 return 1 196 } 197 198 return exitCode 199 } 200 201 func cliConfigFile() (string, error) { 202 mustExist := true 203 configFilePath := os.Getenv("TERRAFORM_CONFIG") 204 if configFilePath == "" { 205 var err error 206 configFilePath, err = ConfigFile() 207 mustExist = false 208 209 if err != nil { 210 log.Printf( 211 "[ERROR] Error detecting default CLI config file path: %s", 212 err) 213 } 214 } 215 216 log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath) 217 f, err := os.Open(configFilePath) 218 if err == nil { 219 f.Close() 220 return configFilePath, nil 221 } 222 223 if mustExist || !os.IsNotExist(err) { 224 return "", err 225 } 226 227 log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.") 228 return "", nil 229 } 230 231 // copyOutput uses output prefixes to determine whether data on stdout 232 // should go to stdout or stderr. This is due to panicwrap using stderr 233 // as the log and error channel. 234 func copyOutput(r io.Reader, doneCh chan<- struct{}) { 235 defer close(doneCh) 236 237 pr, err := prefixedio.NewReader(r) 238 if err != nil { 239 panic(err) 240 } 241 242 stderrR, err := pr.Prefix(ErrorPrefix) 243 if err != nil { 244 panic(err) 245 } 246 stdoutR, err := pr.Prefix(OutputPrefix) 247 if err != nil { 248 panic(err) 249 } 250 defaultR, err := pr.Prefix("") 251 if err != nil { 252 panic(err) 253 } 254 255 var stdout io.Writer = os.Stdout 256 var stderr io.Writer = os.Stderr 257 258 if runtime.GOOS == "windows" { 259 stdout = colorable.NewColorableStdout() 260 stderr = colorable.NewColorableStderr() 261 } 262 263 var wg sync.WaitGroup 264 wg.Add(3) 265 go func() { 266 defer wg.Done() 267 io.Copy(stderr, stderrR) 268 }() 269 go func() { 270 defer wg.Done() 271 io.Copy(stdout, stdoutR) 272 }() 273 go func() { 274 defer wg.Done() 275 io.Copy(stdout, defaultR) 276 }() 277 278 wg.Wait() 279 } 280 281 func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) { 282 v := os.Getenv(envName) 283 if v == "" { 284 return args, nil 285 } 286 287 log.Printf("[INFO] %s value: %q", envName, v) 288 extra, err := shellwords.Parse(v) 289 if err != nil { 290 return nil, fmt.Errorf( 291 "Error parsing extra CLI args from %s: %s", 292 envName, err) 293 } 294 295 // Find the command to look for in the args. If there is a space, 296 // we need to find the last part. 297 search := cmd 298 if idx := strings.LastIndex(search, " "); idx >= 0 { 299 search = cmd[idx+1:] 300 } 301 302 // Find the index to place the flags. We put them exactly 303 // after the first non-flag arg. 304 idx := -1 305 for i, v := range args { 306 if v == search { 307 idx = i 308 break 309 } 310 } 311 312 // idx points to the exact arg that isn't a flag. We increment 313 // by one so that all the copying below expects idx to be the 314 // insertion point. 315 idx++ 316 317 // Copy the args 318 newArgs := make([]string, len(args)+len(extra)) 319 copy(newArgs, args[:idx]) 320 copy(newArgs[idx:], extra) 321 copy(newArgs[len(extra)+idx:], args[idx:]) 322 return newArgs, nil 323 }