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