github.com/ggriffiths/terraform@v0.9.0-beta1.0.20170222213024-79c4935604cb/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  	st, err := b.State()
    71  	if err != nil {
    72  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    73  		return 1
    74  	}
    75  
    76  	if c.Meta.stateLock {
    77  		lockInfo := state.NewLockInfo()
    78  		lockInfo.Operation = "taint"
    79  		lockID, err := clistate.Lock(st, lockInfo, c.Ui, c.Colorize())
    80  		if err != nil {
    81  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
    82  			return 1
    83  		}
    84  
    85  		defer clistate.Unlock(st, lockID, c.Ui, c.Colorize())
    86  	}
    87  
    88  	// Get the actual state structure
    89  	s := st.State()
    90  	if s.Empty() {
    91  		if allowMissing {
    92  			return c.allowMissingExit(name, module)
    93  		}
    94  
    95  		c.Ui.Error(fmt.Sprintf(
    96  			"The state is empty. The most common reason for this is that\n" +
    97  				"an invalid state file path was given or Terraform has never\n " +
    98  				"been run for this infrastructure. Infrastructure must exist\n" +
    99  				"for it to be tainted."))
   100  		return 1
   101  	}
   102  
   103  	// Get the proper module we want to taint
   104  	modPath := strings.Split(module, ".")
   105  	mod := s.ModuleByPath(modPath)
   106  	if mod == nil {
   107  		if allowMissing {
   108  			return c.allowMissingExit(name, module)
   109  		}
   110  
   111  		c.Ui.Error(fmt.Sprintf(
   112  			"The module %s could not be found. There is nothing to taint.",
   113  			module))
   114  		return 1
   115  	}
   116  
   117  	// If there are no resources in this module, it is an error
   118  	if len(mod.Resources) == 0 {
   119  		if allowMissing {
   120  			return c.allowMissingExit(name, module)
   121  		}
   122  
   123  		c.Ui.Error(fmt.Sprintf(
   124  			"The module %s has no resources. There is nothing to taint.",
   125  			module))
   126  		return 1
   127  	}
   128  
   129  	// Get the resource we're looking for
   130  	rs, ok := mod.Resources[name]
   131  	if !ok {
   132  		if allowMissing {
   133  			return c.allowMissingExit(name, module)
   134  		}
   135  
   136  		c.Ui.Error(fmt.Sprintf(
   137  			"The resource %s couldn't be found in the module %s.",
   138  			name,
   139  			module))
   140  		return 1
   141  	}
   142  
   143  	// Taint the resource
   144  	rs.Taint()
   145  
   146  	log.Printf("[INFO] Writing state output to: %s", c.Meta.StateOutPath())
   147  	if err := st.WriteState(s); err != nil {
   148  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   149  		return 1
   150  	}
   151  	if err := st.PersistState(); err != nil {
   152  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   153  		return 1
   154  	}
   155  
   156  	c.Ui.Output(fmt.Sprintf(
   157  		"The resource %s in the module %s has been marked as tainted!",
   158  		name, module))
   159  	return 0
   160  }
   161  
   162  func (c *TaintCommand) Help() string {
   163  	helpText := `
   164  Usage: terraform taint [options] name
   165  
   166    Manually mark a resource as tainted, forcing a destroy and recreate
   167    on the next plan/apply.
   168  
   169    This will not modify your infrastructure. This command changes your
   170    state to mark a resource as tainted so that during the next plan or
   171    apply, that resource will be destroyed and recreated. This command on
   172    its own will not modify infrastructure. This command can be undone by
   173    reverting the state backup file that is created.
   174  
   175  Options:
   176  
   177    -allow-missing      If specified, the command will succeed (exit code 0)
   178                        even if the resource is missing.
   179  
   180    -backup=path        Path to backup the existing state file before
   181                        modifying. Defaults to the "-state-out" path with
   182                        ".backup" extension. Set to "-" to disable backup.
   183  
   184    -lock=true          Lock the state file when locking is supported.
   185  
   186    -module=path        The module path where the resource lives. By
   187                        default this will be root. Child modules can be specified
   188                        by names. Ex. "consul" or "consul.vpc" (nested modules).
   189  
   190    -no-color           If specified, output won't contain any color.
   191  
   192    -state=path         Path to read and save state (unless state-out
   193                        is specified). Defaults to "terraform.tfstate".
   194  
   195    -state-out=path     Path to write updated state file. By default, the
   196                        "-state" path will be used.
   197  
   198  `
   199  	return strings.TrimSpace(helpText)
   200  }
   201  
   202  func (c *TaintCommand) Synopsis() string {
   203  	return "Manually mark a resource for recreation"
   204  }
   205  
   206  func (c *TaintCommand) allowMissingExit(name, module string) int {
   207  	c.Ui.Output(fmt.Sprintf(
   208  		"The resource %s in the module %s was not found, but\n"+
   209  			"-allow-missing is set, so we're exiting successfully.",
   210  		name, module))
   211  	return 0
   212  }