github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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  }