github.com/Hashicorp/terraform@v0.11.12-beta1/main.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"runtime"
    11  	"strings"
    12  	"sync"
    13  
    14  	"github.com/hashicorp/go-plugin"
    15  	backendInit "github.com/hashicorp/terraform/backend/init"
    16  	"github.com/hashicorp/terraform/command/format"
    17  	"github.com/hashicorp/terraform/helper/logging"
    18  	"github.com/hashicorp/terraform/svchost/disco"
    19  	"github.com/hashicorp/terraform/terraform"
    20  	"github.com/mattn/go-colorable"
    21  	"github.com/mattn/go-shellwords"
    22  	"github.com/mitchellh/cli"
    23  	"github.com/mitchellh/colorstring"
    24  	"github.com/mitchellh/panicwrap"
    25  	"github.com/mitchellh/prefixedio"
    26  )
    27  
    28  const (
    29  	// EnvCLI is the environment variable name to set additional CLI args.
    30  	EnvCLI = "TF_CLI_ARGS"
    31  )
    32  
    33  func main() {
    34  	// Override global prefix set by go-dynect during init()
    35  	log.SetPrefix("")
    36  	os.Exit(realMain())
    37  }
    38  
    39  func realMain() int {
    40  	var wrapConfig panicwrap.WrapConfig
    41  
    42  	// don't re-exec terraform as a child process for easier debugging
    43  	if os.Getenv("TF_FORK") == "0" {
    44  		return wrappedMain()
    45  	}
    46  
    47  	if !panicwrap.Wrapped(&wrapConfig) {
    48  		// Determine where logs should go in general (requested by the user)
    49  		logWriter, err := logging.LogOutput()
    50  		if err != nil {
    51  			fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
    52  			return 1
    53  		}
    54  
    55  		// We always send logs to a temporary file that we use in case
    56  		// there is a panic. Otherwise, we delete it.
    57  		logTempFile, err := ioutil.TempFile("", "terraform-log")
    58  		if err != nil {
    59  			fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
    60  			return 1
    61  		}
    62  		defer os.Remove(logTempFile.Name())
    63  		defer logTempFile.Close()
    64  
    65  		// Setup the prefixed readers that send data properly to
    66  		// stdout/stderr.
    67  		doneCh := make(chan struct{})
    68  		outR, outW := io.Pipe()
    69  		go copyOutput(outR, doneCh)
    70  
    71  		// Create the configuration for panicwrap and wrap our executable
    72  		wrapConfig.Handler = panicHandler(logTempFile)
    73  		wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
    74  		wrapConfig.Stdout = outW
    75  		wrapConfig.IgnoreSignals = ignoreSignals
    76  		wrapConfig.ForwardSignals = forwardSignals
    77  		exitStatus, err := panicwrap.Wrap(&wrapConfig)
    78  		if err != nil {
    79  			fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
    80  			return 1
    81  		}
    82  
    83  		// If >= 0, we're the parent, so just exit
    84  		if exitStatus >= 0 {
    85  			// Close the stdout writer so that our copy process can finish
    86  			outW.Close()
    87  
    88  			// Wait for the output copying to finish
    89  			<-doneCh
    90  
    91  			return exitStatus
    92  		}
    93  
    94  		// We're the child, so just close the tempfile we made in order to
    95  		// save file handles since the tempfile is only used by the parent.
    96  		logTempFile.Close()
    97  	}
    98  
    99  	// Call the real main
   100  	return wrappedMain()
   101  }
   102  
   103  func init() {
   104  	Ui = &cli.PrefixedUi{
   105  		AskPrefix:    OutputPrefix,
   106  		OutputPrefix: OutputPrefix,
   107  		InfoPrefix:   OutputPrefix,
   108  		ErrorPrefix:  ErrorPrefix,
   109  		Ui:           &cli.BasicUi{Writer: os.Stdout},
   110  	}
   111  }
   112  
   113  func wrappedMain() int {
   114  	var err error
   115  
   116  	// We always need to close the DebugInfo before we exit.
   117  	defer terraform.CloseDebugInfo()
   118  
   119  	log.SetOutput(os.Stderr)
   120  	log.Printf(
   121  		"[INFO] Terraform version: %s %s %s",
   122  		Version, VersionPrerelease, GitCommit)
   123  	log.Printf("[INFO] Go runtime version: %s", runtime.Version())
   124  	log.Printf("[INFO] CLI args: %#v", os.Args)
   125  
   126  	config, diags := LoadConfig()
   127  	if len(diags) > 0 {
   128  		// Since we haven't instantiated a command.Meta yet, we need to do
   129  		// some things manually here and use some "safe" defaults for things
   130  		// that command.Meta could otherwise figure out in smarter ways.
   131  		Ui.Error("There are some problems with the CLI configuration:")
   132  		for _, diag := range diags {
   133  			earlyColor := &colorstring.Colorize{
   134  				Colors:  colorstring.DefaultColors,
   135  				Disable: true, // Disable color to be conservative until we know better
   136  				Reset:   true,
   137  			}
   138  			Ui.Error(format.Diagnostic(diag, earlyColor, 78))
   139  		}
   140  		if diags.HasErrors() {
   141  			Ui.Error("As a result of the above problems, Terraform may not behave as intended.\n\n")
   142  			// We continue to run anyway, since Terraform has reasonable defaults.
   143  		}
   144  	}
   145  
   146  	// Get any configured credentials from the config and initialize
   147  	// a service discovery object.
   148  	credsSrc := credentialsSource(config)
   149  	services := disco.NewWithCredentialsSource(credsSrc)
   150  
   151  	// Initialize the backends.
   152  	backendInit.Init(services)
   153  
   154  	// In tests, Commands may already be set to provide mock commands
   155  	if Commands == nil {
   156  		initCommands(config, services)
   157  	}
   158  
   159  	// Run checkpoint
   160  	go runCheckpoint(config)
   161  
   162  	// Make sure we clean up any managed plugins at the end of this
   163  	defer plugin.CleanupClients()
   164  
   165  	// Get the command line args.
   166  	binName := filepath.Base(os.Args[0])
   167  	args := os.Args[1:]
   168  
   169  	// Build the CLI so far, we do this so we can query the subcommand.
   170  	cliRunner := &cli.CLI{
   171  		Args:       args,
   172  		Commands:   Commands,
   173  		HelpFunc:   helpFunc,
   174  		HelpWriter: os.Stdout,
   175  	}
   176  
   177  	// Prefix the args with any args from the EnvCLI
   178  	args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args)
   179  	if err != nil {
   180  		Ui.Error(err.Error())
   181  		return 1
   182  	}
   183  
   184  	// Prefix the args with any args from the EnvCLI targeting this command
   185  	suffix := strings.Replace(strings.Replace(
   186  		cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1)
   187  	args, err = mergeEnvArgs(
   188  		fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args)
   189  	if err != nil {
   190  		Ui.Error(err.Error())
   191  		return 1
   192  	}
   193  
   194  	// We shortcut "--version" and "-v" to just show the version
   195  	for _, arg := range args {
   196  		if arg == "-v" || arg == "-version" || arg == "--version" {
   197  			newArgs := make([]string, len(args)+1)
   198  			newArgs[0] = "version"
   199  			copy(newArgs[1:], args)
   200  			args = newArgs
   201  			break
   202  		}
   203  	}
   204  
   205  	// Rebuild the CLI with any modified args.
   206  	log.Printf("[INFO] CLI command args: %#v", args)
   207  	cliRunner = &cli.CLI{
   208  		Name:       binName,
   209  		Args:       args,
   210  		Commands:   Commands,
   211  		HelpFunc:   helpFunc,
   212  		HelpWriter: os.Stdout,
   213  
   214  		Autocomplete:          true,
   215  		AutocompleteInstall:   "install-autocomplete",
   216  		AutocompleteUninstall: "uninstall-autocomplete",
   217  	}
   218  
   219  	// Pass in the overriding plugin paths from config
   220  	PluginOverrides.Providers = config.Providers
   221  	PluginOverrides.Provisioners = config.Provisioners
   222  
   223  	exitCode, err := cliRunner.Run()
   224  	if err != nil {
   225  		Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error()))
   226  		return 1
   227  	}
   228  
   229  	return exitCode
   230  }
   231  
   232  func cliConfigFile() (string, error) {
   233  	mustExist := true
   234  	configFilePath := os.Getenv("TERRAFORM_CONFIG")
   235  	if configFilePath == "" {
   236  		var err error
   237  		configFilePath, err = ConfigFile()
   238  		mustExist = false
   239  
   240  		if err != nil {
   241  			log.Printf(
   242  				"[ERROR] Error detecting default CLI config file path: %s",
   243  				err)
   244  		}
   245  	}
   246  
   247  	log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath)
   248  	f, err := os.Open(configFilePath)
   249  	if err == nil {
   250  		f.Close()
   251  		return configFilePath, nil
   252  	}
   253  
   254  	if mustExist || !os.IsNotExist(err) {
   255  		return "", err
   256  	}
   257  
   258  	log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.")
   259  	return "", nil
   260  }
   261  
   262  // copyOutput uses output prefixes to determine whether data on stdout
   263  // should go to stdout or stderr. This is due to panicwrap using stderr
   264  // as the log and error channel.
   265  func copyOutput(r io.Reader, doneCh chan<- struct{}) {
   266  	defer close(doneCh)
   267  
   268  	pr, err := prefixedio.NewReader(r)
   269  	if err != nil {
   270  		panic(err)
   271  	}
   272  
   273  	stderrR, err := pr.Prefix(ErrorPrefix)
   274  	if err != nil {
   275  		panic(err)
   276  	}
   277  	stdoutR, err := pr.Prefix(OutputPrefix)
   278  	if err != nil {
   279  		panic(err)
   280  	}
   281  	defaultR, err := pr.Prefix("")
   282  	if err != nil {
   283  		panic(err)
   284  	}
   285  
   286  	var stdout io.Writer = os.Stdout
   287  	var stderr io.Writer = os.Stderr
   288  
   289  	if runtime.GOOS == "windows" {
   290  		stdout = colorable.NewColorableStdout()
   291  		stderr = colorable.NewColorableStderr()
   292  
   293  		// colorable is not concurrency-safe when stdout and stderr are the
   294  		// same console, so we need to add some synchronization to ensure that
   295  		// we can't be concurrently writing to both stderr and stdout at
   296  		// once, or else we get intermingled writes that create gibberish
   297  		// in the console.
   298  		wrapped := synchronizedWriters(stdout, stderr)
   299  		stdout = wrapped[0]
   300  		stderr = wrapped[1]
   301  	}
   302  
   303  	var wg sync.WaitGroup
   304  	wg.Add(3)
   305  	go func() {
   306  		defer wg.Done()
   307  		io.Copy(stderr, stderrR)
   308  	}()
   309  	go func() {
   310  		defer wg.Done()
   311  		io.Copy(stdout, stdoutR)
   312  	}()
   313  	go func() {
   314  		defer wg.Done()
   315  		io.Copy(stdout, defaultR)
   316  	}()
   317  
   318  	wg.Wait()
   319  }
   320  
   321  func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) {
   322  	v := os.Getenv(envName)
   323  	if v == "" {
   324  		return args, nil
   325  	}
   326  
   327  	log.Printf("[INFO] %s value: %q", envName, v)
   328  	extra, err := shellwords.Parse(v)
   329  	if err != nil {
   330  		return nil, fmt.Errorf(
   331  			"Error parsing extra CLI args from %s: %s",
   332  			envName, err)
   333  	}
   334  
   335  	// Find the command to look for in the args. If there is a space,
   336  	// we need to find the last part.
   337  	search := cmd
   338  	if idx := strings.LastIndex(search, " "); idx >= 0 {
   339  		search = cmd[idx+1:]
   340  	}
   341  
   342  	// Find the index to place the flags. We put them exactly
   343  	// after the first non-flag arg.
   344  	idx := -1
   345  	for i, v := range args {
   346  		if v == search {
   347  			idx = i
   348  			break
   349  		}
   350  	}
   351  
   352  	// idx points to the exact arg that isn't a flag. We increment
   353  	// by one so that all the copying below expects idx to be the
   354  	// insertion point.
   355  	idx++
   356  
   357  	// Copy the args
   358  	newArgs := make([]string, len(args)+len(extra))
   359  	copy(newArgs, args[:idx])
   360  	copy(newArgs[idx:], extra)
   361  	copy(newArgs[len(extra)+idx:], args[idx:])
   362  	return newArgs, nil
   363  }