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