github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/command/taint.go (about)

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