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