terraform/command/taint.go

266 lines
7.8 KiB
Go
Raw Normal View History

2015-02-26 19:29:23 +01:00
package command
import (
"fmt"
"os"
2015-02-26 19:29:23 +01:00
"strings"
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 19:33:53 +02:00
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/command/arguments"
"github.com/hashicorp/terraform/command/clistate"
"github.com/hashicorp/terraform/command/views"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
2015-02-26 19:29:23 +01:00
)
2015-02-26 23:30:02 +01:00
// TaintCommand is a cli.Command implementation that manually taints
// a resource, marking it for recreation.
2015-02-26 19:29:23 +01:00
type TaintCommand struct {
Meta
}
func (c *TaintCommand) Run(args []string) int {
args = c.Meta.process(args)
var allowMissing bool
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 22:43:56 +01:00
cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("taint")
cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "allow missing")
2015-02-26 19:29:23 +01:00
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
2017-02-06 16:07:32 +01:00
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
2015-02-26 19:29:23 +01:00
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
if err := cmdFlags.Parse(args); err != nil {
c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
2015-02-26 19:29:23 +01:00
return 1
}
var diags tfdiags.Diagnostics
2015-02-26 19:29:23 +01:00
// Require the one argument for the resource to taint
args = cmdFlags.Args()
if len(args) != 1 {
c.Ui.Error("The taint command expects exactly one argument.")
cmdFlags.Usage()
return 1
}
2015-02-26 19:56:45 +01:00
addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
diags = diags.Append(addrDiags)
if addrDiags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr))
return 1
}
// Load the config and check the core version requirements are satisfied
loader, err := c.initConfigLoader()
if err != nil {
diags = diags.Append(err)
c.showDiagnostics(diags)
return 1
}
pwd, err := os.Getwd()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error getting pwd: %s", err))
return 1
}
config, configDiags := loader.LoadConfig(pwd)
diags = diags.Append(configDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
versionDiags := terraform.CheckCoreVersionRequirements(config)
diags = diags.Append(versionDiags)
if diags.HasErrors() {
c.showDiagnostics(diags)
return 1
}
2017-01-19 05:50:45 +01:00
// Load the backend
b, backendDiags := c.Backend(nil)
diags = diags.Append(backendDiags)
if backendDiags.HasErrors() {
c.showDiagnostics(diags)
2017-01-19 05:50:45 +01:00
return 1
}
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 22:43:56 +01:00
// Determine the workspace name
workspace, err := c.Workspace()
if err != nil {
c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
return 1
}
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 22:43:56 +01:00
// Check remote Terraform version is compatible
remoteVersionDiags := c.remoteBackendVersionCheck(b, workspace)
diags = diags.Append(remoteVersionDiags)
c.showDiagnostics(diags)
if diags.HasErrors() {
return 1
}
// Get the state
stateMgr, err := b.StateMgr(workspace)
2015-02-26 19:29:23 +01:00
if err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
if c.stateLock {
stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
if diags := stateLocker.Lock(stateMgr, "taint"); diags.HasErrors() {
c.showDiagnostics(diags)
2017-02-03 20:23:24 +01:00
return 1
}
defer func() {
if diags := stateLocker.Unlock(); diags.HasErrors() {
c.showDiagnostics(diags)
}
}()
2017-02-03 20:23:24 +01:00
}
if err := stateMgr.RefreshState(); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
return 1
}
2015-02-26 19:29:23 +01:00
// Get the actual state structure
state := stateMgr.State()
if state.Empty() {
2015-02-26 19:56:45 +01:00
if allowMissing {
return c.allowMissingExit(addr)
2015-02-26 19:56:45 +01:00
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
"The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
))
c.showDiagnostics(diags)
2015-02-26 19:29:23 +01:00
return 1
}
ss := state.SyncWrapper()
2015-02-26 19:56:45 +01:00
// Get the resource and instance we're going to taint
rs := ss.Resource(addr.ContainingResource())
is := ss.ResourceInstance(addr)
if is == nil {
2015-02-26 19:56:45 +01:00
if allowMissing {
return c.allowMissingExit(addr)
2015-02-26 19:56:45 +01:00
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
))
c.showDiagnostics(diags)
2015-02-26 19:29:23 +01:00
return 1
}
obj := is.Current
if obj == nil {
if len(is.Deposed) != 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
))
} else {
// Don't know why we're here, but we'll produce a generic error message anyway.
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"No such resource instance",
fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
))
2015-02-26 19:56:45 +01:00
}
c.showDiagnostics(diags)
2015-02-26 19:29:23 +01:00
return 1
}
obj.Status = states.ObjectTainted
ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
2015-02-26 19:29:23 +01:00
if err := stateMgr.WriteState(state); err != nil {
2017-01-19 05:50:45 +01:00
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}
if err := stateMgr.PersistState(); err != nil {
2015-02-26 19:29:23 +01:00
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
return 1
}
c.Ui.Output(fmt.Sprintf("Resource instance %s has been marked as tainted.", addr))
2015-02-26 19:29:23 +01:00
return 0
}
func (c *TaintCommand) Help() string {
helpText := `
Usage: terraform [global options] taint [options] <address>
2015-02-26 19:29:23 +01:00
Terraform uses the term "tainted" to describe a resource instance
which may not be fully functional, either because its creation
partially failed or because you've manually marked it as such using
this command.
2015-02-26 19:29:23 +01:00
This will not modify your infrastructure directly, but subsequent
Terraform plans will include actions to destroy the remote object
and create a new object to replace it.
You can remove the "taint" state from a resource instance using
the "terraform untaint" command.
The address is in the usual resource address syntax, such as:
aws_instance.foo
aws_instance.bar[1]
module.foo.module.bar.aws_instance.baz
2015-02-26 19:29:23 +01:00
Use your shell's quoting or escaping syntax to ensure that the
address will reach Terraform correctly, without any special
interpretation.
2015-02-26 19:29:23 +01:00
Options:
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 22:43:56 +01:00
-allow-missing If specified, the command will succeed (exit code 0)
even if the resource is missing.
-lock=false Don't hold a state lock during the operation. This is
dangerous if others might concurrently run commands
against the same workspace.
2015-02-26 19:29:23 +01: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 22:43:56 +01:00
-lock-timeout=0s Duration to retry a state lock.
2017-02-03 20:23:24 +01:00
-ignore-remote-version A rare option used for the remote backend only. See
the remote backend documentation for more information.
2015-02-26 19:29:23 +01:00
-state, state-out, and -backup are legacy options supported for the local
backend only. For more information, see the local backend's documentation.
2015-02-26 19:29:23 +01:00
`
return strings.TrimSpace(helpText)
}
func (c *TaintCommand) Synopsis() string {
return "Mark a resource instance as not fully functional"
2015-02-26 19:29:23 +01:00
}
2015-02-26 19:56:45 +01:00
func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
c.showDiagnostics(tfdiags.Sourceless(
tfdiags.Warning,
"No such resource instance",
fmt.Sprintf("Resource instance %s was not found, but this is not an error because -allow-missing was set.", name),
))
2015-02-26 19:56:45 +01:00
return 0
}