github.com/kevinklinger/open_terraform@v1.3.6/noninternal/command/untaint.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/kevinklinger/open_terraform/noninternal/addrs"
     8  	"github.com/kevinklinger/open_terraform/noninternal/command/arguments"
     9  	"github.com/kevinklinger/open_terraform/noninternal/command/clistate"
    10  	"github.com/kevinklinger/open_terraform/noninternal/command/views"
    11  	"github.com/kevinklinger/open_terraform/noninternal/states"
    12  	"github.com/kevinklinger/open_terraform/noninternal/terraform"
    13  	"github.com/kevinklinger/open_terraform/noninternal/tfdiags"
    14  )
    15  
    16  // UntaintCommand is a cli.Command implementation that manually untaints
    17  // a resource, marking it as primary and ready for service.
    18  type UntaintCommand struct {
    19  	Meta
    20  }
    21  
    22  func (c *UntaintCommand) Run(args []string) int {
    23  	args = c.Meta.process(args)
    24  	var allowMissing bool
    25  	cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("untaint")
    26  	cmdFlags.BoolVar(&allowMissing, "allow-missing", false, "allow missing")
    27  	cmdFlags.StringVar(&c.Meta.backupPath, "backup", "", "path")
    28  	cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state")
    29  	cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout")
    30  	cmdFlags.StringVar(&c.Meta.statePath, "state", "", "path")
    31  	cmdFlags.StringVar(&c.Meta.stateOutPath, "state-out", "", "path")
    32  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    33  	if err := cmdFlags.Parse(args); err != nil {
    34  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    35  		return 1
    36  	}
    37  
    38  	var diags tfdiags.Diagnostics
    39  
    40  	// Require the one argument for the resource to untaint
    41  	args = cmdFlags.Args()
    42  	if len(args) != 1 {
    43  		c.Ui.Error("The untaint command expects exactly one argument.")
    44  		cmdFlags.Usage()
    45  		return 1
    46  	}
    47  
    48  	addr, addrDiags := addrs.ParseAbsResourceInstanceStr(args[0])
    49  	diags = diags.Append(addrDiags)
    50  	if addrDiags.HasErrors() {
    51  		c.showDiagnostics(diags)
    52  		return 1
    53  	}
    54  
    55  	// Load the backend
    56  	b, backendDiags := c.Backend(nil)
    57  	diags = diags.Append(backendDiags)
    58  	if backendDiags.HasErrors() {
    59  		c.showDiagnostics(diags)
    60  		return 1
    61  	}
    62  
    63  	// Determine the workspace name
    64  	workspace, err := c.Workspace()
    65  	if err != nil {
    66  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    67  		return 1
    68  	}
    69  
    70  	// Check remote Terraform version is compatible
    71  	remoteVersionDiags := c.remoteVersionCheck(b, workspace)
    72  	diags = diags.Append(remoteVersionDiags)
    73  	c.showDiagnostics(diags)
    74  	if diags.HasErrors() {
    75  		return 1
    76  	}
    77  
    78  	// Get the state
    79  	stateMgr, err := b.StateMgr(workspace)
    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(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
    87  		if diags := stateLocker.Lock(stateMgr, "untaint"); diags.HasErrors() {
    88  			c.showDiagnostics(diags)
    89  			return 1
    90  		}
    91  		defer func() {
    92  			if diags := stateLocker.Unlock(); diags.HasErrors() {
    93  				c.showDiagnostics(diags)
    94  			}
    95  		}()
    96  	}
    97  
    98  	if err := stateMgr.RefreshState(); err != nil {
    99  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
   100  		return 1
   101  	}
   102  
   103  	// Get the actual state structure
   104  	state := stateMgr.State()
   105  	if state.Empty() {
   106  		if allowMissing {
   107  			return c.allowMissingExit(addr)
   108  		}
   109  
   110  		diags = diags.Append(tfdiags.Sourceless(
   111  			tfdiags.Error,
   112  			"No such resource instance",
   113  			"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.",
   114  		))
   115  		c.showDiagnostics(diags)
   116  		return 1
   117  	}
   118  
   119  	ss := state.SyncWrapper()
   120  
   121  	// Get the resource and instance we're going to taint
   122  	rs := ss.Resource(addr.ContainingResource())
   123  	is := ss.ResourceInstance(addr)
   124  	if is == nil {
   125  		if allowMissing {
   126  			return c.allowMissingExit(addr)
   127  		}
   128  
   129  		diags = diags.Append(tfdiags.Sourceless(
   130  			tfdiags.Error,
   131  			"No such resource instance",
   132  			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),
   133  		))
   134  		c.showDiagnostics(diags)
   135  		return 1
   136  	}
   137  
   138  	obj := is.Current
   139  	if obj == nil {
   140  		if len(is.Deposed) != 0 {
   141  			diags = diags.Append(tfdiags.Sourceless(
   142  				tfdiags.Error,
   143  				"No such resource instance",
   144  				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),
   145  			))
   146  		} else {
   147  			// Don't know why we're here, but we'll produce a generic error message anyway.
   148  			diags = diags.Append(tfdiags.Sourceless(
   149  				tfdiags.Error,
   150  				"No such resource instance",
   151  				fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
   152  			))
   153  		}
   154  		c.showDiagnostics(diags)
   155  		return 1
   156  	}
   157  
   158  	if obj.Status != states.ObjectTainted {
   159  		diags = diags.Append(tfdiags.Sourceless(
   160  			tfdiags.Error,
   161  			"Resource instance is not tainted",
   162  			fmt.Sprintf("Resource instance %s is not currently tainted, and so it cannot be untainted.", addr),
   163  		))
   164  		c.showDiagnostics(diags)
   165  		return 1
   166  	}
   167  
   168  	// Get schemas, if possible, before writing state
   169  	var schemas *terraform.Schemas
   170  	if isCloudMode(b) {
   171  		var schemaDiags tfdiags.Diagnostics
   172  		schemas, schemaDiags = c.MaybeGetSchemas(state, nil)
   173  		diags = diags.Append(schemaDiags)
   174  	}
   175  
   176  	obj.Status = states.ObjectReady
   177  	ss.SetResourceInstanceCurrent(addr, obj, rs.ProviderConfig)
   178  
   179  	if err := stateMgr.WriteState(state); err != nil {
   180  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   181  		return 1
   182  	}
   183  	if err := stateMgr.PersistState(schemas); err != nil {
   184  		c.Ui.Error(fmt.Sprintf("Error writing state file: %s", err))
   185  		return 1
   186  	}
   187  
   188  	c.showDiagnostics(diags)
   189  	c.Ui.Output(fmt.Sprintf("Resource instance %s has been successfully untainted.", addr))
   190  	return 0
   191  }
   192  
   193  func (c *UntaintCommand) Help() string {
   194  	helpText := `
   195  Usage: terraform [global options] untaint [options] name
   196  
   197    Terraform uses the term "tainted" to describe a resource instance
   198    which may not be fully functional, either because its creation
   199    partially failed or because you've manually marked it as such using
   200    the "terraform taint" command.
   201  
   202    This command removes that state from a resource instance, causing
   203    Terraform to see it as fully-functional and not in need of
   204    replacement.
   205  
   206    This will not modify your infrastructure directly. It only avoids
   207    Terraform planning to replace a tainted instance in a future operation.
   208  
   209  Options:
   210  
   211    -allow-missing          If specified, the command will succeed (exit code 0)
   212                            even if the resource is missing.
   213  
   214    -lock=false             Don't hold a state lock during the operation. This is
   215                            dangerous if others might concurrently run commands
   216                            against the same workspace.
   217  
   218    -lock-timeout=0s        Duration to retry a state lock.
   219  
   220    -ignore-remote-version  A rare option used for the remote backend only. See
   221                            the remote backend documentation for more information.
   222  
   223    -state, state-out, and -backup are legacy options supported for the local
   224    backend only. For more information, see the local backend's documentation.
   225  
   226  `
   227  	return strings.TrimSpace(helpText)
   228  }
   229  
   230  func (c *UntaintCommand) Synopsis() string {
   231  	return "Remove the 'tainted' state from a resource instance"
   232  }
   233  
   234  func (c *UntaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
   235  	c.showDiagnostics(tfdiags.Sourceless(
   236  		tfdiags.Warning,
   237  		"No such resource instance",
   238  		fmt.Sprintf("Resource instance %s was not found, but this is not an error because -allow-missing was set.", name),
   239  	))
   240  	return 0
   241  }