github.com/pulumi/terraform@v1.4.0/pkg/command/taint.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  
     7  	"github.com/pulumi/terraform/pkg/addrs"
     8  	"github.com/pulumi/terraform/pkg/command/arguments"
     9  	"github.com/pulumi/terraform/pkg/command/clistate"
    10  	"github.com/pulumi/terraform/pkg/command/views"
    11  	"github.com/pulumi/terraform/pkg/states"
    12  	"github.com/pulumi/terraform/pkg/terraform"
    13  	"github.com/pulumi/terraform/pkg/tfdiags"
    14  )
    15  
    16  // TaintCommand is a cli.Command implementation that manually taints
    17  // a resource, marking it for recreation.
    18  type TaintCommand struct {
    19  	Meta
    20  }
    21  
    22  func (c *TaintCommand) Run(args []string) int {
    23  	args = c.Meta.process(args)
    24  	var allowMissing bool
    25  	cmdFlags := c.Meta.ignoreRemoteVersionFlagSet("taint")
    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 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  	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  	if addr.Resource.Resource.Mode != addrs.ManagedResourceMode {
    56  		c.Ui.Error(fmt.Sprintf("Resource instance %s cannot be tainted", addr))
    57  		return 1
    58  	}
    59  
    60  	if diags := c.Meta.checkRequiredVersion(); diags != nil {
    61  		c.showDiagnostics(diags)
    62  		return 1
    63  	}
    64  
    65  	// Load the backend
    66  	b, backendDiags := c.Backend(nil)
    67  	diags = diags.Append(backendDiags)
    68  	if backendDiags.HasErrors() {
    69  		c.showDiagnostics(diags)
    70  		return 1
    71  	}
    72  
    73  	// Determine the workspace name
    74  	workspace, err := c.Workspace()
    75  	if err != nil {
    76  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    77  		return 1
    78  	}
    79  
    80  	// Check remote Terraform version is compatible
    81  	remoteVersionDiags := c.remoteVersionCheck(b, workspace)
    82  	diags = diags.Append(remoteVersionDiags)
    83  	c.showDiagnostics(diags)
    84  	if diags.HasErrors() {
    85  		return 1
    86  	}
    87  
    88  	// Get the state
    89  	stateMgr, err := b.StateMgr(workspace)
    90  	if err != nil {
    91  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
    92  		return 1
    93  	}
    94  
    95  	if c.stateLock {
    96  		stateLocker := clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
    97  		if diags := stateLocker.Lock(stateMgr, "taint"); diags.HasErrors() {
    98  			c.showDiagnostics(diags)
    99  			return 1
   100  		}
   101  		defer func() {
   102  			if diags := stateLocker.Unlock(); diags.HasErrors() {
   103  				c.showDiagnostics(diags)
   104  			}
   105  		}()
   106  	}
   107  
   108  	if err := stateMgr.RefreshState(); err != nil {
   109  		c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err))
   110  		return 1
   111  	}
   112  
   113  	// Get the actual state structure
   114  	state := stateMgr.State()
   115  	if state.Empty() {
   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  			"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.",
   124  		))
   125  		c.showDiagnostics(diags)
   126  		return 1
   127  	}
   128  
   129  	// Get schemas, if possible, before writing state
   130  	var schemas *terraform.Schemas
   131  	if isCloudMode(b) {
   132  		var schemaDiags tfdiags.Diagnostics
   133  		schemas, schemaDiags = c.MaybeGetSchemas(state, nil)
   134  		diags = diags.Append(schemaDiags)
   135  	}
   136  
   137  	ss := state.SyncWrapper()
   138  
   139  	// Get the resource and instance we're going to taint
   140  	rs := ss.Resource(addr.ContainingResource())
   141  	is := ss.ResourceInstance(addr)
   142  	if is == nil {
   143  		if allowMissing {
   144  			return c.allowMissingExit(addr)
   145  		}
   146  
   147  		diags = diags.Append(tfdiags.Sourceless(
   148  			tfdiags.Error,
   149  			"No such resource instance",
   150  			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),
   151  		))
   152  		c.showDiagnostics(diags)
   153  		return 1
   154  	}
   155  
   156  	obj := is.Current
   157  	if obj == nil {
   158  		if len(is.Deposed) != 0 {
   159  			diags = diags.Append(tfdiags.Sourceless(
   160  				tfdiags.Error,
   161  				"No such resource instance",
   162  				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),
   163  			))
   164  		} else {
   165  			// Don't know why we're here, but we'll produce a generic error message anyway.
   166  			diags = diags.Append(tfdiags.Sourceless(
   167  				tfdiags.Error,
   168  				"No such resource instance",
   169  				fmt.Sprintf("Resource instance %s does not currently have a remote object associated with it, so it cannot be tainted.", addr),
   170  			))
   171  		}
   172  		c.showDiagnostics(diags)
   173  		return 1
   174  	}
   175  
   176  	obj.Status = states.ObjectTainted
   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 marked as tainted.", addr))
   190  	return 0
   191  }
   192  
   193  func (c *TaintCommand) Help() string {
   194  	helpText := `
   195  Usage: terraform [global options] taint [options] <address>
   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    this command.
   201  
   202    This will not modify your infrastructure directly, but subsequent
   203    Terraform plans will include actions to destroy the remote object
   204    and create a new object to replace it.
   205  
   206    You can remove the "taint" state from a resource instance using
   207    the "terraform untaint" command.
   208  
   209    The address is in the usual resource address syntax, such as:
   210      aws_instance.foo
   211      aws_instance.bar[1]
   212      module.foo.module.bar.aws_instance.baz
   213  
   214    Use your shell's quoting or escaping syntax to ensure that the
   215    address will reach Terraform correctly, without any special
   216    interpretation.
   217  
   218  Options:
   219  
   220    -allow-missing          If specified, the command will succeed (exit code 0)
   221                            even if the resource is missing.
   222  
   223    -lock=false             Don't hold a state lock during the operation. This is
   224                            dangerous if others might concurrently run commands
   225                            against the same workspace.
   226  
   227    -lock-timeout=0s        Duration to retry a state lock.
   228  
   229    -ignore-remote-version  A rare option used for the remote backend only. See
   230                            the remote backend documentation for more information.
   231  
   232    -state, state-out, and -backup are legacy options supported for the local
   233    backend only. For more information, see the local backend's documentation.
   234  
   235  `
   236  	return strings.TrimSpace(helpText)
   237  }
   238  
   239  func (c *TaintCommand) Synopsis() string {
   240  	return "Mark a resource instance as not fully functional"
   241  }
   242  
   243  func (c *TaintCommand) allowMissingExit(name addrs.AbsResourceInstance) int {
   244  	c.showDiagnostics(tfdiags.Sourceless(
   245  		tfdiags.Warning,
   246  		"No such resource instance",
   247  		fmt.Sprintf("Resource instance %s was not found, but this is not an error because -allow-missing was set.", name),
   248  	))
   249  	return 0
   250  }