github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/taint.go (about)

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