github.com/jd3nn1s/terraform@v0.9.6-0.20170906225847-13878347b7a1/command/taint.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"log"
     7  	"strings"
     8  
     9  	"github.com/hashicorp/terraform/command/clistate"
    10  	"github.com/hashicorp/terraform/state"
    11  	"github.com/hashicorp/terraform/terraform"
    12  )
    13  
    14  // TaintCommand is a cli.Command implementation that manually taints
    15  // a resource, marking it for recreation.
    16  type TaintCommand struct {
    17  	Meta
    18  }
    19  
    20  func (c *TaintCommand) Run(args []string) int {
    21  	args, err := c.Meta.process(args, false)
    22  	if err != nil {
    23  		return 1
    24  	}
    25  
    26  	var allowMissing bool
    27  	var module string
    28  	cmdFlags := c.Meta.flagSet("taint")
    29  	cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module")
    30  	cmdFlags.StringVar(&module, "module", "", "module")
    31  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    32  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    33  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    34  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    35  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    36  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    37  	if err := cmdFlags.Parse(args); err != nil {
    38  		return 1
    39  	}
    40  
    41  	// Require the one argument for the resource to taint
    42  	args = cmdFlags.Args()
    43  	if len(args) != 1 {
    44  		c.Ui.Error("The taint command expects exactly one argument.")
    45  		cmdFlags.Usage()
    46  		return 1
    47  	}
    48  
    49  	name := args[0]
    50  	if module == "" {
    51  		module = "root"
    52  	} else {
    53  		module = "root." + module
    54  	}
    55  
    56  	rsk, err := terraform.ParseResourceStateKey(name)
    57  	if err != nil {
    58  		c.Ui.Error(fmt.Sprintf("Failed to parse resource name: %s", err))
    59  		return 1
    60  	}
    61  
    62  	if !rsk.Mode.Taintable() {
    63  		c.Ui.Error(fmt.Sprintf("Resource '%s' cannot be tainted", name))
    64  		return 1
    65  	}
    66  
    67  	// Load the backend
    68  	b, err := c.Backend(nil)
    69  	if err != nil {
    70  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
    71  		return 1
    72  	}
    73  
    74  	// Get the state
    75  	env := c.Workspace()
    76  	st, err := b.State(env)
    77  	if err != nil {
    78  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    79  		return 1
    80  	}
    81  	if err := st.RefreshState(); err != nil {
    82  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    83  		return 1
    84  	}
    85  
    86  	if c.stateLock {
    87  		lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
    88  		defer cancel()
    89  
    90  		lockInfo := state.NewLockInfo()
    91  		lockInfo.Operation = "taint"
    92  		lockID, err := clistate.Lock(lockCtx, st, lockInfo, c.Ui, c.Colorize())
    93  		if err != nil {
    94  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
    95  			return 1
    96  		}
    97  
    98  		defer clistate.Unlock(st, lockID, c.Ui, c.Colorize())
    99  	}
   100  
   101  	// Get the actual state structure
   102  	s := st.State()
   103  	if s.Empty() {
   104  		if allowMissing {
   105  			return c.allowMissingExit(name, module)
   106  		}
   107  
   108  		c.Ui.Error(fmt.Sprintf(
   109  			"The state is empty. The most common reason for this is that\n" +
   110  				"an invalid state file path was given or Terraform has never\n " +
   111  				"been run for this infrastructure. Infrastructure must exist\n" +
   112  				"for it to be tainted."))
   113  		return 1
   114  	}
   115  
   116  	// Get the proper module we want to taint
   117  	modPath := strings.Split(module, ".")
   118  	mod := s.ModuleByPath(modPath)
   119  	if mod == nil {
   120  		if allowMissing {
   121  			return c.allowMissingExit(name, module)
   122  		}
   123  
   124  		c.Ui.Error(fmt.Sprintf(
   125  			"The module %s could not be found. There is nothing to taint.",
   126  			module))
   127  		return 1
   128  	}
   129  
   130  	// If there are no resources in this module, it is an error
   131  	if len(mod.Resources) == 0 {
   132  		if allowMissing {
   133  			return c.allowMissingExit(name, module)
   134  		}
   135  
   136  		c.Ui.Error(fmt.Sprintf(
   137  			"The module %s has no resources. There is nothing to taint.",
   138  			module))
   139  		return 1
   140  	}
   141  
   142  	// Get the resource we're looking for
   143  	rs, ok := mod.Resources[name]
   144  	if !ok {
   145  		if allowMissing {
   146  			return c.allowMissingExit(name, module)
   147  		}
   148  
   149  		c.Ui.Error(fmt.Sprintf(
   150  			"The resource %s couldn't be found in the module %s.",
   151  			name,
   152  			module))
   153  		return 1
   154  	}
   155  
   156  	// Taint the resource
   157  	rs.Taint()
   158  
   159  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   160  	if err := st.WriteState(s); err != nil {
   161  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   162  		return 1
   163  	}
   164  	if err := st.PersistState(); err != nil {
   165  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   166  		return 1
   167  	}
   168  
   169  	c.Ui.Output(fmt.Sprintf(
   170  		"The resource %s in the module %s has been marked as tainted!",
   171  		name, module))
   172  	return 0
   173  }
   174  
   175  func (c *TaintCommand) Help() string {
   176  	helpText := `
   177  Usage: terraform taint [options] name
   178  
   179    Manually mark a resource as tainted, forcing a destroy and recreate
   180    on the next plan/apply.
   181  
   182    This will not modify your infrastructure. This command changes your
   183    state to mark a resource as tainted so that during the next plan or
   184    apply, that resource will be destroyed and recreated. This command on
   185    its own will not modify infrastructure. This command can be undone by
   186    reverting the state backup file that is created.
   187  
   188  Options:
   189  
   190    -allow-missing      If specified, the command will succeed (exit code 0)
   191                        even if the resource is missing.
   192  
   193    -backup=path        Path to backup the existing state file before
   194                        modifying. Defaults to the "-state-out" path with
   195                        ".backup" extension. Set to "-" to disable backup.
   196  
   197    -lock=true          Lock the state file when locking is supported.
   198  
   199    -lock-timeout=0s    Duration to retry a state lock.
   200  
   201    -module=path        The module path where the resource lives. By
   202                        default this will be root. Child modules can be specified
   203                        by names. Ex. "consul" or "consul.vpc" (nested modules).
   204  
   205    -no-color           If specified, output won't contain any color.
   206  
   207    -state=path         Path to read and save state (unless state-out
   208                        is specified). Defaults to "terraform.tfstate".
   209  
   210    -state-out=path     Path to write updated state file. By default, the
   211                        "-state" path will be used.
   212  
   213  `
   214  	return strings.TrimSpace(helpText)
   215  }
   216  
   217  func (c *TaintCommand) Synopsis() string {
   218  	return "Manually mark a resource for recreation"
   219  }
   220  
   221  func (c *TaintCommand) allowMissingExit(name, module string) int {
   222  	c.Ui.Output(fmt.Sprintf(
   223  		"The resource %s in the module %s was not found, but\n"+
   224  			"-allow-missing is set, so we're exiting successfully.",
   225  		name, module))
   226  	return 0
   227  }