2023-05-02 10:33:06 -05:00
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
2014-05-24 14:04:43 -05:00
package main
import (
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
"context"
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"
"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-05-24 14:04:43 -05:00
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
"github.com/apparentlymart/go-shquot/shquot"
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"
2021-05-17 14:00:50 -05:00
"github.com/hashicorp/terraform/internal/addrs"
2021-05-17 14:07:38 -05:00
"github.com/hashicorp/terraform/internal/command/cliconfig"
"github.com/hashicorp/terraform/internal/command/format"
2020-11-18 12:50:34 -06:00
"github.com/hashicorp/terraform/internal/didyoumean"
2021-05-17 11:54:53 -05:00
"github.com/hashicorp/terraform/internal/httpclient"
2020-10-21 08:45:00 -05:00
"github.com/hashicorp/terraform/internal/logging"
2021-01-11 20:15:04 -06:00
"github.com/hashicorp/terraform/internal/terminal"
2019-10-11 04:34:26 -05:00
"github.com/hashicorp/terraform/version"
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"
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
"go.opentelemetry.io/otel/trace"
2018-10-31 10:45:03 -05:00
2021-05-17 10:42:17 -05:00
backendInit "github.com/hashicorp/terraform/internal/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"
2020-10-21 13:57:46 -05:00
// The parent process will create a file to collect crash logs
envTmpLogPath = "TF_TEMP_LOG_PATH"
2017-02-13 16:05:37 -06:00
)
2020-11-30 11:13:54 -06:00
// ui wraps the primary output cli.Ui, and redirects Warn calls to Output
// calls. This ensures that warnings are sent to stdout, and are properly
// serialized within the stdout stream.
type ui struct {
cli . Ui
}
func ( u * ui ) Warn ( msg string ) {
u . Ui . Output ( msg )
}
2017-09-01 17:58:38 -05:00
func init ( ) {
2020-11-30 11:13:54 -06:00
Ui = & ui { & cli . BasicUi {
2020-10-21 12:06:23 -05:00
Writer : os . Stdout ,
ErrorWriter : os . Stderr ,
Reader : os . Stdin ,
2020-11-30 11:13:54 -06:00
} }
2017-09-01 17:58:38 -05:00
}
2021-10-26 14:54:09 -05:00
func main ( ) {
os . Exit ( realMain ( ) )
}
func realMain ( ) int {
2021-10-27 15:33:35 -05:00
defer logging . PanicHandler ( )
2017-10-19 19:01:02 -05:00
var err error
2023-07-07 20:56:29 -05:00
err = openTelemetryInit ( )
if err != nil {
// openTelemetryInit can only fail if Terraform was run with an
// explicit environment variable to enable telemetry collection,
// so in typical use we cannot get here.
Ui . Error ( fmt . Sprintf ( "Could not initialize telemetry: %s" , err ) )
Ui . Error ( fmt . Sprintf ( "Unset environment variable %s if you don't intend to collect telemetry from Terraform." , openTelemetryExporterEnvVar ) )
return 1
}
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
var ctx context . Context
var otelSpan trace . Span
{
// At minimum we emit a span covering the entire command execution.
_ , displayArgs := shquot . POSIXShellSplit ( os . Args )
ctx , otelSpan = tracer . Start ( context . Background ( ) , fmt . Sprintf ( "terraform %s" , displayArgs ) )
defer otelSpan . End ( )
}
2023-07-07 20:56:29 -05:00
2020-10-21 13:57:46 -05:00
tmpLogPath := os . Getenv ( envTmpLogPath )
if tmpLogPath != "" {
f , err := os . OpenFile ( tmpLogPath , os . O_RDWR | os . O_APPEND , 0666 )
if err == nil {
defer f . Close ( )
log . Printf ( "[DEBUG] Adding temp file log sink: %s" , f . Name ( ) )
logging . RegisterSink ( f )
} else {
log . Printf ( "[ERROR] Could not open temp log file: %v" , err )
}
}
2014-10-21 00:32:00 -05:00
log . Printf (
2021-01-12 15:35:30 -06:00
"[INFO] Terraform version: %s %s" ,
Version , VersionPrerelease )
2021-11-04 18:20:51 -05:00
for _ , depMod := range version . InterestingDependencies ( ) {
log . Printf ( "[DEBUG] using %s %s" , depMod . Path , depMod . Version )
}
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 )
Experiments supported only in alpha/dev builds
We originally introduced the idea of language experiments as a way to get
early feedback on not-yet-proven feature ideas, ideally as part of the
initial exploration of the solution space rather than only after a
solution has become relatively clear.
Unfortunately, our tradeoff of making them available in normal releases
behind an explicit opt-in in order to make it easier to participate in the
feedback process had the unintended side-effect of making it feel okay
to use experiments in production and endure the warnings they generate.
This in turn has made us reluctant to make use of the experiments feature
lest experiments become de-facto production features which we then feel
compelled to preserve even though we aren't yet ready to graduate them
to stable features.
In an attempt to tweak that compromise, here we make the availability of
experiments _at all_ a build-time flag which will not be set by default,
and therefore experiments will not be available in most release builds.
The intent (not yet implemented in this PR) is for our release process to
set this flag only when it knows it's building an alpha release or a
development snapshot not destined for release at all, which will therefore
allow us to still use the alpha releases as a vehicle for giving feedback
participants access to a feature (without needing to install a Go
toolchain) but will not encourage pretending that these features are
production-ready before they graduate from experimental.
Only language experiments have an explicit framework for dealing with them
which outlives any particular experiment, so most of the changes here are
to that generalized mechanism. However, the intent is that non-language
experiments, such as experimental CLI commands, would also in future
check Meta.AllowExperimentalFeatures and gate the use of those experiments
too, so that we can be consistent that experimental features will never
be available unless you explicitly choose to use an alpha release or
a custom build from source code.
Since there are already some experiments active at the time of this commit
which were not previously subject to this restriction, we'll pragmatically
leave those as exceptions that will remain generally available for now,
and so this new approach will apply only to new experiments started in the
future. Once those experiments have all concluded, we will be left with
no more exceptions unless we explicitly choose to make an exception for
some reason we've not imagined yet.
It's important that we be able to write tests that rely on experiments
either being available or not being available, so here we're using our
typical approach of making "package main" deal with the global setting
that applies to Terraform CLI executables while making the layers below
all support fine-grain selection of this behavior so that tests with
different needs can run concurrently without trampling on one another.
As a compromise, the integration tests in the terraform package will
run with experiments enabled _by default_ since we commonly need to
exercise experiments in those tests, but they can selectively opt-out
if they need to by overriding the loader setting back to false again.
2022-04-27 13:14:51 -05:00
if ExperimentsAllowed ( ) {
log . Printf ( "[INFO] This build of Terraform allows using experimental features" )
}
2014-05-24 14:04:43 -05:00
2021-10-26 14:54:09 -05:00
streams , err := terminal . Init ( )
2021-01-11 20:15:04 -06:00
if err != nil {
Ui . Error ( fmt . Sprintf ( "Failed to configure the terminal: %s" , err ) )
return 1
}
if streams . Stdout . IsTerminal ( ) {
log . Printf ( "[TRACE] Stdout is a terminal of width %d" , streams . Stdout . Columns ( ) )
} else {
log . Printf ( "[TRACE] Stdout is not a terminal" )
}
if streams . Stderr . IsTerminal ( ) {
log . Printf ( "[TRACE] Stderr is a terminal of width %d" , streams . Stderr . Columns ( ) )
} else {
log . Printf ( "[TRACE] Stderr is not a terminal" )
}
if streams . Stdin . IsTerminal ( ) {
log . Printf ( "[TRACE] Stdin is a terminal" )
} else {
log . Printf ( "[TRACE] Stdin is not a terminal" )
}
main: new global option -chdir
This new option is intended to address the previous inconsistencies where
some older subcommands supported partially changing the target directory
(where Terraform would use the new directory inconsistently) where newer
commands did not support that override at all.
Instead, now Terraform will accept a -chdir command at the start of the
command line (before the subcommand) and will interpret it as a request
to direct all actions that would normally be taken in the current working
directory into the target directory instead. This is similar to options
offered by some other similar tools, such as the -C option in "make".
The new option is only accepted at the start of the command line (before
the subcommand) as a way to reflect that it is a global command (not
specific to a particular subcommand) and that it takes effect _before_
executing the subcommand. This also means it'll be forced to appear before
any other command-specific arguments that take file paths, which hopefully
communicates that those other arguments are interpreted relative to the
overridden path.
As a measure of pragmatism for existing uses, the path.cwd object in
the Terraform language will continue to return the _original_ working
directory (ignoring -chdir), in case that is important in some exceptional
workflows. The path.root object gives the root module directory, which
will always match the overriden working directory unless the user
simultaneously uses one of the legacy directory override arguments, which
is not a pattern we intend to support in the long run.
As a first step down the deprecation path, this commit adjusts the
documentation to de-emphasize the inconsistent old command line arguments,
including specific guidance on what to use instead for the main three
workflow commands, but all of those options remain supported in the same
way as they were before. In a later commit we'll make those arguments
produce a visible deprecation warning in Terraform's output, and then
in an even later commit we'll remove them entirely so that -chdir is the
single supported way to run Terraform from a directory other than the
one containing the root module configuration.
2020-09-01 17:45:12 -05:00
// NOTE: We're intentionally calling LoadConfig _before_ handling a possible
// -chdir=... option on the command line, so that a possible relative
// path in the TERRAFORM_CONFIG_FILE environment variable (though probably
// ill-advised) will be resolved relative to the true working directory,
// not the overridden one.
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-10-14 20:00:23 -05:00
providerDevOverrides := providerDevOverrides ( config . ProviderInstallation )
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
2021-06-24 15:41:58 -05:00
// Terraform's behalf using this environment variable. This is used
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
// 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 )
main: new global option -chdir
This new option is intended to address the previous inconsistencies where
some older subcommands supported partially changing the target directory
(where Terraform would use the new directory inconsistently) where newer
commands did not support that override at all.
Instead, now Terraform will accept a -chdir command at the start of the
command line (before the subcommand) and will interpret it as a request
to direct all actions that would normally be taken in the current working
directory into the target directory instead. This is similar to options
offered by some other similar tools, such as the -C option in "make".
The new option is only accepted at the start of the command line (before
the subcommand) as a way to reflect that it is a global command (not
specific to a particular subcommand) and that it takes effect _before_
executing the subcommand. This also means it'll be forced to appear before
any other command-specific arguments that take file paths, which hopefully
communicates that those other arguments are interpreted relative to the
overridden path.
As a measure of pragmatism for existing uses, the path.cwd object in
the Terraform language will continue to return the _original_ working
directory (ignoring -chdir), in case that is important in some exceptional
workflows. The path.root object gives the root module directory, which
will always match the overriden working directory unless the user
simultaneously uses one of the legacy directory override arguments, which
is not a pattern we intend to support in the long run.
As a first step down the deprecation path, this commit adjusts the
documentation to de-emphasize the inconsistent old command line arguments,
including specific guidance on what to use instead for the main three
workflow commands, but all of those options remain supported in the same
way as they were before. In a later commit we'll make those arguments
produce a visible deprecation warning in Terraform's output, and then
in an even later commit we'll remove them entirely so that -chdir is the
single supported way to run Terraform from a directory other than the
one containing the root module configuration.
2020-09-01 17:45:12 -05:00
// Get the command line args.
binName := filepath . Base ( os . Args [ 0 ] )
args := os . Args [ 1 : ]
originalWd , err := os . Getwd ( )
if err != nil {
// It would be very strange to end up here
Ui . Error ( fmt . Sprintf ( "Failed to determine current working directory: %s" , err ) )
return 1
}
// The arguments can begin with a -chdir option to ask Terraform to switch
// to a different working directory for the rest of its work. If that
// option is present then extractChdirOption returns a trimmed args with that option removed.
overrideWd , args , err := extractChdirOption ( args )
if err != nil {
Ui . Error ( fmt . Sprintf ( "Invalid -chdir option: %s" , err ) )
return 1
}
if overrideWd != "" {
2020-10-13 13:22:25 -05:00
err := os . Chdir ( overrideWd )
main: new global option -chdir
This new option is intended to address the previous inconsistencies where
some older subcommands supported partially changing the target directory
(where Terraform would use the new directory inconsistently) where newer
commands did not support that override at all.
Instead, now Terraform will accept a -chdir command at the start of the
command line (before the subcommand) and will interpret it as a request
to direct all actions that would normally be taken in the current working
directory into the target directory instead. This is similar to options
offered by some other similar tools, such as the -C option in "make".
The new option is only accepted at the start of the command line (before
the subcommand) as a way to reflect that it is a global command (not
specific to a particular subcommand) and that it takes effect _before_
executing the subcommand. This also means it'll be forced to appear before
any other command-specific arguments that take file paths, which hopefully
communicates that those other arguments are interpreted relative to the
overridden path.
As a measure of pragmatism for existing uses, the path.cwd object in
the Terraform language will continue to return the _original_ working
directory (ignoring -chdir), in case that is important in some exceptional
workflows. The path.root object gives the root module directory, which
will always match the overriden working directory unless the user
simultaneously uses one of the legacy directory override arguments, which
is not a pattern we intend to support in the long run.
As a first step down the deprecation path, this commit adjusts the
documentation to de-emphasize the inconsistent old command line arguments,
including specific guidance on what to use instead for the main three
workflow commands, but all of those options remain supported in the same
way as they were before. In a later commit we'll make those arguments
produce a visible deprecation warning in Terraform's output, and then
in an even later commit we'll remove them entirely so that -chdir is the
single supported way to run Terraform from a directory other than the
one containing the root module configuration.
2020-09-01 17:45:12 -05:00
if err != nil {
Ui . Error ( fmt . Sprintf ( "Error handling -chdir option: %s" , err ) )
return 1
}
}
2017-09-01 17:58:38 -05:00
// In tests, Commands may already be set to provide mock commands
if Commands == nil {
main: new global option -chdir
This new option is intended to address the previous inconsistencies where
some older subcommands supported partially changing the target directory
(where Terraform would use the new directory inconsistently) where newer
commands did not support that override at all.
Instead, now Terraform will accept a -chdir command at the start of the
command line (before the subcommand) and will interpret it as a request
to direct all actions that would normally be taken in the current working
directory into the target directory instead. This is similar to options
offered by some other similar tools, such as the -C option in "make".
The new option is only accepted at the start of the command line (before
the subcommand) as a way to reflect that it is a global command (not
specific to a particular subcommand) and that it takes effect _before_
executing the subcommand. This also means it'll be forced to appear before
any other command-specific arguments that take file paths, which hopefully
communicates that those other arguments are interpreted relative to the
overridden path.
As a measure of pragmatism for existing uses, the path.cwd object in
the Terraform language will continue to return the _original_ working
directory (ignoring -chdir), in case that is important in some exceptional
workflows. The path.root object gives the root module directory, which
will always match the overriden working directory unless the user
simultaneously uses one of the legacy directory override arguments, which
is not a pattern we intend to support in the long run.
As a first step down the deprecation path, this commit adjusts the
documentation to de-emphasize the inconsistent old command line arguments,
including specific guidance on what to use instead for the main three
workflow commands, but all of those options remain supported in the same
way as they were before. In a later commit we'll make those arguments
produce a visible deprecation warning in Terraform's output, and then
in an even later commit we'll remove them entirely so that -chdir is the
single supported way to run Terraform from a directory other than the
one containing the root module configuration.
2020-09-01 17:45:12 -05:00
// Commands get to hold on to the original working directory here,
// in case they need to refer back to it for any special reason, though
// they should primarily be working with the override working directory
// that we've now switched to above.
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
initCommands ( ctx , originalWd , streams , config , services , providerSrc , providerDevOverrides , unmanagedProviders )
2017-09-01 17:58:38 -05:00
}
2014-10-13 16:05:29 -05:00
// Run checkpoint
command: Start of propagating OpenTelemetry context
Several times over the years we've considered adding tracing
instrumentation to Terraform, since even when running in isolation as a
CLI program it has a "distributed system-like" structure, with lots of
concurrent internal work and also some work delegated to provider plugins
that are essentially temporarily-running microservices.
However, it's always felt a bit overwhelming to do it because much of
Terraform predates the Go context.Context idiom and so it's tough to get
a clean chain of context.Context values all the way down the stack without
disturbing a lot of existing APIs.
This commit aims to just get that process started by establishing how a
context can propagate from "package main" into the command package,
focusing initially on "terraform init" and some other commands that share
some underlying functions with that command.
OpenTelemetry has emerged as a de-facto industry standard and so this uses
its API directly, without any attempt to hide it behind an abstraction.
The OpenTelemetry API is itself already an adapter layer, so we should be
able to swap in any backend that uses comparable concepts. For now we just
discard the tracing reports by default, and allow users to opt in to
delivering traces over OTLP by setting an environment variable when
running Terraform (the environment variable was established in an earlier
commit, so this commit builds on that.)
When tracing collection is enabled, every Terraform CLI run will generate
at least one overall span representing the command that was run. Some
commands might also create child spans, but most currently do not.
2023-07-10 13:29:57 -05:00
go runCheckpoint ( ctx , 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 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
}
2020-11-18 12:50:34 -06:00
// Before we continue we'll check whether the requested command is
// actually known. If not, we might be able to suggest an alternative
// if it seems like the user made a typo.
// (This bypasses the built-in help handling in cli.CLI for the situation
// where a command isn't found, because it's likely more helpful to
// mention what specifically went wrong, rather than just printing out
// a big block of usage information.)
2021-03-31 12:24:43 -05:00
// Check if this is being run via shell auto-complete, which uses the
// binary name as the first argument and won't be listed as a subcommand.
autoComplete := os . Getenv ( "COMP_LINE" ) != ""
if cmd := cliRunner . Subcommand ( ) ; cmd != "" && ! autoComplete {
2020-11-18 12:50:34 -06:00
// Due to the design of cli.CLI, this special error message only works
// for typos of top-level commands. For a subcommand typo, like
// "terraform state posh", cmd would be "state" here and thus would
// be considered to exist, and it would print out its own usage message.
if _ , exists := Commands [ cmd ] ; ! exists {
suggestions := make ( [ ] string , 0 , len ( Commands ) )
for name := range Commands {
suggestions = append ( suggestions , name )
}
suggestion := didyoumean . NameSuggestion ( cmd , suggestions )
if suggestion != "" {
suggestion = fmt . Sprintf ( " Did you mean %q?" , suggestion )
}
fmt . Fprintf ( os . Stderr , "Terraform has no command named %q.%s\n\nTo see all of Terraform's top-level commands, run:\n terraform -help\n\n" , cmd , suggestion )
return 1
}
}
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
}
2020-10-23 12:09:29 -05:00
// if we are exiting with a non-zero code, check if it was caused by any
// plugins crashing
if exitCode != 0 {
for _ , panicLog := range logging . PluginPanics ( ) {
2021-10-27 16:13:00 -05:00
Ui . Error ( panicLog )
2020-10-23 12:09:29 -05:00
}
}
2014-05-24 14:04:43 -05:00
return exitCode
}
2014-06-10 12:28:47 -05: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 {
2021-05-18 09:59:14 -05:00
Protocol string
ProtocolVersion int
Addr struct {
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
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 {
2021-05-18 09:59:14 -05:00
Protocol : plugin . Protocol ( c . Protocol ) ,
ProtocolVersion : c . ProtocolVersion ,
Pid : c . Pid ,
Test : c . Test ,
Addr : addr ,
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
}
}
}
return unmanagedProviders , nil
}
main: new global option -chdir
This new option is intended to address the previous inconsistencies where
some older subcommands supported partially changing the target directory
(where Terraform would use the new directory inconsistently) where newer
commands did not support that override at all.
Instead, now Terraform will accept a -chdir command at the start of the
command line (before the subcommand) and will interpret it as a request
to direct all actions that would normally be taken in the current working
directory into the target directory instead. This is similar to options
offered by some other similar tools, such as the -C option in "make".
The new option is only accepted at the start of the command line (before
the subcommand) as a way to reflect that it is a global command (not
specific to a particular subcommand) and that it takes effect _before_
executing the subcommand. This also means it'll be forced to appear before
any other command-specific arguments that take file paths, which hopefully
communicates that those other arguments are interpreted relative to the
overridden path.
As a measure of pragmatism for existing uses, the path.cwd object in
the Terraform language will continue to return the _original_ working
directory (ignoring -chdir), in case that is important in some exceptional
workflows. The path.root object gives the root module directory, which
will always match the overriden working directory unless the user
simultaneously uses one of the legacy directory override arguments, which
is not a pattern we intend to support in the long run.
As a first step down the deprecation path, this commit adjusts the
documentation to de-emphasize the inconsistent old command line arguments,
including specific guidance on what to use instead for the main three
workflow commands, but all of those options remain supported in the same
way as they were before. In a later commit we'll make those arguments
produce a visible deprecation warning in Terraform's output, and then
in an even later commit we'll remove them entirely so that -chdir is the
single supported way to run Terraform from a directory other than the
one containing the root module configuration.
2020-09-01 17:45:12 -05:00
func extractChdirOption ( args [ ] string ) ( string , [ ] string , error ) {
if len ( args ) == 0 {
return "" , args , nil
}
const argName = "-chdir"
const argPrefix = argName + "="
var argValue string
var argPos int
for i , arg := range args {
if ! strings . HasPrefix ( arg , "-" ) {
// Because the chdir option is a subcommand-agnostic one, we require
// it to appear before any subcommand argument, so if we find a
// non-option before we find -chdir then we are finished.
break
}
if arg == argName || arg == argPrefix {
return "" , args , fmt . Errorf ( "must include an equals sign followed by a directory path, like -chdir=example" )
}
if strings . HasPrefix ( arg , argPrefix ) {
argPos = i
argValue = arg [ len ( argPrefix ) : ]
}
}
// When we fall out here, we'll have populated argValue with a non-empty
// string if the -chdir=... option was present and valid, or left it
// empty if it wasn't present.
if argValue == "" {
return "" , args , nil
}
// If we did find the option then we'll need to produce a new args that
// doesn't include it anymore.
if argPos == 0 {
// Easy case: we can just slice off the front
return argValue , args [ 1 : ] , nil
}
// Otherwise we need to construct a new array and copy to it.
newArgs := make ( [ ] string , len ( args ) - 1 )
copy ( newArgs , args [ : argPos ] )
copy ( newArgs [ argPos : ] , args [ argPos + 1 : ] )
return argValue , newArgs , nil
}