github.com/IBM-Cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/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  		// colorable is not concurrency-safe when stdout and stderr are the
   263  		// same console, so we need to add some synchronization to ensure that
   264  		// we can't be concurrently writing to both stderr and stdout at
   265  		// once, or else we get intermingled writes that create gibberish
   266  		// in the console.
   267  		wrapped := synchronizedWriters(stdout, stderr)
   268  		stdout = wrapped[0]
   269  		stderr = wrapped[1]
   270  	}
   271  
   272  	var wg sync.WaitGroup
   273  	wg.Add(3)
   274  	go func() {
   275  		defer wg.Done()
   276  		io.Copy(stderr, stderrR)
   277  	}()
   278  	go func() {
   279  		defer wg.Done()
   280  		io.Copy(stdout, stdoutR)
   281  	}()
   282  	go func() {
   283  		defer wg.Done()
   284  		io.Copy(stdout, defaultR)
   285  	}()
   286  
   287  	wg.Wait()
   288  }
   289  
   290  func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) {
   291  	v := os.Getenv(envName)
   292  	if v == "" {
   293  		return args, nil
   294  	}
   295  
   296  	log.Printf("[INFO] %s value: %q", envName, v)
   297  	extra, err := shellwords.Parse(v)
   298  	if err != nil {
   299  		return nil, fmt.Errorf(
   300  			"Error parsing extra CLI args from %s: %s",
   301  			envName, err)
   302  	}
   303  
   304  	// Find the command to look for in the args. If there is a space,
   305  	// we need to find the last part.
   306  	search := cmd
   307  	if idx := strings.LastIndex(search, " "); idx >= 0 {
   308  		search = cmd[idx+1:]
   309  	}
   310  
   311  	// Find the index to place the flags. We put them exactly
   312  	// after the first non-flag arg.
   313  	idx := -1
   314  	for i, v := range args {
   315  		if v == search {
   316  			idx = i
   317  			break
   318  		}
   319  	}
   320  
   321  	// idx points to the exact arg that isn't a flag. We increment
   322  	// by one so that all the copying below expects idx to be the
   323  	// insertion point.
   324  	idx++
   325  
   326  	// Copy the args
   327  	newArgs := make([]string, len(args)+len(extra))
   328  	copy(newArgs, args[:idx])
   329  	copy(newArgs[idx:], extra)
   330  	copy(newArgs[len(extra)+idx:], args[idx:])
   331  	return newArgs, nil
   332  }