github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/untaint.go (about)

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