github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/untaint.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  // UntaintCommand is a cli.Command implementation that manually untaints
    15  // a resource, marking it as primary and ready for service.
    16  type UntaintCommand struct {
    17  	Meta
    18  }
    19  
    20  func (c *UntaintCommand) 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("untaint")
    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 untaint
    45  	args = cmdFlags.Args()
    46  	if len(args) != 1 {
    47  		c.Ui.Error("The untaint 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  	// Load the backend
    65  	b, backendDiags := c.Backend(nil)
    66  	diags = diags.Append(backendDiags)
    67  	if backendDiags.HasErrors() {
    68  		c.showDiagnostics(diags)
    69  		return 1
    70  	}
    71  
    72  	// Get the state
    73  	workspace := c.Workspace()
    74  	stateMgr, err := b.StateMgr(workspace)
    75  	if err != nil {
    76  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    77  		return 1
    78  	}
    79  
    80  	if c.stateLock {
    81  		stateLocker := clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
    82  		if err := stateLocker.Lock(stateMgr, "untaint"); err != nil {
    83  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
    84  			return 1
    85  		}
    86  		defer stateLocker.Unlock(nil)
    87  	}
    88  
    89  	if err := stateMgr.RefreshState(); err != nil {
    90  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    91  		return 1
    92  	}
    93  
    94  	// Get the actual state structure
    95  	state := stateMgr.State()
    96  	if state.Empty() {
    97  		if allowMissing {
    98  			return c.allowMissingExit(addr)
    99  		}
   100  
   101  		diags = diags.Append(tfdiags.Sourceless(
   102  			tfdiags.Error,
   103  			"No such resource instance",
   104  			"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.",
   105  		))
   106  		c.showDiagnostics(diags)
   107  		return 1
   108  	}
   109  
   110  	ss := state.SyncWrapper()
   111  
   112  	// Get the resource and instance we're going to taint
   113  	rs := ss.Resource(addr.ContainingResource())
   114  	is := ss.ResourceInstance(addr)
   115  	if is == nil {
   116  		if allowMissing {
   117  			return c.allowMissingExit(addr)
   118  		}
   119  
   120  		diags = diags.Append(tfdiags.Sourceless(
   121  			tfdiags.Error,
   122  			"No such resource instance",
   123  			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),
   124  		))
   125  		c.showDiagnostics(diags)
   126  		return 1
   127  	}
   128  
   129  	obj := is.Current
   130  	if obj == nil {
   131  		if len(is.Deposed) != 0 {
   132  			diags = diags.Append(tfdiags.Sourceless(
   133  				tfdiags.Error,
   134  				"No such resource instance",
   135  				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),
   136  			))
   137  		} else {
   138  			// Don't know why we're here, but we'll produce a generic error message anyway.
   139  			diags = diags.Append(tfdiags.Sourceless(
   140  				tfdiags.Error,
   141  				"No such resource instance",
   142  				fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
   143  			))
   144  		}
   145  		c.showDiagnostics(diags)
   146  		return 1
   147  	}
   148  
   149  	if obj.Status != states.ObjectTainted {
   150  		diags = diags.Append(tfdiags.Sourceless(
   151  			tfdiags.Error,
   152  			"Resource instance is not tainted",
   153  			fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr),
   154  		))
   155  		c.showDiagnostics(diags)
   156  		return 1
   157  	}
   158  	obj.Status = states.ObjectReady
   159  	ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
   160  
   161  	if err := stateMgr.WriteState(state); err != nil {
   162  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   163  		return 1
   164  	}
   165  	if err := stateMgr.PersistState(); err != nil {
   166  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   167  		return 1
   168  	}
   169  
   170  	c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr))
   171  	return 0
   172  }
   173  
   174  func (c *UntaintCommand) Help() string {
   175  	helpText := `
   176  Usage: terraform untaint [options] name
   177  
   178    Manually unmark a resource as tainted, restoring it as the primary
   179    instance in the state.  This reverses either a manual 'terraform taint'
   180    or the result of provisioners failing on a resource.
   181  
   182    This will not modify your infrastructure. This command changes your
   183    state to unmark a resource as tainted.  This command can be undone by
   184    reverting the state backup file that is created, or by running
   185    'terraform taint' on the resource.
   186  
   187  Options:
   188  
   189    -allow-missing      If specified, the command will succeed (exit code 0)
   190                        even if the resource is missing.
   191  
   192    -backup=path        Path to backup the existing state file before
   193                        modifying. Defaults to the "-state-out" path with
   194                        ".backup" extension. Set to "-" to disable backup.
   195  
   196    -lock=true          Lock the state file when locking is supported.
   197  
   198    -lock-timeout=0s    Duration to retry a state lock.
   199  
   200    -module=path        The module path where the resource lives. By
   201                        default this will be root. Child modules can be specified
   202                        by names. Ex. "consul" or "consul.vpc" (nested modules).
   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 *UntaintCommand) Synopsis() string {
   215  	return "Manually unmark a resource as tainted"
   216  }
   217  
   218  func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
   219  	c.showDiagnostics(tfdiags.Sourceless(
   220  		tfdiags.Warning,
   221  		"No such resource instance",
   222  		"Resource instance %s was not found, but this is not an error because -allow-missing was set.",
   223  	))
   224  	return 0
   225  }