2023-05-02 10:33:06 -05:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2017-02-28 12:13:03 -06:00
|
|
|
"bytes"
|
2018-02-09 17:51:29 -06:00
|
|
|
"context"
|
2017-03-29 15:45:25 -05:00
|
|
|
"errors"
|
2014-07-18 13:37:27 -05:00
|
|
|
"flag"
|
2014-07-12 22:37:30 -05:00
|
|
|
"fmt"
|
2016-11-21 17:05:49 -06:00
|
|
|
"io/ioutil"
|
2016-11-02 12:30:28 -05:00
|
|
|
"log"
|
2014-07-12 22:37:30 -05:00
|
|
|
"os"
|
2014-09-22 13:15:27 -05:00
|
|
|
"path/filepath"
|
2015-04-30 09:59:14 -05:00
|
|
|
"strconv"
|
2017-01-18 22:50:45 -06:00
|
|
|
"strings"
|
2016-11-21 17:05:49 -06:00
|
|
|
"time"
|
2014-07-12 22:37:30 -05: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
|
|
|
plugin "github.com/hashicorp/go-plugin"
|
2019-10-11 04:34:26 -05:00
|
|
|
"github.com/hashicorp/terraform-svchost/disco"
|
2021-09-01 19:01:44 -05:00
|
|
|
"github.com/mitchellh/cli"
|
|
|
|
"github.com/mitchellh/colorstring"
|
|
|
|
|
2021-05-17 14:00:50 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2021-05-17 10:42:17 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/backend"
|
|
|
|
"github.com/hashicorp/terraform/internal/backend/local"
|
2021-05-17 14:07:38 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/command/arguments"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/format"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/views"
|
|
|
|
"github.com/hashicorp/terraform/internal/command/webbrowser"
|
2021-09-01 19:01:44 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/command/workdir"
|
2022-08-30 17:01:44 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/configs"
|
2021-05-17 14:17:09 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/configs/configload"
|
2020-01-16 19:42:41 -06:00
|
|
|
"github.com/hashicorp/terraform/internal/getproviders"
|
2021-09-01 19:01:44 -05:00
|
|
|
legacy "github.com/hashicorp/terraform/internal/legacy/terraform"
|
2021-05-17 12:40:40 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
2021-05-17 12:51:48 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/provisioners"
|
2022-08-30 17:01:44 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/states"
|
2021-01-11 20:20:58 -06:00
|
|
|
"github.com/hashicorp/terraform/internal/terminal"
|
2021-05-17 14:46:19 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
2021-05-17 12:11:06 -05:00
|
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
2014-07-12 22:21:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Meta are the meta-options that are available on all or most commands.
|
|
|
|
type Meta struct {
|
2017-01-18 22:50:45 -06:00
|
|
|
// The exported fields below should be set by anyone using a
|
|
|
|
// command with a Meta field. These are expected to be set externally
|
|
|
|
// (not from within the command itself).
|
2014-07-28 00:58:35 -05:00
|
|
|
|
2021-09-01 19:01:44 -05:00
|
|
|
// WorkingDir is an object representing the "working directory" where we're
|
|
|
|
// running commands. In the normal case this literally refers to the
|
|
|
|
// working directory of the Terraform process, though this can take on
|
|
|
|
// a more symbolic meaning when the user has overridden default behavior
|
|
|
|
// to specify a different working directory or to override the special
|
|
|
|
// data directory where we'll persist settings that must survive between
|
|
|
|
// consecutive commands.
|
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
|
|
|
//
|
2021-09-01 19:01:44 -05:00
|
|
|
// We're currently gradually migrating the various bits of state that
|
|
|
|
// must persist between consecutive commands in a session to be encapsulated
|
|
|
|
// in here, but we're not there yet and so there are also some methods on
|
|
|
|
// Meta which directly read and modify paths inside the data directory.
|
|
|
|
WorkingDir *workdir.Dir
|
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
|
|
|
|
2021-01-11 20:20:58 -06:00
|
|
|
// Streams tracks the raw Stdout, Stderr, and Stdin handles along with
|
|
|
|
// some basic metadata about them, such as whether each is connected to
|
|
|
|
// a terminal, how wide the possible terminal is, etc.
|
|
|
|
//
|
|
|
|
// For historical reasons this might not be set in unit test code, and
|
|
|
|
// so functions working with this field must check if it's nil and
|
|
|
|
// do some default behavior instead if so, rather than panicking.
|
|
|
|
Streams *terminal.Streams
|
|
|
|
|
cli: Add initial command views abstraction
Terraform supports multiple output formats for several sub-commands.
The default format is user-readable text, but many sub-commands support
a `-json` flag to output a machine-readable format for the result. The
output command also supports a `-raw` flag for a simpler, scripting-
focused machine readable format.
This commit adds a "views" abstraction, intended to help ensure
consistency between the various output formats. This extracts the render
specific code from the command package, and moves it into a views
package. Each command is expected to create an interface for its view,
and one or more implementations of that interface.
By doing so, we separate the concerns of generating the sub-command
result from rendering the result in the specified output format. This
should make it easier to ensure that all output formats will be updated
together when changes occur in the result-generating phase.
There are some other consequences of this restructuring:
- Views now directly access the terminal streams, rather than the
now-redundant cli.Ui instance;
- With the reorganization of commands, parsing CLI arguments is now the
responsibility of a separate "arguments" package.
For now, views are added only for the output sub-command, as an example.
Because this command uses code which is shared with the apply and
refresh commands, those are also partially updated.
2021-01-27 14:51:40 -06:00
|
|
|
View *views.View
|
|
|
|
|
2020-10-14 20:00:23 -05:00
|
|
|
Color bool // True if output should be colored
|
|
|
|
GlobalPluginDirs []string // Additional paths to search for plugins
|
|
|
|
Ui cli.Ui // Ui for output
|
2014-07-12 22:59:16 -05:00
|
|
|
|
2017-10-18 10:52:13 -05:00
|
|
|
// Services provides access to remote endpoint information for
|
|
|
|
// "terraform-native' services running at a specific user-facing hostname.
|
|
|
|
Services *disco.Disco
|
|
|
|
|
2017-09-08 19:14:37 -05:00
|
|
|
// RunningInAutomation indicates that commands are being run by an
|
|
|
|
// automated system rather than directly at a command prompt.
|
|
|
|
//
|
|
|
|
// This is a hint to various command routines that it may be confusing
|
|
|
|
// to print out messages that suggest running specific follow-up
|
|
|
|
// commands, since the user consuming the output will not be
|
|
|
|
// in a position to run such commands.
|
|
|
|
//
|
|
|
|
// The intended use-case of this flag is when Terraform is running in
|
|
|
|
// some sort of workflow orchestration tool which is abstracting away
|
|
|
|
// the specific commands being run.
|
|
|
|
RunningInAutomation bool
|
|
|
|
|
2019-07-09 14:06:20 -05:00
|
|
|
// CLIConfigDir is the directory from which CLI configuration files were
|
|
|
|
// read by the caller and the directory where any changes to CLI
|
|
|
|
// configuration files by commands should be made.
|
|
|
|
//
|
|
|
|
// If this is empty then no configuration directory is available and
|
|
|
|
// commands which require one cannot proceed.
|
|
|
|
CLIConfigDir string
|
|
|
|
|
2017-09-01 18:05:05 -05:00
|
|
|
// PluginCacheDir, if non-empty, enables caching of downloaded plugins
|
|
|
|
// into the given directory.
|
|
|
|
PluginCacheDir string
|
|
|
|
|
2023-01-10 18:26:11 -06:00
|
|
|
// PluginCacheMayBreakDependencyLockFile is a temporary CLI configuration-based
|
|
|
|
// opt out for the behavior of only using the plugin cache dir if its
|
|
|
|
// contents match checksums recorded in the dependency lock file.
|
|
|
|
//
|
|
|
|
// This is an accommodation for those who currently essentially ignore the
|
|
|
|
// dependency lock file -- treating it only as transient working directory
|
|
|
|
// state -- and therefore don't care if the plugin cache dir causes the
|
|
|
|
// checksums inside to only be sufficient for the computer where Terraform
|
|
|
|
// is currently running.
|
|
|
|
//
|
|
|
|
// We intend to remove this exception again (making the CLI configuration
|
|
|
|
// setting a silent no-op) in future once we've improved the dependency
|
|
|
|
// lock file mechanism so that it's usable for everyone and there are no
|
|
|
|
// longer any compelling reasons for folks to not lock their dependencies.
|
|
|
|
PluginCacheMayBreakDependencyLockFile bool
|
|
|
|
|
2020-01-16 19:42:41 -06:00
|
|
|
// ProviderSource allows determining the available versions of a provider
|
|
|
|
// and determines where a distribution package for a particular
|
|
|
|
// provider version can be obtained.
|
|
|
|
ProviderSource getproviders.Source
|
|
|
|
|
2019-08-29 17:50:03 -05:00
|
|
|
// BrowserLauncher is used by commands that need to open a URL in a
|
|
|
|
// web browser.
|
|
|
|
BrowserLauncher webbrowser.Launcher
|
|
|
|
|
2017-12-01 10:03:41 -06:00
|
|
|
// When this channel is closed, the command will be cancelled.
|
|
|
|
ShutdownCh <-chan struct{}
|
|
|
|
|
2020-10-14 20:00:23 -05:00
|
|
|
// ProviderDevOverrides are providers where we ignore the lock file, the
|
|
|
|
// configured version constraints, and the local cache directory and just
|
|
|
|
// always use exactly the path specified. This is intended to allow
|
|
|
|
// provider developers to easily test local builds without worrying about
|
|
|
|
// what version number they might eventually be released as, or what
|
|
|
|
// checksums they have.
|
|
|
|
ProviderDevOverrides map[addrs.Provider]getproviders.PackageLocalDir
|
|
|
|
|
|
|
|
// UnmanagedProviders are a set of providers that exist as processes
|
|
|
|
// predating Terraform, which Terraform should use but not worry about the
|
|
|
|
// lifecycle of.
|
|
|
|
//
|
|
|
|
// This is essentially a more extreme version of ProviderDevOverrides where
|
|
|
|
// Terraform doesn't even worry about how the provider server gets launched,
|
|
|
|
// just trusting that someone else did it before running Terraform.
|
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
|
|
|
UnmanagedProviders map[addrs.Provider]*plugin.ReattachConfig
|
|
|
|
|
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
|
|
|
// AllowExperimentalFeatures controls whether a command that embeds this
|
|
|
|
// Meta is permitted to make use of experimental Terraform features.
|
|
|
|
//
|
|
|
|
// Set this field only during the initial creation of Meta. If you change
|
|
|
|
// this field after calling methods of type Meta then the resulting
|
|
|
|
// behavior is undefined.
|
|
|
|
//
|
|
|
|
// In normal code this would be set by package main only in builds
|
|
|
|
// explicitly marked as being alpha releases or development snapshots,
|
|
|
|
// making experimental features unavailable otherwise. Test code may
|
|
|
|
// choose to set this if it needs to exercise experimental features.
|
|
|
|
//
|
|
|
|
// Some experiments predated the addition of this setting, and may
|
|
|
|
// therefore still be available even if this flag is false. Our intent
|
|
|
|
// is that all/most _future_ experiments will be unavailable unless this
|
|
|
|
// flag is set, to reinforce that experiments are not for production use.
|
|
|
|
AllowExperimentalFeatures bool
|
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
//----------------------------------------------------------
|
|
|
|
// Protected: commands can set these
|
|
|
|
//----------------------------------------------------------
|
|
|
|
|
2017-06-15 13:26:12 -05:00
|
|
|
// pluginPath is a user defined set of directories to look for plugins.
|
|
|
|
// This is set during init with the `-plugin-dir` flag, saved to a file in
|
|
|
|
// the data directory.
|
2017-07-03 12:59:13 -05:00
|
|
|
// This overrides all other search paths when discovering plugins.
|
2017-06-15 13:26:12 -05:00
|
|
|
pluginPath []string
|
|
|
|
|
2017-04-13 20:05:58 -05:00
|
|
|
// Override certain behavior for tests within this package
|
|
|
|
testingOverrides *testingOverrides
|
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
//----------------------------------------------------------
|
|
|
|
// Private: do not set these
|
|
|
|
//----------------------------------------------------------
|
|
|
|
|
2018-02-28 19:09:48 -06:00
|
|
|
// configLoader is a shared configuration loader that is used by
|
|
|
|
// LoadConfig and other commands that access configuration files.
|
|
|
|
// It is initialized on first use.
|
|
|
|
configLoader *configload.Loader
|
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
// backendState is the currently active backend state
|
2020-11-18 12:41:33 -06:00
|
|
|
backendState *legacy.BackendState
|
2017-01-18 22:50:45 -06:00
|
|
|
|
2014-07-18 13:37:27 -05:00
|
|
|
// Variables for the context (private)
|
terraform: ugly huge change to weave in new HCL2-oriented types
Due to how deeply the configuration types go into Terraform Core, there
isn't a great way to switch out to HCL2 gradually. As a consequence, this
huge commit gets us from the old state to a _compilable_ new state, but
does not yet attempt to fix any tests and has a number of known missing
parts and bugs. We will continue to iterate on this in forthcoming
commits, heading back towards passing tests and making Terraform
fully-functional again.
The three main goals here are:
- Use the configuration models from the "configs" package instead of the
older models in the "config" package, which is now deprecated and
preserved only to help us write our migration tool.
- Do expression inspection and evaluation using the functionality of the
new "lang" package, instead of the Interpolator type and related
functionality in the main "terraform" package.
- Represent addresses of various objects using types in the addrs package,
rather than hand-constructed strings. This is not critical to support
the above, but was a big help during the implementation of these other
points since it made it much more explicit what kind of address is
expected in each context.
Since our new packages are built to accommodate some future planned
features that are not yet implemented (e.g. the "for_each" argument on
resources, "count"/"for_each" on modules), and since there's still a fair
amount of functionality still using old-style APIs, there is a moderate
amount of shimming here to connect new assumptions with old, hopefully in
a way that makes it easier to find and eliminate these shims later.
I apologize in advance to the person who inevitably just found this huge
commit while spelunking through the commit history.
2018-04-30 12:33:53 -05:00
|
|
|
variableArgs rawFlags
|
|
|
|
input bool
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
// Targets for this context (private)
|
2021-02-08 12:29:42 -06:00
|
|
|
targets []addrs.Targetable
|
|
|
|
targetFlags []string
|
2015-03-24 11:18:15 -05:00
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
// Internal fields
|
2014-07-17 11:34:32 -05:00
|
|
|
color bool
|
2014-07-12 22:59:16 -05:00
|
|
|
oldUi cli.Ui
|
2014-10-11 20:05:23 -05:00
|
|
|
|
2015-02-21 20:17:40 -06:00
|
|
|
// The fields below are expected to be set by the command via
|
|
|
|
// command line flags. See the Apply command for an example.
|
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// statePath is the path to the state file. If this is empty, then
|
|
|
|
// no state will be loaded. It is also okay for this to be a path to
|
|
|
|
// a file that doesn't exist; it is assumed that this means that there
|
|
|
|
// is simply no state.
|
2015-02-21 20:17:40 -06:00
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// stateOutPath is used to override the output path for the state.
|
|
|
|
// If not provided, the StatePath is used causing the old state to
|
2019-03-07 14:07:13 -06:00
|
|
|
// be overridden.
|
2015-02-21 20:17:40 -06:00
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// backupPath is used to backup the state file before writing a modified
|
2015-09-11 13:56:20 -05:00
|
|
|
// version. It defaults to stateOutPath + DefaultBackupExtension
|
2015-05-06 10:58:42 -05:00
|
|
|
//
|
|
|
|
// parallelism is used to control the number of concurrent operations
|
|
|
|
// allowed when walking the graph
|
2016-10-21 16:25:05 -05:00
|
|
|
//
|
2016-11-23 03:44:52 -06:00
|
|
|
// provider is to specify specific resource providers
|
2017-02-01 17:16:16 -06:00
|
|
|
//
|
2017-03-21 14:05:51 -05:00
|
|
|
// stateLock is set to false to disable state locking
|
|
|
|
//
|
2017-04-01 14:42:13 -05:00
|
|
|
// stateLockTimeout is the optional duration to retry a state locks locks
|
|
|
|
// when it is already locked by another process.
|
|
|
|
//
|
2017-03-21 14:05:51 -05:00
|
|
|
// forceInitCopy suppresses confirmation for copying state data during
|
|
|
|
// init.
|
2017-04-20 16:26:50 -05:00
|
|
|
//
|
|
|
|
// reconfigure forces init to ignore any stored configuration.
|
2019-12-10 13:06:06 -06:00
|
|
|
//
|
2021-05-14 16:36:54 -05:00
|
|
|
// migrateState confirms the user wishes to migrate from the prior backend
|
|
|
|
// configuration to a new configuration.
|
|
|
|
//
|
2019-12-10 13:06:06 -06:00
|
|
|
// compactWarnings (-compact-warnings) selects a more compact presentation
|
|
|
|
// of warnings in the output when they are not accompanied by errors.
|
2017-04-01 14:42:13 -05:00
|
|
|
statePath string
|
|
|
|
stateOutPath string
|
|
|
|
backupPath string
|
|
|
|
parallelism int
|
|
|
|
stateLock bool
|
|
|
|
stateLockTimeout time.Duration
|
|
|
|
forceInitCopy bool
|
2017-04-20 16:26:50 -05:00
|
|
|
reconfigure bool
|
2021-05-14 16:36:54 -05:00
|
|
|
migrateState bool
|
2019-12-10 13:06:06 -06:00
|
|
|
compactWarnings bool
|
2017-06-12 12:30:19 -05:00
|
|
|
|
backend: Validate remote backend Terraform version
When using the enhanced remote backend, a subset of all Terraform
operations are supported. Of these, only plan and apply can be executed
on the remote infrastructure (e.g. Terraform Cloud). Other operations
run locally and use the remote backend for state storage.
This causes problems when the local version of Terraform does not match
the configured version from the remote workspace. If the two versions
are incompatible, an `import` or `state mv` operation can cause the
remote workspace to be unusable until a manual fix is applied.
To prevent this from happening accidentally, this commit introduces a
check that the local Terraform version and the configured remote
workspace Terraform version are compatible. This check is skipped for
commands which do not write state, and can also be disabled by the use
of a new command-line flag, `-ignore-remote-version`.
Terraform version compatibility is defined as:
- For all releases before 0.14.0, local must exactly equal remote, as
two different versions cannot share state;
- 0.14.0 to 1.0.x are compatible, as we will not change the state
version number until at least Terraform 1.1.0;
- Versions after 1.1.0 must have the same major and minor versions, as
we will not change the state version number in a patch release.
If the two versions are incompatible, a diagnostic is displayed,
advising that the error can be suppressed with `-ignore-remote-version`.
When this flag is used, the diagnostic is still displayed, but as a
warning instead of an error.
Commands which will not write state can assert this fact by calling the
helper `meta.ignoreRemoteBackendVersionConflict`, which will disable the
checks. Those which can write state should instead call the helper
`meta.remoteBackendVersionCheck`, which will return diagnostics for
display.
In addition to these explicit paths for managing the version check, we
have an implicit check in the remote backend's state manager
initialization method. Both of the above helpers will disable this
check. This fallback is in place to ensure that future code paths which
access state cannot accidentally skip the remote version check.
2020-11-13 15:43:56 -06:00
|
|
|
// Used with commands which write state to allow users to write remote
|
|
|
|
// state even if the remote and local Terraform versions don't match.
|
|
|
|
ignoreRemoteVersion bool
|
2014-10-11 20:05:23 -05:00
|
|
|
}
|
|
|
|
|
2017-04-13 20:05:58 -05:00
|
|
|
type testingOverrides struct {
|
2020-03-30 17:30:56 -05:00
|
|
|
Providers map[addrs.Provider]providers.Factory
|
|
|
|
Provisioners map[string]provisioners.Factory
|
2017-04-13 20:05:58 -05:00
|
|
|
}
|
|
|
|
|
2014-10-11 20:05:23 -05:00
|
|
|
// initStatePaths is used to initialize the default values for
|
|
|
|
// statePath, stateOutPath, and backupPath
|
|
|
|
func (m *Meta) initStatePaths() {
|
|
|
|
if m.statePath == "" {
|
|
|
|
m.statePath = DefaultStateFilename
|
|
|
|
}
|
|
|
|
if m.stateOutPath == "" {
|
|
|
|
m.stateOutPath = m.statePath
|
|
|
|
}
|
|
|
|
if m.backupPath == "" {
|
2015-09-11 13:56:20 -05:00
|
|
|
m.backupPath = m.stateOutPath + DefaultBackupExtension
|
2014-10-11 20:05:23 -05:00
|
|
|
}
|
2014-07-12 22:21:46 -05:00
|
|
|
}
|
|
|
|
|
2014-10-11 20:34:11 -05:00
|
|
|
// StateOutPath returns the true output path for the state file
|
|
|
|
func (m *Meta) StateOutPath() string {
|
|
|
|
return m.stateOutPath
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
// Colorize returns the colorization structure for a command.
|
|
|
|
func (m *Meta) Colorize() *colorstring.Colorize {
|
2021-04-21 21:27:19 -05:00
|
|
|
colors := make(map[string]string)
|
|
|
|
for k, v := range colorstring.DefaultColors {
|
|
|
|
colors[k] = v
|
|
|
|
}
|
|
|
|
colors["purple"] = "38;5;57"
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
return &colorstring.Colorize{
|
2021-04-21 21:27:19 -05:00
|
|
|
Colors: colors,
|
2014-07-17 11:34:32 -05:00
|
|
|
Disable: !m.color,
|
2014-07-12 22:21:46 -05:00
|
|
|
Reset: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-01 19:01:44 -05:00
|
|
|
// fixupMissingWorkingDir is a compensation for various existing tests which
|
|
|
|
// directly construct incomplete "Meta" objects. Specifically, it deals with
|
|
|
|
// a test that omits a WorkingDir value by constructing one just-in-time.
|
|
|
|
//
|
|
|
|
// We shouldn't ever rely on this in any real codepath, because it doesn't
|
|
|
|
// take into account the various ways users can override our default
|
|
|
|
// directory selection behaviors.
|
|
|
|
func (m *Meta) fixupMissingWorkingDir() {
|
|
|
|
if m.WorkingDir == nil {
|
|
|
|
log.Printf("[WARN] This 'Meta' object is missing its WorkingDir, so we're creating a default one suitable only for tests")
|
|
|
|
m.WorkingDir = workdir.NewDir(".")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-04 22:52:06 -06:00
|
|
|
// DataDir returns the directory where local data will be stored.
|
2017-11-01 18:44:03 -05:00
|
|
|
// Defaults to DefaultDataDir in the current working directory.
|
2015-03-04 22:52:06 -06:00
|
|
|
func (m *Meta) DataDir() string {
|
2021-09-01 19:01:44 -05:00
|
|
|
m.fixupMissingWorkingDir()
|
|
|
|
return m.WorkingDir.DataDir()
|
2015-03-04 22:52:06 -06:00
|
|
|
}
|
|
|
|
|
2015-04-30 09:59:14 -05:00
|
|
|
const (
|
|
|
|
// InputModeEnvVar is the environment variable that, if set to "false" or
|
|
|
|
// "0", causes terraform commands to behave as if the `-input=false` flag was
|
|
|
|
// specified.
|
|
|
|
InputModeEnvVar = "TF_INPUT"
|
|
|
|
)
|
|
|
|
|
2014-10-08 12:29:54 -05:00
|
|
|
// InputMode returns the type of input we should ask for in the form of
|
|
|
|
// terraform.InputMode which is passed directly to Context.Input.
|
|
|
|
func (m *Meta) InputMode() terraform.InputMode {
|
|
|
|
if test || !m.input {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2015-04-30 09:59:14 -05:00
|
|
|
if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
|
|
|
|
if v, err := strconv.ParseBool(envVar); err == nil {
|
|
|
|
if !v {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-08 12:29:54 -05:00
|
|
|
var mode terraform.InputMode
|
|
|
|
mode |= terraform.InputModeProvider
|
|
|
|
|
|
|
|
return mode
|
2014-09-29 13:11:35 -05:00
|
|
|
}
|
|
|
|
|
2015-02-21 18:04:32 -06:00
|
|
|
// UIInput returns a UIInput object to be used for asking for input.
|
|
|
|
func (m *Meta) UIInput() terraform.UIInput {
|
|
|
|
return &UIInput{
|
|
|
|
Colorize: m.Colorize(),
|
2014-10-11 20:21:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 20:20:58 -06:00
|
|
|
// OutputColumns returns the number of columns that normal (non-error) UI
|
|
|
|
// output should be wrapped to fill.
|
|
|
|
//
|
|
|
|
// This is the column count to use if you'll be printing your message via
|
|
|
|
// the Output or Info methods of m.Ui.
|
|
|
|
func (m *Meta) OutputColumns() int {
|
|
|
|
if m.Streams == nil {
|
|
|
|
// A default for unit tests that don't populate Meta fully.
|
|
|
|
return 78
|
|
|
|
}
|
|
|
|
return m.Streams.Stdout.Columns()
|
|
|
|
}
|
|
|
|
|
|
|
|
// ErrorColumns returns the number of columns that error UI output should be
|
|
|
|
// wrapped to fill.
|
|
|
|
//
|
|
|
|
// This is the column count to use if you'll be printing your message via
|
|
|
|
// the Error or Warn methods of m.Ui.
|
|
|
|
func (m *Meta) ErrorColumns() int {
|
|
|
|
if m.Streams == nil {
|
|
|
|
// A default for unit tests that don't populate Meta fully.
|
|
|
|
return 78
|
|
|
|
}
|
|
|
|
return m.Streams.Stderr.Columns()
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:18:18 -06:00
|
|
|
// StdinPiped returns true if the input is piped.
|
|
|
|
func (m *Meta) StdinPiped() bool {
|
2021-01-11 20:20:58 -06:00
|
|
|
if m.Streams == nil {
|
|
|
|
// If we don't have m.Streams populated then we're presumably in a unit
|
|
|
|
// test that doesn't properly populate Meta, so we'll just say the
|
|
|
|
// output _isn't_ piped because that's the common case and so most likely
|
|
|
|
// to be useful to a unit test.
|
2016-11-14 00:18:18 -06:00
|
|
|
return false
|
|
|
|
}
|
2021-01-11 20:20:58 -06:00
|
|
|
return !m.Streams.Stdin.IsTerminal()
|
2016-11-14 00:18:18 -06:00
|
|
|
}
|
|
|
|
|
2020-09-28 19:13:32 -05:00
|
|
|
// InterruptibleContext returns a context.Context that will be cancelled
|
|
|
|
// if the process is interrupted by a platform-specific interrupt signal.
|
|
|
|
//
|
|
|
|
// As usual with cancelable contexts, the caller must always call the given
|
|
|
|
// cancel function once all operations are complete in order to make sure
|
|
|
|
// that the context resources will still be freed even if there is no
|
|
|
|
// interruption.
|
|
|
|
func (m *Meta) InterruptibleContext() (context.Context, context.CancelFunc) {
|
|
|
|
base := context.Background()
|
|
|
|
if m.ShutdownCh == nil {
|
|
|
|
// If we're running in a unit testing context without a shutdown
|
|
|
|
// channel populated then we'll return an uncancelable channel.
|
|
|
|
return base, func() {}
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(base)
|
|
|
|
go func() {
|
|
|
|
select {
|
|
|
|
case <-m.ShutdownCh:
|
|
|
|
cancel()
|
|
|
|
case <-ctx.Done():
|
|
|
|
// finished without being interrupted
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return ctx, cancel
|
|
|
|
}
|
|
|
|
|
2018-03-27 17:31:05 -05:00
|
|
|
// RunOperation executes the given operation on the given backend, blocking
|
2019-07-15 08:32:35 -05:00
|
|
|
// until that operation completes or is interrupted, and then returns
|
2018-03-27 17:31:05 -05:00
|
|
|
// the RunningOperation object representing the completed or
|
|
|
|
// aborted operation that is, despite the name, no longer running.
|
|
|
|
//
|
|
|
|
// An error is returned if the operation either fails to start or is cancelled.
|
|
|
|
// If the operation runs to completion then no error is returned even if the
|
|
|
|
// operation itself is unsuccessful. Use the "Result" field of the
|
|
|
|
// returned operation object to recognize operation-level failure.
|
2018-02-09 17:51:29 -06:00
|
|
|
func (m *Meta) RunOperation(b backend.Enhanced, opReq *backend.Operation) (*backend.RunningOperation, error) {
|
2021-02-18 16:23:34 -06:00
|
|
|
if opReq.View == nil {
|
|
|
|
panic("RunOperation called with nil View")
|
|
|
|
}
|
2018-12-19 14:12:24 -06:00
|
|
|
if opReq.ConfigDir != "" {
|
|
|
|
opReq.ConfigDir = m.normalizePath(opReq.ConfigDir)
|
|
|
|
}
|
|
|
|
|
2018-02-09 17:51:29 -06:00
|
|
|
op, err := b.Operation(context.Background(), opReq)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("error starting operation: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the operation to complete or an interrupt to occur
|
|
|
|
select {
|
|
|
|
case <-m.ShutdownCh:
|
|
|
|
// gracefully stop the operation
|
|
|
|
op.Stop()
|
|
|
|
|
|
|
|
// Notify the user
|
2021-02-18 16:23:34 -06:00
|
|
|
opReq.View.Interrupted()
|
2018-02-09 17:51:29 -06:00
|
|
|
|
|
|
|
// Still get the result, since there is still one
|
|
|
|
select {
|
|
|
|
case <-m.ShutdownCh:
|
2021-02-18 16:23:34 -06:00
|
|
|
opReq.View.FatalInterrupt()
|
2018-02-09 17:51:29 -06:00
|
|
|
|
|
|
|
// cancel the operation completely
|
|
|
|
op.Cancel()
|
|
|
|
|
|
|
|
// the operation should return asap
|
|
|
|
// but timeout just in case
|
|
|
|
select {
|
|
|
|
case <-op.Done():
|
|
|
|
case <-time.After(5 * time.Second):
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errors.New("operation canceled")
|
|
|
|
|
|
|
|
case <-op.Done():
|
|
|
|
// operation completed after Stop
|
|
|
|
}
|
|
|
|
case <-op.Done():
|
|
|
|
// operation completed normally
|
|
|
|
}
|
|
|
|
|
|
|
|
return op, nil
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:37:30 -05:00
|
|
|
// contextOpts returns the options to use to initialize a Terraform
|
|
|
|
// context with the settings from this Meta.
|
2020-06-16 11:23:15 -05:00
|
|
|
func (m *Meta) contextOpts() (*terraform.ContextOpts, error) {
|
|
|
|
workspace, err := m.Workspace()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
var opts terraform.ContextOpts
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2014-10-01 00:01:11 -05:00
|
|
|
opts.UIInput = m.UIInput()
|
2017-01-18 22:50:45 -06:00
|
|
|
opts.Parallelism = m.parallelism
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2017-04-13 20:05:58 -05:00
|
|
|
// If testingOverrides are set, we'll skip the plugin discovery process
|
|
|
|
// and just work with what we've been given, thus allowing the tests
|
|
|
|
// to provide mock providers and provisioners.
|
|
|
|
if m.testingOverrides != nil {
|
2020-03-30 17:30:56 -05:00
|
|
|
opts.Providers = m.testingOverrides.Providers
|
2017-04-13 20:05:58 -05:00
|
|
|
opts.Provisioners = m.testingOverrides.Provisioners
|
|
|
|
} else {
|
2021-10-21 07:44:26 -05:00
|
|
|
var providerFactories map[addrs.Provider]providers.Factory
|
|
|
|
providerFactories, err = m.providerFactories()
|
2020-03-30 17:30:56 -05:00
|
|
|
opts.Providers = providerFactories
|
2017-04-13 20:05:58 -05:00
|
|
|
opts.Provisioners = m.provisionerFactories()
|
|
|
|
}
|
|
|
|
|
2017-03-13 18:25:27 -05:00
|
|
|
opts.Meta = &terraform.ContextMeta{
|
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
|
|
|
Env: workspace,
|
2021-09-01 19:01:44 -05:00
|
|
|
OriginalWorkingDir: m.WorkingDir.OriginalWorkingDir(),
|
2017-03-13 18:25:27 -05:00
|
|
|
}
|
|
|
|
|
2021-10-21 07:44:26 -05:00
|
|
|
return &opts, err
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
|
|
|
|
2018-11-21 08:35:27 -06:00
|
|
|
// defaultFlagSet creates a default flag set for commands.
|
cli: Add initial command views abstraction
Terraform supports multiple output formats for several sub-commands.
The default format is user-readable text, but many sub-commands support
a `-json` flag to output a machine-readable format for the result. The
output command also supports a `-raw` flag for a simpler, scripting-
focused machine readable format.
This commit adds a "views" abstraction, intended to help ensure
consistency between the various output formats. This extracts the render
specific code from the command package, and moves it into a views
package. Each command is expected to create an interface for its view,
and one or more implementations of that interface.
By doing so, we separate the concerns of generating the sub-command
result from rendering the result in the specified output format. This
should make it easier to ensure that all output formats will be updated
together when changes occur in the result-generating phase.
There are some other consequences of this restructuring:
- Views now directly access the terminal streams, rather than the
now-redundant cli.Ui instance;
- With the reorganization of commands, parsing CLI arguments is now the
responsibility of a separate "arguments" package.
For now, views are added only for the output sub-command, as an example.
Because this command uses code which is shared with the apply and
refresh commands, those are also partially updated.
2021-01-27 14:51:40 -06:00
|
|
|
// See also command/arguments/default.go
|
2018-11-21 08:35:27 -06:00
|
|
|
func (m *Meta) defaultFlagSet(n string) *flag.FlagSet {
|
2014-07-18 13:37:27 -05:00
|
|
|
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
2019-08-16 07:31:21 -05:00
|
|
|
f.SetOutput(ioutil.Discard)
|
2014-09-08 22:56:18 -05:00
|
|
|
|
2016-03-22 12:41:02 -05:00
|
|
|
// Set the default Usage to empty
|
|
|
|
f.Usage = func() {}
|
|
|
|
|
2018-11-21 08:35:27 -06:00
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
backend: Validate remote backend Terraform version
When using the enhanced remote backend, a subset of all Terraform
operations are supported. Of these, only plan and apply can be executed
on the remote infrastructure (e.g. Terraform Cloud). Other operations
run locally and use the remote backend for state storage.
This causes problems when the local version of Terraform does not match
the configured version from the remote workspace. If the two versions
are incompatible, an `import` or `state mv` operation can cause the
remote workspace to be unusable until a manual fix is applied.
To prevent this from happening accidentally, this commit introduces a
check that the local Terraform version and the configured remote
workspace Terraform version are compatible. This check is skipped for
commands which do not write state, and can also be disabled by the use
of a new command-line flag, `-ignore-remote-version`.
Terraform version compatibility is defined as:
- For all releases before 0.14.0, local must exactly equal remote, as
two different versions cannot share state;
- 0.14.0 to 1.0.x are compatible, as we will not change the state
version number until at least Terraform 1.1.0;
- Versions after 1.1.0 must have the same major and minor versions, as
we will not change the state version number in a patch release.
If the two versions are incompatible, a diagnostic is displayed,
advising that the error can be suppressed with `-ignore-remote-version`.
When this flag is used, the diagnostic is still displayed, but as a
warning instead of an error.
Commands which will not write state can assert this fact by calling the
helper `meta.ignoreRemoteBackendVersionConflict`, which will disable the
checks. Those which can write state should instead call the helper
`meta.remoteBackendVersionCheck`, which will return diagnostics for
display.
In addition to these explicit paths for managing the version check, we
have an implicit check in the remote backend's state manager
initialization method. Both of the above helpers will disable this
check. This fallback is in place to ensure that future code paths which
access state cannot accidentally skip the remote version check.
2020-11-13 15:43:56 -06:00
|
|
|
// ignoreRemoteVersionFlagSet add the ignore-remote version flag to suppress
|
|
|
|
// the error when the configured Terraform version on the remote workspace
|
|
|
|
// does not match the local Terraform version.
|
|
|
|
func (m *Meta) ignoreRemoteVersionFlagSet(n string) *flag.FlagSet {
|
|
|
|
f := m.defaultFlagSet(n)
|
|
|
|
|
2021-01-27 12:58:41 -06:00
|
|
|
f.BoolVar(&m.ignoreRemoteVersion, "ignore-remote-version", false, "continue even if remote and local Terraform versions are incompatible")
|
backend: Validate remote backend Terraform version
When using the enhanced remote backend, a subset of all Terraform
operations are supported. Of these, only plan and apply can be executed
on the remote infrastructure (e.g. Terraform Cloud). Other operations
run locally and use the remote backend for state storage.
This causes problems when the local version of Terraform does not match
the configured version from the remote workspace. If the two versions
are incompatible, an `import` or `state mv` operation can cause the
remote workspace to be unusable until a manual fix is applied.
To prevent this from happening accidentally, this commit introduces a
check that the local Terraform version and the configured remote
workspace Terraform version are compatible. This check is skipped for
commands which do not write state, and can also be disabled by the use
of a new command-line flag, `-ignore-remote-version`.
Terraform version compatibility is defined as:
- For all releases before 0.14.0, local must exactly equal remote, as
two different versions cannot share state;
- 0.14.0 to 1.0.x are compatible, as we will not change the state
version number until at least Terraform 1.1.0;
- Versions after 1.1.0 must have the same major and minor versions, as
we will not change the state version number in a patch release.
If the two versions are incompatible, a diagnostic is displayed,
advising that the error can be suppressed with `-ignore-remote-version`.
When this flag is used, the diagnostic is still displayed, but as a
warning instead of an error.
Commands which will not write state can assert this fact by calling the
helper `meta.ignoreRemoteBackendVersionConflict`, which will disable the
checks. Those which can write state should instead call the helper
`meta.remoteBackendVersionCheck`, which will return diagnostics for
display.
In addition to these explicit paths for managing the version check, we
have an implicit check in the remote backend's state manager
initialization method. Both of the above helpers will disable this
check. This fallback is in place to ensure that future code paths which
access state cannot accidentally skip the remote version check.
2020-11-13 15:43:56 -06:00
|
|
|
|
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2018-11-21 08:35:27 -06:00
|
|
|
// extendedFlagSet adds custom flags that are mostly used by commands
|
|
|
|
// that are used to run an operation like plan or apply.
|
|
|
|
func (m *Meta) extendedFlagSet(n string) *flag.FlagSet {
|
|
|
|
f := m.defaultFlagSet(n)
|
|
|
|
|
|
|
|
f.BoolVar(&m.input, "input", true, "input")
|
2021-02-08 12:29:42 -06:00
|
|
|
f.Var((*FlagStringSlice)(&m.targetFlags), "target", "resource to target")
|
2019-12-10 13:06:06 -06:00
|
|
|
f.BoolVar(&m.compactWarnings, "compact-warnings", false, "use compact warnings")
|
2018-11-21 08:35:27 -06:00
|
|
|
|
|
|
|
if m.variableArgs.items == nil {
|
|
|
|
m.variableArgs = newRawFlags("-var")
|
|
|
|
}
|
|
|
|
varValues := m.variableArgs.Alias("-var")
|
|
|
|
varFiles := m.variableArgs.Alias("-var-file")
|
|
|
|
f.Var(varValues, "var", "variables")
|
|
|
|
f.Var(varFiles, "var-file", "variable file")
|
|
|
|
|
|
|
|
// commands that bypass locking will supply their own flag on this var,
|
|
|
|
// but set the initial meta value to true as a failsafe.
|
2017-03-30 12:54:27 -05:00
|
|
|
m.stateLock = true
|
|
|
|
|
2014-07-18 13:37:27 -05:00
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
cli: Add initial command views abstraction
Terraform supports multiple output formats for several sub-commands.
The default format is user-readable text, but many sub-commands support
a `-json` flag to output a machine-readable format for the result. The
output command also supports a `-raw` flag for a simpler, scripting-
focused machine readable format.
This commit adds a "views" abstraction, intended to help ensure
consistency between the various output formats. This extracts the render
specific code from the command package, and moves it into a views
package. Each command is expected to create an interface for its view,
and one or more implementations of that interface.
By doing so, we separate the concerns of generating the sub-command
result from rendering the result in the specified output format. This
should make it easier to ensure that all output formats will be updated
together when changes occur in the result-generating phase.
There are some other consequences of this restructuring:
- Views now directly access the terminal streams, rather than the
now-redundant cli.Ui instance;
- With the reorganization of commands, parsing CLI arguments is now the
responsibility of a separate "arguments" package.
For now, views are added only for the output sub-command, as an example.
Because this command uses code which is shared with the apply and
refresh commands, those are also partially updated.
2021-01-27 14:51:40 -06:00
|
|
|
// process will process any -no-color entries out of the arguments. This
|
2014-07-12 22:21:46 -05:00
|
|
|
// will potentially modify the args in-place. It will return the resulting
|
cli: Add initial command views abstraction
Terraform supports multiple output formats for several sub-commands.
The default format is user-readable text, but many sub-commands support
a `-json` flag to output a machine-readable format for the result. The
output command also supports a `-raw` flag for a simpler, scripting-
focused machine readable format.
This commit adds a "views" abstraction, intended to help ensure
consistency between the various output formats. This extracts the render
specific code from the command package, and moves it into a views
package. Each command is expected to create an interface for its view,
and one or more implementations of that interface.
By doing so, we separate the concerns of generating the sub-command
result from rendering the result in the specified output format. This
should make it easier to ensure that all output formats will be updated
together when changes occur in the result-generating phase.
There are some other consequences of this restructuring:
- Views now directly access the terminal streams, rather than the
now-redundant cli.Ui instance;
- With the reorganization of commands, parsing CLI arguments is now the
responsibility of a separate "arguments" package.
For now, views are added only for the output sub-command, as an example.
Because this command uses code which is shared with the apply and
refresh commands, those are also partially updated.
2021-01-27 14:51:40 -06:00
|
|
|
// slice, and update the Meta and Ui.
|
2020-04-01 14:01:08 -05:00
|
|
|
func (m *Meta) process(args []string) []string {
|
2014-07-12 22:59:16 -05:00
|
|
|
// We do this so that we retain the ability to technically call
|
|
|
|
// process multiple times, even if we have no plans to do so
|
|
|
|
if m.oldUi != nil {
|
|
|
|
m.Ui = m.oldUi
|
|
|
|
}
|
2014-07-12 22:21:46 -05:00
|
|
|
|
2014-07-12 22:59:16 -05:00
|
|
|
// Set colorization
|
2014-07-17 11:34:32 -05:00
|
|
|
m.color = m.Color
|
2020-08-13 13:10:09 -05:00
|
|
|
i := 0 // output index
|
|
|
|
for _, v := range args {
|
2014-07-12 22:21:46 -05:00
|
|
|
if v == "-no-color" {
|
2014-09-08 22:41:10 -05:00
|
|
|
m.color = false
|
2015-06-21 15:51:40 -05:00
|
|
|
m.Color = false
|
2020-08-13 13:10:09 -05:00
|
|
|
} else {
|
|
|
|
// copy and increment index
|
|
|
|
args[i] = v
|
|
|
|
i++
|
2014-07-12 22:21:46 -05:00
|
|
|
}
|
|
|
|
}
|
2020-08-13 13:10:09 -05:00
|
|
|
args = args[:i]
|
2014-07-12 22:21:46 -05:00
|
|
|
|
2014-07-12 22:59:16 -05:00
|
|
|
// Set the UI
|
|
|
|
m.oldUi = m.Ui
|
2014-08-19 12:22:26 -05:00
|
|
|
m.Ui = &cli.ConcurrentUi{
|
|
|
|
Ui: &ColorizeUi{
|
|
|
|
Colorize: m.Colorize(),
|
|
|
|
ErrorColor: "[red]",
|
2015-03-05 14:22:34 -06:00
|
|
|
WarnColor: "[yellow]",
|
2014-08-19 12:22:26 -05:00
|
|
|
Ui: m.oldUi,
|
|
|
|
},
|
2014-07-12 22:59:16 -05:00
|
|
|
}
|
|
|
|
|
2021-02-11 19:52:10 -06:00
|
|
|
// Reconfigure the view. This is necessary for commands which use both
|
|
|
|
// views.View and cli.Ui during the migration phase.
|
|
|
|
if m.View != nil {
|
|
|
|
m.View.Configure(&arguments.View{
|
|
|
|
CompactWarnings: m.compactWarnings,
|
|
|
|
NoColor: !m.Color,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-04-01 14:01:08 -05:00
|
|
|
return args
|
2014-07-12 22:21:46 -05:00
|
|
|
}
|
2014-07-12 22:37:30 -05:00
|
|
|
|
|
|
|
// uiHook returns the UiHook to use with the context.
|
2023-05-10 04:00:45 -05:00
|
|
|
func (m *Meta) uiHook() *views.UiHook {
|
|
|
|
return views.NewUiHook(m.View)
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
2014-09-22 12:56:50 -05:00
|
|
|
|
2017-01-18 22:50:45 -06:00
|
|
|
// confirm asks a yes/no confirmation.
|
|
|
|
func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
|
2017-06-23 18:11:30 -05:00
|
|
|
if !m.Input() {
|
|
|
|
return false, errors.New("input is disabled")
|
2017-03-29 15:45:25 -05:00
|
|
|
}
|
2017-12-18 10:38:51 -06:00
|
|
|
|
|
|
|
for i := 0; i < 2; i++ {
|
2019-03-07 14:07:13 -06:00
|
|
|
v, err := m.UIInput().Input(context.Background(), opts)
|
2017-01-18 22:50:45 -06:00
|
|
|
if err != nil {
|
|
|
|
return false, fmt.Errorf(
|
|
|
|
"Error asking for confirmation: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch strings.ToLower(v) {
|
|
|
|
case "no":
|
|
|
|
return false, nil
|
|
|
|
case "yes":
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
}
|
2017-12-18 10:38:51 -06:00
|
|
|
return false, nil
|
2017-01-18 22:50:45 -06:00
|
|
|
}
|
|
|
|
|
2017-10-05 13:59:08 -05:00
|
|
|
// showDiagnostics displays error and warning messages in the UI.
|
|
|
|
//
|
|
|
|
// "Diagnostics" here means the Diagnostics type from the tfdiag package,
|
|
|
|
// though as a convenience this function accepts anything that could be
|
|
|
|
// passed to the "Append" method on that type, converting it to Diagnostics
|
|
|
|
// before displaying it.
|
|
|
|
//
|
|
|
|
// Internally this function uses Diagnostics.Append, and so it will panic
|
|
|
|
// if given unsupported value types, just as Append does.
|
|
|
|
func (m *Meta) showDiagnostics(vals ...interface{}) {
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
diags = diags.Append(vals...)
|
2018-06-20 20:57:23 -05:00
|
|
|
diags.Sort()
|
2017-10-05 13:59:08 -05:00
|
|
|
|
2019-12-10 13:06:06 -06:00
|
|
|
if len(diags) == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-01-11 20:29:39 -06:00
|
|
|
outputWidth := m.ErrorColumns()
|
|
|
|
|
2019-12-10 13:06:06 -06:00
|
|
|
diags = diags.ConsolidateWarnings(1)
|
|
|
|
|
2019-11-19 15:57:35 -06:00
|
|
|
// Since warning messages are generally competing
|
2019-12-10 13:06:06 -06:00
|
|
|
if m.compactWarnings {
|
|
|
|
// If the user selected compact warnings and all of the diagnostics are
|
|
|
|
// warnings then we'll use a more compact representation of the warnings
|
|
|
|
// that only includes their summaries.
|
|
|
|
// We show full warnings if there are also errors, because a warning
|
|
|
|
// can sometimes serve as good context for a subsequent error.
|
|
|
|
useCompact := true
|
|
|
|
for _, diag := range diags {
|
|
|
|
if diag.Severity() != tfdiags.Warning {
|
|
|
|
useCompact = false
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if useCompact {
|
|
|
|
msg := format.DiagnosticWarningsCompact(diags, m.Colorize())
|
|
|
|
msg = "\n" + msg + "\nTo see the full warning notes, run Terraform without -compact-warnings.\n"
|
|
|
|
m.Ui.Warn(msg)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2019-11-19 15:57:35 -06:00
|
|
|
|
2017-10-05 13:59:08 -05:00
|
|
|
for _, diag := range diags {
|
2021-01-26 12:13:32 -06:00
|
|
|
var msg string
|
|
|
|
if m.Color {
|
|
|
|
msg = format.Diagnostic(diag, m.configSources(), m.Colorize(), outputWidth)
|
|
|
|
} else {
|
|
|
|
msg = format.DiagnosticPlain(diag, m.configSources(), outputWidth)
|
|
|
|
}
|
|
|
|
|
2017-10-05 13:59:08 -05:00
|
|
|
switch diag.Severity() {
|
|
|
|
case tfdiags.Error:
|
2021-01-26 12:13:32 -06:00
|
|
|
m.Ui.Error(msg)
|
2017-10-05 13:59:08 -05:00
|
|
|
case tfdiags.Warning:
|
2021-01-26 12:13:32 -06:00
|
|
|
m.Ui.Warn(msg)
|
2017-10-05 13:59:08 -05:00
|
|
|
default:
|
2021-01-26 12:13:32 -06:00
|
|
|
m.Ui.Output(msg)
|
2017-10-05 13:59:08 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-30 19:13:43 -05:00
|
|
|
// WorkspaceNameEnvVar is the name of the environment variable that can be used
|
|
|
|
// to set the name of the Terraform workspace, overriding the workspace chosen
|
|
|
|
// by `terraform workspace select`.
|
|
|
|
//
|
|
|
|
// Note that this environment variable is ignored by `terraform workspace new`
|
|
|
|
// and `terraform workspace delete`.
|
|
|
|
const WorkspaceNameEnvVar = "TF_WORKSPACE"
|
2017-05-12 15:53:29 -05:00
|
|
|
|
2020-12-01 11:34:50 -06:00
|
|
|
var errInvalidWorkspaceNameEnvVar = fmt.Errorf("Invalid workspace name set using %s", WorkspaceNameEnvVar)
|
2020-06-16 11:23:15 -05:00
|
|
|
|
2017-05-30 19:13:43 -05:00
|
|
|
// Workspace returns the name of the currently configured workspace, corresponding
|
2017-02-28 12:13:03 -06:00
|
|
|
// to the desired named state.
|
2020-06-16 11:23:15 -05:00
|
|
|
func (m *Meta) Workspace() (string, error) {
|
|
|
|
current, overridden := m.WorkspaceOverridden()
|
|
|
|
if overridden && !validWorkspaceName(current) {
|
2020-12-01 11:34:50 -06:00
|
|
|
return "", errInvalidWorkspaceNameEnvVar
|
2020-06-16 11:23:15 -05:00
|
|
|
}
|
|
|
|
return current, nil
|
2017-05-12 15:53:29 -05:00
|
|
|
}
|
|
|
|
|
2017-05-30 19:13:43 -05:00
|
|
|
// WorkspaceOverridden returns the name of the currently configured workspace,
|
2017-05-12 15:53:29 -05:00
|
|
|
// corresponding to the desired named state, as well as a bool saying whether
|
2017-05-30 17:06:13 -05:00
|
|
|
// this was set via the TF_WORKSPACE environment variable.
|
2017-05-30 19:13:43 -05:00
|
|
|
func (m *Meta) WorkspaceOverridden() (string, bool) {
|
|
|
|
if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
|
2017-05-12 15:53:29 -05:00
|
|
|
return envVar, true
|
|
|
|
}
|
|
|
|
|
2017-06-14 14:14:26 -05:00
|
|
|
envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile))
|
2017-02-28 12:13:03 -06:00
|
|
|
current := string(bytes.TrimSpace(envData))
|
|
|
|
if current == "" {
|
|
|
|
current = backend.DefaultStateName
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil && !os.IsNotExist(err) {
|
2017-05-30 17:06:13 -05:00
|
|
|
// always return the default if we can't get a workspace name
|
|
|
|
log.Printf("[ERROR] failed to read current workspace: %s", err)
|
2017-02-28 12:13:03 -06:00
|
|
|
}
|
|
|
|
|
2017-05-12 15:53:29 -05:00
|
|
|
return current, false
|
2017-02-28 12:13:03 -06:00
|
|
|
}
|
|
|
|
|
2017-05-30 19:13:43 -05:00
|
|
|
// SetWorkspace saves the given name as the current workspace in the local
|
2017-05-30 17:06:13 -05:00
|
|
|
// filesystem.
|
2017-05-30 19:13:43 -05:00
|
|
|
func (m *Meta) SetWorkspace(name string) error {
|
2017-06-14 14:14:26 -05:00
|
|
|
err := os.MkdirAll(m.DataDir(), 0755)
|
2017-02-28 12:13:03 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-06-14 14:14:26 -05:00
|
|
|
err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
|
2017-02-28 12:13:03 -06:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2017-03-07 22:09:48 -06:00
|
|
|
|
2017-06-21 20:22:07 -05:00
|
|
|
// isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
|
|
|
|
func isAutoVarFile(path string) bool {
|
|
|
|
return strings.HasSuffix(path, ".auto.tfvars") ||
|
|
|
|
strings.HasSuffix(path, ".auto.tfvars.json")
|
2017-03-07 22:09:48 -06:00
|
|
|
}
|
2021-02-18 16:23:34 -06:00
|
|
|
|
|
|
|
// FIXME: as an interim refactoring step, we apply the contents of the state
|
|
|
|
// arguments directly to the Meta object. Future work would ideally update the
|
|
|
|
// code paths which use these arguments to be passed them directly for clarity.
|
|
|
|
func (m *Meta) applyStateArguments(args *arguments.State) {
|
|
|
|
m.stateLock = args.Lock
|
|
|
|
m.stateLockTimeout = args.LockTimeout
|
|
|
|
m.statePath = args.StatePath
|
|
|
|
m.stateOutPath = args.StateOutPath
|
|
|
|
m.backupPath = args.BackupPath
|
|
|
|
}
|
2022-03-31 12:42:42 -05:00
|
|
|
|
|
|
|
// checkRequiredVersion loads the config and check if the
|
|
|
|
// core version requirements are satisfied.
|
|
|
|
func (m *Meta) checkRequiredVersion() tfdiags.Diagnostics {
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
|
|
|
loader, err := m.initConfigLoader()
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(err)
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
pwd, err := os.Getwd()
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(fmt.Errorf("Error getting pwd: %s", err))
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
config, configDiags := loader.LoadConfig(pwd)
|
|
|
|
if configDiags.HasErrors() {
|
|
|
|
diags = diags.Append(configDiags)
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
versionDiags := terraform.CheckCoreVersionRequirements(config)
|
|
|
|
if versionDiags.HasErrors() {
|
|
|
|
diags = diags.Append(versionDiags)
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2022-08-29 11:10:03 -05:00
|
|
|
|
2022-08-30 17:01:44 -05:00
|
|
|
// MaybeGetSchemas attempts to load and return the schemas
|
|
|
|
// If there is not enough information to return the schemas,
|
|
|
|
// it could potentially return nil without errors. It is the
|
|
|
|
// responsibility of the caller to handle the lack of schema
|
|
|
|
// information accordingly
|
|
|
|
func (c *Meta) MaybeGetSchemas(state *states.State, config *configs.Config) (*terraform.Schemas, tfdiags.Diagnostics) {
|
2022-08-29 11:10:03 -05:00
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
|
|
|
path, err := os.Getwd()
|
|
|
|
if err != nil {
|
2022-08-30 17:52:51 -05:00
|
|
|
diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
|
2022-08-29 11:10:03 -05:00
|
|
|
return nil, diags
|
|
|
|
}
|
|
|
|
|
2022-08-29 11:32:14 -05:00
|
|
|
if config == nil {
|
|
|
|
config, diags = c.loadConfig(path)
|
|
|
|
if diags.HasErrors() {
|
2022-08-30 17:52:51 -05:00
|
|
|
diags.Append(tfdiags.SimpleWarning(failedToLoadSchemasMessage))
|
2022-08-29 11:32:14 -05:00
|
|
|
return nil, diags
|
|
|
|
}
|
2022-08-29 11:10:03 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if config != nil || state != nil {
|
|
|
|
opts, err := c.contextOpts()
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(err)
|
|
|
|
return nil, diags
|
|
|
|
}
|
|
|
|
tfCtx, ctxDiags := terraform.NewContext(opts)
|
|
|
|
diags = diags.Append(ctxDiags)
|
|
|
|
if ctxDiags.HasErrors() {
|
|
|
|
return nil, diags
|
|
|
|
}
|
|
|
|
var schemaDiags tfdiags.Diagnostics
|
|
|
|
schemas, schemaDiags := tfCtx.Schemas(config, state)
|
|
|
|
diags = diags.Append(schemaDiags)
|
|
|
|
if schemaDiags.HasErrors() {
|
|
|
|
return nil, diags
|
|
|
|
}
|
|
|
|
return schemas, diags
|
|
|
|
|
|
|
|
}
|
|
|
|
return nil, diags
|
|
|
|
}
|