2016-03-08 21:37:34 +01:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2017-04-01 21:42:13 +02:00
|
|
|
"context"
|
2016-03-08 21:37:34 +01:00
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
"strings"
|
2017-02-03 20:23:24 +01:00
|
|
|
|
2017-04-01 20:58:19 +02:00
|
|
|
"github.com/hashicorp/terraform/command/clistate"
|
2017-02-15 00:51:32 +01:00
|
|
|
"github.com/hashicorp/terraform/state"
|
2016-03-08 21:37:34 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// UntaintCommand is a cli.Command implementation that manually untaints
|
|
|
|
// a resource, marking it as primary and ready for service.
|
|
|
|
type UntaintCommand struct {
|
|
|
|
Meta
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *UntaintCommand) Run(args []string) int {
|
|
|
|
args = c.Meta.process(args, false)
|
|
|
|
|
|
|
|
var allowMissing bool
|
|
|
|
var module string
|
|
|
|
cmdFlags := c.Meta.flagSet("untaint")
|
|
|
|
cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module")
|
|
|
|
cmdFlags.StringVar(&module, "module", "", "module")
|
|
|
|
cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
|
|
|
|
cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
|
|
|
|
cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
|
2017-02-06 16:07:32 +01:00
|
|
|
cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
|
2017-04-01 22:19:59 +02:00
|
|
|
cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
|
2016-03-08 21:37:34 +01:00
|
|
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
|
|
if err := cmdFlags.Parse(args); err != nil {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Require the one argument for the resource to untaint
|
|
|
|
args = cmdFlags.Args()
|
|
|
|
if len(args) != 1 {
|
|
|
|
c.Ui.Error("The untaint command expects exactly one argument.")
|
|
|
|
cmdFlags.Usage()
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
name := args[0]
|
|
|
|
if module == "" {
|
|
|
|
module = "root"
|
|
|
|
} else {
|
|
|
|
module = "root." + module
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:50:45 +01:00
|
|
|
// Load the backend
|
|
|
|
b, err := c.Backend(nil)
|
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the state
|
2017-02-28 19:13:03 +01:00
|
|
|
env := c.Env()
|
|
|
|
st, err := b.State(env)
|
2016-03-08 21:37:34 +01:00
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
2017-02-22 05:35:43 +01:00
|
|
|
if err := st.RefreshState(); err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
2016-03-08 21:37:34 +01:00
|
|
|
|
2017-04-01 21:42:13 +02:00
|
|
|
if c.stateLock {
|
|
|
|
lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
|
|
|
|
defer cancel()
|
|
|
|
|
2017-02-15 16:25:04 +01:00
|
|
|
lockInfo := state.NewLockInfo()
|
|
|
|
lockInfo.Operation = "untaint"
|
2017-04-01 21:42:13 +02:00
|
|
|
lockID, err := clistate.Lock(lockCtx, st, lockInfo, c.Ui, c.Colorize())
|
2017-02-14 20:33:55 +01:00
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
2017-02-03 20:23:24 +01:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-02-15 00:51:32 +01:00
|
|
|
defer clistate.Unlock(st, lockID, c.Ui, c.Colorize())
|
2017-02-03 20:23:24 +01:00
|
|
|
}
|
|
|
|
|
2016-03-08 21:37:34 +01:00
|
|
|
// Get the actual state structure
|
2017-02-03 20:23:24 +01:00
|
|
|
s := st.State()
|
2016-03-08 21:37:34 +01:00
|
|
|
if s.Empty() {
|
|
|
|
if allowMissing {
|
|
|
|
return c.allowMissingExit(name, module)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"The state is empty. The most common reason for this is that\n" +
|
|
|
|
"an invalid state file path was given or Terraform has never\n " +
|
|
|
|
"been run for this infrastructure. Infrastructure must exist\n" +
|
|
|
|
"for it to be untainted."))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the proper module holding the resource we want to untaint
|
|
|
|
modPath := strings.Split(module, ".")
|
|
|
|
mod := s.ModuleByPath(modPath)
|
|
|
|
if mod == nil {
|
|
|
|
if allowMissing {
|
|
|
|
return c.allowMissingExit(name, module)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"The module %s could not be found. There is nothing to untaint.",
|
|
|
|
module))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// If there are no resources in this module, it is an error
|
|
|
|
if len(mod.Resources) == 0 {
|
|
|
|
if allowMissing {
|
|
|
|
return c.allowMissingExit(name, module)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"The module %s has no resources. There is nothing to untaint.",
|
|
|
|
module))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the resource we're looking for
|
|
|
|
rs, ok := mod.Resources[name]
|
|
|
|
if !ok {
|
|
|
|
if allowMissing {
|
|
|
|
return c.allowMissingExit(name, module)
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Error(fmt.Sprintf(
|
|
|
|
"The resource %s couldn't be found in the module %s.",
|
|
|
|
name,
|
|
|
|
module))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
// Untaint the resource
|
2016-04-21 21:55:29 +02:00
|
|
|
rs.Untaint()
|
2016-03-08 21:37:34 +01:00
|
|
|
|
|
|
|
log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
|
2017-02-03 20:23:24 +01:00
|
|
|
if err := st.WriteState(s); err != nil {
|
2017-01-19 05:50:45 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
2017-02-03 20:23:24 +01:00
|
|
|
if err := st.PersistState(); err != nil {
|
2016-03-08 21:37:34 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Output(fmt.Sprintf(
|
|
|
|
"The resource %s in the module %s has been successfully untainted!",
|
|
|
|
name, module))
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *UntaintCommand) Help() string {
|
|
|
|
helpText := `
|
|
|
|
Usage: terraform untaint [options] name
|
|
|
|
|
|
|
|
Manually unmark a resource as tainted, restoring it as the primary
|
|
|
|
instance in the state. This reverses either a manual 'terraform taint'
|
|
|
|
or the result of provisioners failing on a resource.
|
|
|
|
|
|
|
|
This will not modify your infrastructure. This command changes your
|
|
|
|
state to unmark a resource as tainted. This command can be undone by
|
|
|
|
reverting the state backup file that is created, or by running
|
|
|
|
'terraform taint' on the resource.
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
|
|
|
-allow-missing If specified, the command will succeed (exit code 0)
|
|
|
|
even if the resource is missing.
|
|
|
|
|
|
|
|
-backup=path Path to backup the existing state file before
|
|
|
|
modifying. Defaults to the "-state-out" path with
|
|
|
|
".backup" extension. Set to "-" to disable backup.
|
|
|
|
|
2017-02-06 16:07:32 +01:00
|
|
|
-lock=true Lock the state file when locking is supported.
|
2017-02-03 20:23:24 +01:00
|
|
|
|
2017-04-01 22:19:59 +02:00
|
|
|
-lock-timeout=0s Duration to retry a state lock.
|
|
|
|
|
2016-03-08 21:37:34 +01:00
|
|
|
-module=path The module path where the resource lives. By
|
|
|
|
default this will be root. Child modules can be specified
|
|
|
|
by names. Ex. "consul" or "consul.vpc" (nested modules).
|
|
|
|
|
|
|
|
-no-color If specified, output won't contain any color.
|
|
|
|
|
|
|
|
-state=path Path to read and save state (unless state-out
|
|
|
|
is specified). Defaults to "terraform.tfstate".
|
|
|
|
|
|
|
|
-state-out=path Path to write updated state file. By default, the
|
|
|
|
"-state" path will be used.
|
|
|
|
|
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *UntaintCommand) Synopsis() string {
|
|
|
|
return "Manually unmark a resource as tainted"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *UntaintCommand) allowMissingExit(name, module string) int {
|
|
|
|
c.Ui.Output(fmt.Sprintf(
|
|
|
|
"The resource %s in the module %s was not found, but\n"+
|
|
|
|
"-allow-missing is set, so we're exiting successfully.",
|
|
|
|
name, module))
|
|
|
|
return 0
|
|
|
|
}
|