github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/taint.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/addrs"
     9  	"github.com/hashicorp/terraform/command/clistate"
    10  	"github.com/hashicorp/terraform/states"
    11  	"github.com/hashicorp/terraform/tfdiags"
    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, err := c.Meta.process(args, false)
    22  	if err != nil {
    23  		return 1
    24  	}
    25  
    26  	var module string
    27  	var allowMissing bool
    28  	cmdFlags := c.Meta.defaultFlagSet("taint")
    29  	cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "module")
    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.StringVar(&module, "module", "", "module")
    34  	cmdFlags.StringVar(&c.Meta.statePath, "state", DefaultStateFilename, "path")
    35  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    36  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    37  	if err := cmdFlags.Parse(args); err != nil {
    38  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    39  		return 1
    40  	}
    41  
    42  	var diags tfdiags.Diagnostics
    43  
    44  	// Require the one argument for the resource to taint
    45  	args = cmdFlags.Args()
    46  	if len(args) != 1 {
    47  		c.Ui.Error("The taint command expects exactly one argument.")
    48  		cmdFlags.Usage()
    49  		return 1
    50  	}
    51  
    52  	if module != "" {
    53  		c.Ui.Error("The -module option is no longer used. Instead, include the module path in the main resource address, like \"module.foo.module.bar.null_resource.baz\".")
    54  		return 1
    55  	}
    56  
    57  	addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
    58  	diags = diags.Append(addrDiags)
    59  	if addrDiags.HasErrors() {
    60  		c.showDiagnostics(diags)
    61  		return 1
    62  	}
    63  
    64  	if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
    65  		c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr))
    66  		return 1
    67  	}
    68  
    69  	// Load the backend
    70  	b, backendDiags := c.Backend(nil)
    71  	diags = diags.Append(backendDiags)
    72  	if backendDiags.HasErrors() {
    73  		c.showDiagnostics(diags)
    74  		return 1
    75  	}
    76  
    77  	// Get the state
    78  	env := c.Workspace()
    79  	stateMgr, err := b.StateMgr(env)
    80  	if 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(stateMgr, "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  	if err := stateMgr.RefreshState(); err != nil {
    95  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    96  		return 1
    97  	}
    98  
    99  	// Get the actual state structure
   100  	state := stateMgr.State()
   101  	if state.Empty() {
   102  		if allowMissing {
   103  			return c.allowMissingExit(addr)
   104  		}
   105  
   106  		diags = diags.Append(tfdiags.Sourceless(
   107  			tfdiags.Error,
   108  			"No such resource instance",
   109  			"The state currently contains no resource instances whatsoever. This may occur if the configuration has never been applied or if it has recently been destroyed.",
   110  		))
   111  		c.showDiagnostics(diags)
   112  		return 1
   113  	}
   114  
   115  	ss := state.SyncWrapper()
   116  
   117  	// Get the resource and instance we're going to taint
   118  	rs := ss.Resource(addr.ContainingResource())
   119  	is := ss.ResourceInstance(addr)
   120  	if is == nil {
   121  		if allowMissing {
   122  			return c.allowMissingExit(addr)
   123  		}
   124  
   125  		diags = diags.Append(tfdiags.Sourceless(
   126  			tfdiags.Error,
   127  			"No such resource instance",
   128  			fmt.Sprintf("There is no resource instance in the state with the address %s. If the resource configuration has just been added, you must run \"terraform apply\" once to create the corresponding instance(s) before they can be tainted.", addr),
   129  		))
   130  		c.showDiagnostics(diags)
   131  		return 1
   132  	}
   133  
   134  	obj := is.Current
   135  	if obj == nil {
   136  		if len(is.Deposed) != 0 {
   137  			diags = diags.Append(tfdiags.Sourceless(
   138  				tfdiags.Error,
   139  				"No such resource instance",
   140  				fmt.Sprintf("Resource instance %s is currently part-way through a create_before_destroy replacement action. Run \"terraform apply\" to complete its replacement before tainting it.", addr),
   141  			))
   142  		} else {
   143  			// Don't know why we're here, but we'll produce a generic error message anyway.
   144  			diags = diags.Append(tfdiags.Sourceless(
   145  				tfdiags.Error,
   146  				"No such resource instance",
   147  				fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
   148  			))
   149  		}
   150  		c.showDiagnostics(diags)
   151  		return 1
   152  	}
   153  
   154  	obj.Status = states.ObjectTainted
   155  	ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
   156  
   157  	if err := stateMgr.WriteState(state); err != nil {
   158  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   159  		return 1
   160  	}
   161  	if err := stateMgr.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("Resource instance %s has been marked as tainted.", addr))
   167  	return 0
   168  }
   169  
   170  func (c *TaintCommand) Help() string {
   171  	helpText := `
   172  Usage: terraform taint [options] <address>
   173  
   174    Manually mark a resource as tainted, forcing a destroy and recreate
   175    on the next plan/apply.
   176  
   177    This will not modify your infrastructure. This command changes your
   178    state to mark a resource as tainted so that during the next plan or
   179    apply that resource will be destroyed and recreated. This command on
   180    its own will not modify infrastructure. This command can be undone
   181    using the "terraform untaint" command with the same address.
   182  
   183    The address is in the usual resource address syntax, as shown in
   184    the output from other commands, such as:
   185      aws_instance.foo
   186      aws_instance.bar[1]
   187      module.foo.module.bar.aws_instance.baz
   188  
   189  Options:
   190  
   191    -allow-missing      If specified, the command will succeed (exit code 0)
   192                        even if the resource is missing.
   193  
   194    -backup=path        Path to backup the existing state file before
   195                        modifying. Defaults to the "-state-out" path with
   196                        ".backup" extension. Set to "-" to disable backup.
   197  
   198    -lock=true          Lock the state file when locking is supported.
   199  
   200    -lock-timeout=0s    Duration to retry a state lock.
   201  
   202    -state=path         Path to read and save state (unless state-out
   203                        is specified). Defaults to "terraform.tfstate".
   204  
   205    -state-out=path     Path to write updated state file. By default, the
   206                        "-state" path will be used.
   207  
   208  `
   209  	return strings.TrimSpace(helpText)
   210  }
   211  
   212  func (c *TaintCommand) Synopsis() string {
   213  	return "Manually mark a resource for recreation"
   214  }
   215  
   216  func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
   217  	c.showDiagnostics(tfdiags.Sourceless(
   218  		tfdiags.Warning,
   219  		"No such resource instance",
   220  		"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
   221  	))
   222  	return 0
   223  }