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