2014-05-24 14:04:43 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
"encoding/json"
|
2014-05-24 14:04:43 -05:00
|
|
|
"fmt"
|
2014-06-10 12:28:47 -05:00
|
|
|
"io"
|
2014-05-24 14:04:43 -05:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
"net"
|
2014-05-24 14:04:43 -05:00
|
|
|
"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"
|
2019-10-11 04:34:26 -05:00
|
|
|
"github.com/hashicorp/terraform-svchost/disco"
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
"github.com/hashicorp/terraform/addrs"
|
2020-01-13 15:50:05 -06:00
|
|
|
"github.com/hashicorp/terraform/command/cliconfig"
|
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"
|
2019-10-11 04:34:26 -05:00
|
|
|
"github.com/hashicorp/terraform/httpclient"
|
|
|
|
"github.com/hashicorp/terraform/version"
|
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,
|
2019-08-09 19:58:03 -05:00
|
|
|
Ui: &cli.BasicUi{
|
|
|
|
Writer: os.Stdout,
|
|
|
|
Reader: os.Stdin,
|
|
|
|
},
|
2017-09-01 17:58:38 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
2020-01-13 15:50:05 -06:00
|
|
|
config, diags := cliconfig.LoadConfig()
|
2020-04-21 18:28:59 -05:00
|
|
|
|
2017-10-19 19:01:02 -05:00
|
|
|
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
|
2020-06-03 08:43:24 -05:00
|
|
|
// a service discovery object. The slightly awkward predeclaration of
|
|
|
|
// disco is required to allow us to pass untyped nil as the creds source
|
|
|
|
// when creating the source fails. Otherwise we pass a typed nil which
|
|
|
|
// breaks the nil checks in the disco object
|
|
|
|
var services *disco.Disco
|
2019-08-08 19:08:49 -05:00
|
|
|
credsSrc, err := credentialsSource(config)
|
2020-06-03 08:43:24 -05:00
|
|
|
if err == nil {
|
|
|
|
services = disco.NewWithCredentialsSource(credsSrc)
|
|
|
|
} else {
|
2019-08-08 19:08:49 -05:00
|
|
|
// Most commands don't actually need credentials, and most situations
|
|
|
|
// that would get us here would already have been reported by the config
|
|
|
|
// loading above, so we'll just log this one as an aid to debugging
|
|
|
|
// in the unlikely event that it _does_ arise.
|
|
|
|
log.Printf("[WARN] Cannot initialize remote host credentials manager: %s", err)
|
2020-06-03 08:43:24 -05:00
|
|
|
// passing (untyped) nil as the creds source is okay because the disco
|
2019-08-08 19:08:49 -05:00
|
|
|
// object checks that and just acts as though no credentials are present.
|
2020-06-03 08:43:24 -05:00
|
|
|
services = disco.NewWithCredentialsSource(nil)
|
2019-08-08 19:08:49 -05:00
|
|
|
}
|
2019-10-11 04:34:26 -05:00
|
|
|
services.SetUserAgent(httpclient.TerraformUserAgent(version.String()))
|
2018-07-04 10:24:49 -05:00
|
|
|
|
2020-04-21 18:28:59 -05:00
|
|
|
providerSrc, diags := providerSource(config.ProviderInstallation, services)
|
|
|
|
if len(diags) > 0 {
|
|
|
|
Ui.Error("There are some problems with the provider_installation configuration:")
|
|
|
|
for _, diag := range diags {
|
|
|
|
earlyColor := &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
|
|
|
Disable: true, // Disable color to be conservative until we know better
|
|
|
|
Reset: true,
|
|
|
|
}
|
|
|
|
Ui.Error(format.Diagnostic(diag, nil, earlyColor, 78))
|
|
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
|
|
Ui.Error("As a result of the above problems, Terraform's provider installer may not behave as intended.\n\n")
|
|
|
|
// We continue to run anyway, because most commands don't do provider installation.
|
|
|
|
}
|
|
|
|
}
|
2020-01-16 19:42:41 -06:00
|
|
|
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
// The user can declare that certain providers are being managed on
|
|
|
|
// Terraform's behalf using this environment variable. Thsi is used
|
|
|
|
// primarily by the SDK's acceptance testing framework.
|
|
|
|
unmanagedProviders, err := parseReattachProviders(os.Getenv("TF_REATTACH_PROVIDERS"))
|
|
|
|
if err != nil {
|
|
|
|
Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-07-04 10:24:49 -05:00
|
|
|
// 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 {
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
initCommands(config, services, providerSrc, unmanagedProviders)
|
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-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
|
|
|
|
}
|
command: Unmanaged providers
This adds supports for "unmanaged" providers, or providers with process
lifecycles not controlled by Terraform. These providers are assumed to
be started before Terraform is launched, and are assumed to shut
themselves down after Terraform has finished running.
To do this, we must update the go-plugin dependency to v1.3.0, which
added support for the "test mode" plugin serving that powers all this.
As a side-effect of not needing to manage the process lifecycle anymore,
Terraform also no longer needs to worry about the provider's binary, as
it won't be used for anything anymore. Because of this, we can disable
the init behavior that concerns itself with downloading that provider's
binary, checking its version, and otherwise managing the binary.
This is all managed on a per-provider basis, so managed providers that
Terraform downloads, starts, and stops can be used in the same commands
as unmanaged providers. The TF_REATTACH_PROVIDERS environment variable
is added, and is a JSON encoding of the provider's address to the
information we need to connect to it.
This change enables two benefits: first, delve and other debuggers can
now be attached to provider server processes, and Terraform can connect.
This allows for attaching debuggers to provider processes, which before
was difficult to impossible. Second, it allows the SDK test framework to
host the provider in the same process as the test driver, while running
a production Terraform binary against the provider. This allows for Go's
built-in race detector and test coverage tooling to work as expected in
provider tests.
Unmanaged providers are expected to work in the exact same way as
managed providers, with one caveat: Terraform kills provider processes
and restarts them once per graph walk, meaning multiple times during
most Terraform CLI commands. As unmanaged providers can't be killed by
Terraform, and have no visibility into graph walks, unmanaged providers
are likely to have differences in how their global mutable state behaves
when compared to managed providers. Namely, unmanaged providers are
likely to retain global state when managed providers would have reset
it. Developers relying on global state should be aware of this.
2020-05-26 19:48:57 -05:00
|
|
|
|
|
|
|
// parse information on reattaching to unmanaged providers out of a
|
|
|
|
// JSON-encoded environment variable.
|
|
|
|
func parseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfig, error) {
|
|
|
|
unmanagedProviders := map[addrs.Provider]*plugin.ReattachConfig{}
|
|
|
|
if in != "" {
|
|
|
|
type reattachConfig struct {
|
|
|
|
Protocol string
|
|
|
|
Addr struct {
|
|
|
|
Network string
|
|
|
|
String string
|
|
|
|
}
|
|
|
|
Pid int
|
|
|
|
Test bool
|
|
|
|
}
|
|
|
|
var m map[string]reattachConfig
|
|
|
|
err := json.Unmarshal([]byte(in), &m)
|
|
|
|
if err != nil {
|
|
|
|
return unmanagedProviders, fmt.Errorf("Invalid format for TF_REATTACH_PROVIDERS: %w", err)
|
|
|
|
}
|
|
|
|
for p, c := range m {
|
|
|
|
a, diags := addrs.ParseProviderSourceString(p)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
return unmanagedProviders, fmt.Errorf("Error parsing %q as a provider address: %w", a, diags.Err())
|
|
|
|
}
|
|
|
|
var addr net.Addr
|
|
|
|
switch c.Addr.Network {
|
|
|
|
case "unix":
|
|
|
|
addr, err = net.ResolveUnixAddr("unix", c.Addr.String)
|
|
|
|
if err != nil {
|
|
|
|
return unmanagedProviders, fmt.Errorf("Invalid unix socket path %q for %q: %w", c.Addr.String, p, err)
|
|
|
|
}
|
|
|
|
case "tcp":
|
|
|
|
addr, err = net.ResolveTCPAddr("tcp", c.Addr.String)
|
|
|
|
if err != nil {
|
|
|
|
return unmanagedProviders, fmt.Errorf("Invalid TCP address %q for %q: %w", c.Addr.String, p, err)
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return unmanagedProviders, fmt.Errorf("Unknown address type %q for %q", c.Addr.Network, p)
|
|
|
|
}
|
|
|
|
unmanagedProviders[a] = &plugin.ReattachConfig{
|
|
|
|
Protocol: plugin.Protocol(c.Protocol),
|
|
|
|
Pid: c.Pid,
|
|
|
|
Test: c.Test,
|
|
|
|
Addr: addr,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return unmanagedProviders, nil
|
|
|
|
}
|