2014-05-24 14:04:43 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2014-06-10 12:28:47 -05:00
|
|
|
"io"
|
2014-05-24 14:04:43 -05:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
|
|
|
"os"
|
2017-09-25 19:22:37 -05:00
|
|
|
"path/filepath"
|
2016-03-18 12:10:20 -05:00
|
|
|
"runtime"
|
2017-02-13 17:12:29 -06:00
|
|
|
"strings"
|
2014-10-03 15:02:16 -05:00
|
|
|
"sync"
|
2014-05-24 14:04:43 -05:00
|
|
|
|
2016-03-23 12:09:46 -05:00
|
|
|
"github.com/hashicorp/go-plugin"
|
2017-10-19 18:43:18 -05:00
|
|
|
"github.com/hashicorp/terraform/command/format"
|
2015-12-07 18:10:30 -06:00
|
|
|
"github.com/hashicorp/terraform/helper/logging"
|
2018-07-05 14:28:29 -05:00
|
|
|
"github.com/hashicorp/terraform/svchost/disco"
|
2016-03-18 12:10:20 -05:00
|
|
|
"github.com/mattn/go-colorable"
|
2017-02-13 16:05:37 -06:00
|
|
|
"github.com/mattn/go-shellwords"
|
2014-05-24 14:04:43 -05:00
|
|
|
"github.com/mitchellh/cli"
|
2018-07-04 10:24:49 -05:00
|
|
|
"github.com/mitchellh/colorstring"
|
2014-05-30 18:07:26 -05:00
|
|
|
"github.com/mitchellh/panicwrap"
|
2014-06-10 12:28:47 -05:00
|
|
|
"github.com/mitchellh/prefixedio"
|
2018-10-31 10:45:03 -05:00
|
|
|
|
|
|
|
backendInit "github.com/hashicorp/terraform/backend/init"
|
2014-05-24 14:04:43 -05:00
|
|
|
)
|
|
|
|
|
2017-02-13 16:05:37 -06:00
|
|
|
const (
|
|
|
|
// EnvCLI is the environment variable name to set additional CLI args.
|
|
|
|
EnvCLI = "TF_CLI_ARGS"
|
|
|
|
)
|
|
|
|
|
2014-05-24 14:04:43 -05:00
|
|
|
func main() {
|
2016-04-25 17:33:53 -05:00
|
|
|
// Override global prefix set by go-dynect during init()
|
|
|
|
log.SetPrefix("")
|
2014-05-24 14:04:43 -05:00
|
|
|
os.Exit(realMain())
|
|
|
|
}
|
|
|
|
|
|
|
|
func realMain() int {
|
2014-05-30 18:07:26 -05:00
|
|
|
var wrapConfig panicwrap.WrapConfig
|
|
|
|
|
2016-09-21 13:09:02 -05:00
|
|
|
// don't re-exec terraform as a child process for easier debugging
|
|
|
|
if os.Getenv("TF_FORK") == "0" {
|
|
|
|
return wrappedMain()
|
|
|
|
}
|
|
|
|
|
2014-05-30 18:07:26 -05:00
|
|
|
if !panicwrap.Wrapped(&wrapConfig) {
|
|
|
|
// Determine where logs should go in general (requested by the user)
|
2015-12-07 18:10:30 -06:00
|
|
|
logWriter, err := logging.LogOutput()
|
2014-05-30 18:07:26 -05:00
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Couldn't setup log output: %s", err)
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// We always send logs to a temporary file that we use in case
|
|
|
|
// there is a panic. Otherwise, we delete it.
|
|
|
|
logTempFile, err := ioutil.TempFile("", "terraform-log")
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Couldn't setup logging tempfile: %s", err)
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
defer os.Remove(logTempFile.Name())
|
2014-06-10 12:28:47 -05:00
|
|
|
defer logTempFile.Close()
|
2014-05-30 18:07:26 -05:00
|
|
|
|
2014-06-10 12:28:47 -05:00
|
|
|
// Setup the prefixed readers that send data properly to
|
|
|
|
// stdout/stderr.
|
2014-10-03 15:02:16 -05:00
|
|
|
doneCh := make(chan struct{})
|
2014-06-10 12:28:47 -05:00
|
|
|
outR, outW := io.Pipe()
|
2014-10-03 15:02:16 -05:00
|
|
|
go copyOutput(outR, doneCh)
|
2014-05-30 18:07:26 -05:00
|
|
|
|
|
|
|
// Create the configuration for panicwrap and wrap our executable
|
|
|
|
wrapConfig.Handler = panicHandler(logTempFile)
|
2014-06-10 12:28:47 -05:00
|
|
|
wrapConfig.Writer = io.MultiWriter(logTempFile, logWriter)
|
|
|
|
wrapConfig.Stdout = outW
|
2016-12-08 11:10:52 -06:00
|
|
|
wrapConfig.IgnoreSignals = ignoreSignals
|
|
|
|
wrapConfig.ForwardSignals = forwardSignals
|
2014-05-30 18:07:26 -05:00
|
|
|
exitStatus, err := panicwrap.Wrap(&wrapConfig)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// If >= 0, we're the parent, so just exit
|
|
|
|
if exitStatus >= 0 {
|
2014-10-03 15:02:16 -05:00
|
|
|
// Close the stdout writer so that our copy process can finish
|
|
|
|
outW.Close()
|
|
|
|
|
|
|
|
// Wait for the output copying to finish
|
|
|
|
<-doneCh
|
|
|
|
|
2014-05-30 18:07:26 -05:00
|
|
|
return exitStatus
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're the child, so just close the tempfile we made in order to
|
|
|
|
// save file handles since the tempfile is only used by the parent.
|
|
|
|
logTempFile.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Call the real main
|
|
|
|
return wrappedMain()
|
|
|
|
}
|
|
|
|
|
2017-09-01 17:58:38 -05:00
|
|
|
func init() {
|
|
|
|
Ui = &cli.PrefixedUi{
|
|
|
|
AskPrefix: OutputPrefix,
|
|
|
|
OutputPrefix: OutputPrefix,
|
|
|
|
InfoPrefix: OutputPrefix,
|
|
|
|
ErrorPrefix: ErrorPrefix,
|
|
|
|
Ui: &cli.BasicUi{Writer: os.Stdout},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-05-30 18:07:26 -05:00
|
|
|
func wrappedMain() int {
|
2017-10-19 19:01:02 -05:00
|
|
|
var err error
|
|
|
|
|
2014-06-10 12:28:47 -05:00
|
|
|
log.SetOutput(os.Stderr)
|
2014-10-21 00:32:00 -05:00
|
|
|
log.Printf(
|
|
|
|
"[INFO] Terraform version: %s %s %s",
|
|
|
|
Version, VersionPrerelease, GitCommit)
|
2017-02-15 18:10:30 -06:00
|
|
|
log.Printf("[INFO] Go runtime version: %s", runtime.Version())
|
2016-08-17 09:49:54 -05:00
|
|
|
log.Printf("[INFO] CLI args: %#v", os.Args)
|
2014-05-24 14:04:43 -05:00
|
|
|
|
2017-10-19 19:01:02 -05:00
|
|
|
config, diags := LoadConfig()
|
|
|
|
if len(diags) > 0 {
|
|
|
|
// Since we haven't instantiated a command.Meta yet, we need to do
|
|
|
|
// some things manually here and use some "safe" defaults for things
|
|
|
|
// that command.Meta could otherwise figure out in smarter ways.
|
|
|
|
Ui.Error("There are some problems with the CLI configuration:")
|
|
|
|
for _, diag := range diags {
|
|
|
|
earlyColor := &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
|
|
|
Disable: true, // Disable color to be conservative until we know better
|
|
|
|
Reset: true,
|
2017-10-19 18:43:18 -05:00
|
|
|
}
|
2018-02-28 19:06:21 -06:00
|
|
|
// We don't currently have access to the source code cache for
|
|
|
|
// the parser used to load the CLI config, so we can't show
|
|
|
|
// source code snippets in early diagnostics.
|
|
|
|
Ui.Error(format.Diagnostic(diag, nil, earlyColor, 78))
|
2017-10-19 19:01:02 -05:00
|
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
|
|
Ui.Error("As a result of the above problems, Terraform may not behave as intended.\n\n")
|
|
|
|
// We continue to run anyway, since Terraform has reasonable defaults.
|
2017-10-19 18:43:18 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-04 10:24:49 -05:00
|
|
|
// Get any configured credentials from the config and initialize
|
|
|
|
// a service discovery object.
|
|
|
|
credsSrc := credentialsSource(config)
|
|
|
|
services := disco.NewWithCredentialsSource(credsSrc)
|
|
|
|
|
|
|
|
// Initialize the backends.
|
|
|
|
backendInit.Init(services)
|
|
|
|
|
2017-09-01 17:58:38 -05:00
|
|
|
// In tests, Commands may already be set to provide mock commands
|
|
|
|
if Commands == nil {
|
2018-07-05 14:28:29 -05:00
|
|
|
initCommands(config, services)
|
2017-09-01 17:58:38 -05:00
|
|
|
}
|
|
|
|
|
2014-10-13 16:05:29 -05:00
|
|
|
// Run checkpoint
|
2017-10-19 19:01:02 -05:00
|
|
|
go runCheckpoint(config)
|
2014-10-13 16:05:29 -05:00
|
|
|
|
2014-06-09 23:57:37 -05:00
|
|
|
// Make sure we clean up any managed plugins at the end of this
|
|
|
|
defer plugin.CleanupClients()
|
|
|
|
|
2017-02-13 16:05:37 -06:00
|
|
|
// Get the command line args.
|
2017-09-25 19:22:37 -05:00
|
|
|
binName := filepath.Base(os.Args[0])
|
2014-05-24 14:04:43 -05:00
|
|
|
args := os.Args[1:]
|
2017-02-13 16:05:37 -06:00
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
// Build the CLI so far, we do this so we can query the subcommand.
|
|
|
|
cliRunner := &cli.CLI{
|
|
|
|
Args: args,
|
|
|
|
Commands: Commands,
|
|
|
|
HelpFunc: helpFunc,
|
|
|
|
HelpWriter: os.Stdout,
|
|
|
|
}
|
2017-02-13 16:05:37 -06:00
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
// Prefix the args with any args from the EnvCLI
|
2017-02-13 17:18:50 -06:00
|
|
|
args, err = mergeEnvArgs(EnvCLI, cliRunner.Subcommand(), args)
|
2017-02-13 17:12:29 -06:00
|
|
|
if err != nil {
|
|
|
|
Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
2017-02-13 16:05:37 -06:00
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
// Prefix the args with any args from the EnvCLI targeting this command
|
2017-02-13 17:18:50 -06:00
|
|
|
suffix := strings.Replace(strings.Replace(
|
|
|
|
cliRunner.Subcommand(), "-", "_", -1), " ", "_", -1)
|
|
|
|
args, err = mergeEnvArgs(
|
|
|
|
fmt.Sprintf("%s_%s", EnvCLI, suffix), cliRunner.Subcommand(), args)
|
2017-02-13 17:12:29 -06:00
|
|
|
if err != nil {
|
|
|
|
Ui.Error(err.Error())
|
|
|
|
return 1
|
2017-02-13 16:05:37 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// We shortcut "--version" and "-v" to just show the version
|
2014-05-24 14:04:43 -05:00
|
|
|
for _, arg := range args {
|
2014-07-13 12:37:25 -05:00
|
|
|
if arg == "-v" || arg == "-version" || arg == "--version" {
|
2014-05-24 14:04:43 -05:00
|
|
|
newArgs := make([]string, len(args)+1)
|
|
|
|
newArgs[0] = "version"
|
|
|
|
copy(newArgs[1:], args)
|
|
|
|
args = newArgs
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
// Rebuild the CLI with any modified args.
|
2017-02-13 16:05:37 -06:00
|
|
|
log.Printf("[INFO] CLI command args: %#v", args)
|
2017-02-13 17:12:29 -06:00
|
|
|
cliRunner = &cli.CLI{
|
2017-09-25 19:22:37 -05:00
|
|
|
Name: binName,
|
2014-06-26 12:24:32 -05:00
|
|
|
Args: args,
|
|
|
|
Commands: Commands,
|
2016-03-22 12:41:02 -05:00
|
|
|
HelpFunc: helpFunc,
|
2014-06-26 12:24:32 -05:00
|
|
|
HelpWriter: os.Stdout,
|
2017-09-25 19:22:37 -05:00
|
|
|
|
|
|
|
Autocomplete: true,
|
|
|
|
AutocompleteInstall: "install-autocomplete",
|
|
|
|
AutocompleteUninstall: "uninstall-autocomplete",
|
2014-05-24 14:04:43 -05:00
|
|
|
}
|
|
|
|
|
2017-04-13 20:05:58 -05:00
|
|
|
// Pass in the overriding plugin paths from config
|
|
|
|
PluginOverrides.Providers = config.Providers
|
|
|
|
PluginOverrides.Provisioners = config.Provisioners
|
2014-08-14 11:45:58 -05:00
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
exitCode, err := cliRunner.Run()
|
2014-05-24 14:04:43 -05:00
|
|
|
if err != nil {
|
2014-10-11 15:03:11 -05:00
|
|
|
Ui.Error(fmt.Sprintf("Error executing CLI: %s", err.Error()))
|
2014-05-24 14:04:43 -05:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
return exitCode
|
|
|
|
}
|
2014-06-10 12:28:47 -05:00
|
|
|
|
2014-08-14 11:45:58 -05:00
|
|
|
func cliConfigFile() (string, error) {
|
|
|
|
mustExist := true
|
2019-04-05 13:21:40 -05:00
|
|
|
|
|
|
|
configFilePath := os.Getenv("TF_CLI_CONFIG_FILE")
|
|
|
|
if configFilePath == "" {
|
|
|
|
configFilePath = os.Getenv("TERRAFORM_CONFIG")
|
|
|
|
}
|
|
|
|
|
2014-08-14 11:45:58 -05:00
|
|
|
if configFilePath == "" {
|
|
|
|
var err error
|
2014-10-11 15:03:11 -05:00
|
|
|
configFilePath, err = ConfigFile()
|
2014-08-14 11:45:58 -05:00
|
|
|
mustExist = false
|
|
|
|
|
|
|
|
if err != nil {
|
2014-08-19 12:58:23 -05:00
|
|
|
log.Printf(
|
|
|
|
"[ERROR] Error detecting default CLI config file path: %s",
|
|
|
|
err)
|
2014-08-14 11:45:58 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-19 12:58:23 -05:00
|
|
|
log.Printf("[DEBUG] Attempting to open CLI config file: %s", configFilePath)
|
2014-08-14 11:45:58 -05:00
|
|
|
f, err := os.Open(configFilePath)
|
2014-08-19 12:58:23 -05:00
|
|
|
if err == nil {
|
|
|
|
f.Close()
|
|
|
|
return configFilePath, nil
|
|
|
|
}
|
2014-08-14 11:45:58 -05:00
|
|
|
|
2014-08-19 12:58:23 -05:00
|
|
|
if mustExist || !os.IsNotExist(err) {
|
|
|
|
return "", err
|
2014-08-14 11:45:58 -05:00
|
|
|
}
|
|
|
|
|
2014-08-19 12:58:23 -05:00
|
|
|
log.Println("[DEBUG] File doesn't exist, but doesn't need to. Ignoring.")
|
|
|
|
return "", nil
|
2014-08-14 11:45:58 -05:00
|
|
|
}
|
|
|
|
|
2014-06-10 12:32:59 -05:00
|
|
|
// copyOutput uses output prefixes to determine whether data on stdout
|
|
|
|
// should go to stdout or stderr. This is due to panicwrap using stderr
|
|
|
|
// as the log and error channel.
|
2014-10-03 15:02:16 -05:00
|
|
|
func copyOutput(r io.Reader, doneCh chan<- struct{}) {
|
|
|
|
defer close(doneCh)
|
|
|
|
|
2014-06-10 12:28:47 -05:00
|
|
|
pr, err := prefixedio.NewReader(r)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
stderrR, err := pr.Prefix(ErrorPrefix)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
stdoutR, err := pr.Prefix(OutputPrefix)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2014-06-26 12:24:32 -05:00
|
|
|
defaultR, err := pr.Prefix("")
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2014-06-10 12:28:47 -05:00
|
|
|
|
2016-03-18 12:10:20 -05:00
|
|
|
var stdout io.Writer = os.Stdout
|
|
|
|
var stderr io.Writer = os.Stderr
|
|
|
|
|
|
|
|
if runtime.GOOS == "windows" {
|
|
|
|
stdout = colorable.NewColorableStdout()
|
|
|
|
stderr = colorable.NewColorableStderr()
|
2017-05-03 18:25:41 -05:00
|
|
|
|
|
|
|
// colorable is not concurrency-safe when stdout and stderr are the
|
|
|
|
// same console, so we need to add some synchronization to ensure that
|
|
|
|
// we can't be concurrently writing to both stderr and stdout at
|
|
|
|
// once, or else we get intermingled writes that create gibberish
|
|
|
|
// in the console.
|
|
|
|
wrapped := synchronizedWriters(stdout, stderr)
|
|
|
|
stdout = wrapped[0]
|
|
|
|
stderr = wrapped[1]
|
2016-03-18 12:10:20 -05:00
|
|
|
}
|
|
|
|
|
2014-10-03 15:02:16 -05:00
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(3)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-03-18 12:10:20 -05:00
|
|
|
io.Copy(stderr, stderrR)
|
2014-10-03 15:02:16 -05:00
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-03-18 12:10:20 -05:00
|
|
|
io.Copy(stdout, stdoutR)
|
2014-10-03 15:02:16 -05:00
|
|
|
}()
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
2016-03-18 12:10:20 -05:00
|
|
|
io.Copy(stdout, defaultR)
|
2014-10-03 15:02:16 -05:00
|
|
|
}()
|
|
|
|
|
|
|
|
wg.Wait()
|
2014-06-10 12:28:47 -05:00
|
|
|
}
|
2017-02-13 17:12:29 -06:00
|
|
|
|
2017-02-13 17:18:50 -06:00
|
|
|
func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) {
|
2017-02-13 17:12:29 -06:00
|
|
|
v := os.Getenv(envName)
|
|
|
|
if v == "" {
|
|
|
|
return args, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Printf("[INFO] %s value: %q", envName, v)
|
|
|
|
extra, err := shellwords.Parse(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"Error parsing extra CLI args from %s: %s",
|
|
|
|
envName, err)
|
|
|
|
}
|
|
|
|
|
2017-02-13 17:18:50 -06:00
|
|
|
// Find the command to look for in the args. If there is a space,
|
|
|
|
// we need to find the last part.
|
|
|
|
search := cmd
|
|
|
|
if idx := strings.LastIndex(search, " "); idx >= 0 {
|
|
|
|
search = cmd[idx+1:]
|
|
|
|
}
|
|
|
|
|
2017-02-13 17:12:29 -06:00
|
|
|
// Find the index to place the flags. We put them exactly
|
|
|
|
// after the first non-flag arg.
|
|
|
|
idx := -1
|
|
|
|
for i, v := range args {
|
2017-02-13 17:18:50 -06:00
|
|
|
if v == search {
|
2017-02-13 17:12:29 -06:00
|
|
|
idx = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// idx points to the exact arg that isn't a flag. We increment
|
|
|
|
// by one so that all the copying below expects idx to be the
|
|
|
|
// insertion point.
|
|
|
|
idx++
|
|
|
|
|
|
|
|
// Copy the args
|
|
|
|
newArgs := make([]string, len(args)+len(extra))
|
|
|
|
copy(newArgs, args[:idx])
|
|
|
|
copy(newArgs[idx:], extra)
|
|
|
|
copy(newArgs[len(extra)+idx:], args[idx:])
|
|
|
|
return newArgs, nil
|
|
|
|
}
|