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