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