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