github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/command/workspace_delete.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"strings"
     6  	"time"
     7  
     8  	"github.com/hashicorp/terraform/internal/command/arguments"
     9  	"github.com/hashicorp/terraform/internal/command/clistate"
    10  	"github.com/hashicorp/terraform/internal/command/views"
    11  	"github.com/hashicorp/terraform/internal/states"
    12  	"github.com/hashicorp/terraform/internal/tfdiags"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/posener/complete"
    15  )
    16  
    17  type WorkspaceDeleteCommand struct {
    18  	Meta
    19  	LegacyName bool
    20  }
    21  
    22  func (c *WorkspaceDeleteCommand) Run(args []string) int {
    23  	args = c.Meta.process(args)
    24  	envCommandShowWarning(c.Ui, c.LegacyName)
    25  
    26  	var force bool
    27  	var stateLock bool
    28  	var stateLockTimeout time.Duration
    29  	cmdFlags := c.Meta.defaultFlagSet("workspace delete")
    30  	cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace")
    31  	cmdFlags.BoolVar(&stateLock, "lock", true, "lock state")
    32  	cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout")
    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  	args = cmdFlags.Args()
    40  	if len(args) != 1 {
    41  		c.Ui.Error("Expected a single argument: NAME.\n")
    42  		return cli.RunResultHelp
    43  	}
    44  
    45  	configPath, err := ModulePath(args[1:])
    46  	if err != nil {
    47  		c.Ui.Error(err.Error())
    48  		return 1
    49  	}
    50  
    51  	var diags tfdiags.Diagnostics
    52  
    53  	backendConfig, backendDiags := c.loadBackendConfig(configPath)
    54  	diags = diags.Append(backendDiags)
    55  	if diags.HasErrors() {
    56  		c.showDiagnostics(diags)
    57  		return 1
    58  	}
    59  
    60  	// Load the backend
    61  	b, backendDiags := c.Backend(&BackendOpts{
    62  		Config: backendConfig,
    63  	})
    64  	diags = diags.Append(backendDiags)
    65  	if backendDiags.HasErrors() {
    66  		c.showDiagnostics(diags)
    67  		return 1
    68  	}
    69  
    70  	// This command will not write state
    71  	c.ignoreRemoteVersionConflict(b)
    72  
    73  	workspaces, err := b.Workspaces()
    74  	if err != nil {
    75  		c.Ui.Error(err.Error())
    76  		return 1
    77  	}
    78  
    79  	workspace := args[0]
    80  	exists := false
    81  	for _, ws := range workspaces {
    82  		if workspace == ws {
    83  			exists = true
    84  			break
    85  		}
    86  	}
    87  
    88  	if !exists {
    89  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), workspace))
    90  		return 1
    91  	}
    92  
    93  	currentWorkspace, err := c.Workspace()
    94  	if err != nil {
    95  		c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err))
    96  		return 1
    97  	}
    98  	if workspace == currentWorkspace {
    99  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), workspace))
   100  		return 1
   101  	}
   102  
   103  	// we need the actual state to see if it's empty
   104  	stateMgr, err := b.StateMgr(workspace)
   105  	if err != nil {
   106  		c.Ui.Error(err.Error())
   107  		return 1
   108  	}
   109  
   110  	var stateLocker clistate.Locker
   111  	if stateLock {
   112  		stateLocker = clistate.NewLocker(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View))
   113  		if diags := stateLocker.Lock(stateMgr, "state-replace-provider"); diags.HasErrors() {
   114  			c.showDiagnostics(diags)
   115  			return 1
   116  		}
   117  	} else {
   118  		stateLocker = clistate.NewNoopLocker()
   119  	}
   120  
   121  	if err := stateMgr.RefreshState(); err != nil {
   122  		// We need to release the lock before exit
   123  		stateLocker.Unlock()
   124  		c.Ui.Error(err.Error())
   125  		return 1
   126  	}
   127  
   128  	hasResources := stateMgr.State().HasManagedResourceInstanceObjects()
   129  
   130  	if hasResources && !force {
   131  		// We'll collect a list of what's being managed here as extra context
   132  		// for the message.
   133  		var buf strings.Builder
   134  		for _, obj := range stateMgr.State().AllResourceInstanceObjectAddrs() {
   135  			if obj.DeposedKey == states.NotDeposed {
   136  				fmt.Fprintf(&buf, "\n  - %s", obj.Instance.String())
   137  			} else {
   138  				fmt.Fprintf(&buf, "\n  - %s (deposed object %s)", obj.Instance.String(), obj.DeposedKey)
   139  			}
   140  		}
   141  
   142  		// We need to release the lock before exit
   143  		stateLocker.Unlock()
   144  
   145  		diags = diags.Append(tfdiags.Sourceless(
   146  			tfdiags.Error,
   147  			"Workspace is not empty",
   148  			fmt.Sprintf(
   149  				"Workspace %q is currently tracking the following resource instances:%s\n\nDeleting this workspace would cause Terraform to lose track of any associated remote objects, which would then require you to delete them manually outside of Terraform. You should destroy these objects with Terraform before deleting the workspace.\n\nIf you want to delete this workspace anyway, and have Terraform forget about these managed objects, use the -force option to disable this safety check.",
   150  				workspace, buf.String(),
   151  			),
   152  		))
   153  		c.showDiagnostics(diags)
   154  		return 1
   155  	}
   156  
   157  	// We need to release the lock just before deleting the state, in case
   158  	// the backend can't remove the resource while holding the lock. This
   159  	// is currently true for Windows local files.
   160  	//
   161  	// TODO: While there is little safety in locking while deleting the
   162  	// state, it might be nice to be able to coordinate processes around
   163  	// state deletion, i.e. in a CI environment. Adding Delete() as a
   164  	// required method of States would allow the removal of the resource to
   165  	// be delegated from the Backend to the State itself.
   166  	stateLocker.Unlock()
   167  
   168  	err = b.DeleteWorkspace(workspace)
   169  	if err != nil {
   170  		c.Ui.Error(err.Error())
   171  		return 1
   172  	}
   173  
   174  	c.Ui.Output(
   175  		c.Colorize().Color(
   176  			fmt.Sprintf(envDeleted, workspace),
   177  		),
   178  	)
   179  
   180  	if hasResources {
   181  		c.Ui.Output(
   182  			c.Colorize().Color(
   183  				fmt.Sprintf(envWarnNotEmpty, workspace),
   184  			),
   185  		)
   186  	}
   187  
   188  	return 0
   189  }
   190  
   191  func (c *WorkspaceDeleteCommand) AutocompleteArgs() complete.Predictor {
   192  	return completePredictSequence{
   193  		c.completePredictWorkspaceName(),
   194  		complete.PredictDirs(""),
   195  	}
   196  }
   197  
   198  func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags {
   199  	return complete.Flags{
   200  		"-force": complete.PredictNothing,
   201  	}
   202  }
   203  
   204  func (c *WorkspaceDeleteCommand) Help() string {
   205  	helpText := `
   206  Usage: terraform [global options] workspace delete [OPTIONS] NAME
   207  
   208    Delete a Terraform workspace
   209  
   210  
   211  Options:
   212  
   213    -force             Remove even a non-empty workspace.
   214  
   215    -lock=false        Don't hold a state lock during the operation. This is
   216                       dangerous if others might concurrently run commands
   217                       against the same workspace.
   218  
   219    -lock-timeout=0s   Duration to retry a state lock.
   220  
   221  `
   222  	return strings.TrimSpace(helpText)
   223  }
   224  
   225  func (c *WorkspaceDeleteCommand) Synopsis() string {
   226  	return "Delete a workspace"
   227  }