github.com/rstandt/terraform@v0.12.32-0.20230710220336-b1063613405c/command/workspace_delete.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  	"time"
     8  
     9  	"github.com/hashicorp/terraform/command/clistate"
    10  	"github.com/hashicorp/terraform/tfdiags"
    11  	"github.com/mitchellh/cli"
    12  	"github.com/posener/complete"
    13  )
    14  
    15  type WorkspaceDeleteCommand struct {
    16  	Meta
    17  	LegacyName bool
    18  }
    19  
    20  func (c *WorkspaceDeleteCommand) Run(args []string) int {
    21  	args, err := c.Meta.process(args, true)
    22  	if err != nil {
    23  		return 1
    24  	}
    25  
    26  	envCommandShowWarning(c.Ui, c.LegacyName)
    27  
    28  	var force bool
    29  	var stateLock bool
    30  	var stateLockTimeout time.Duration
    31  	cmdFlags := c.Meta.defaultFlagSet("workspace delete")
    32  	cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace")
    33  	cmdFlags.BoolVar(&stateLock, "lock", true, "lock state")
    34  	cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout")
    35  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    36  	if err := cmdFlags.Parse(args); err != nil {
    37  		c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error()))
    38  		return 1
    39  	}
    40  
    41  	args = cmdFlags.Args()
    42  	if len(args) == 0 {
    43  		c.Ui.Error("expected NAME.\n")
    44  		return cli.RunResultHelp
    45  	}
    46  
    47  	workspace := args[0]
    48  
    49  	if !validWorkspaceName(workspace) {
    50  		c.Ui.Error(fmt.Sprintf(envInvalidName, workspace))
    51  		return 1
    52  	}
    53  
    54  	configPath, err := ModulePath(args[1:])
    55  	if err != nil {
    56  		c.Ui.Error(err.Error())
    57  		return 1
    58  	}
    59  
    60  	var diags tfdiags.Diagnostics
    61  
    62  	backendConfig, backendDiags := c.loadBackendConfig(configPath)
    63  	diags = diags.Append(backendDiags)
    64  	if diags.HasErrors() {
    65  		c.showDiagnostics(diags)
    66  		return 1
    67  	}
    68  
    69  	// Load the backend
    70  	b, backendDiags := c.Backend(&BackendOpts{
    71  		Config: backendConfig,
    72  	})
    73  	diags = diags.Append(backendDiags)
    74  	if backendDiags.HasErrors() {
    75  		c.showDiagnostics(diags)
    76  		return 1
    77  	}
    78  
    79  	workspaces, err := b.Workspaces()
    80  	if err != nil {
    81  		c.Ui.Error(err.Error())
    82  		return 1
    83  	}
    84  
    85  	exists := false
    86  	for _, ws := range workspaces {
    87  		if workspace == ws {
    88  			exists = true
    89  			break
    90  		}
    91  	}
    92  
    93  	if !exists {
    94  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), workspace))
    95  		return 1
    96  	}
    97  
    98  	if workspace == c.Workspace() {
    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(context.Background(), stateLockTimeout, c.Ui, c.Colorize())
   113  		if err := stateLocker.Lock(stateMgr, "workspace_delete"); err != nil {
   114  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
   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(nil)
   124  		c.Ui.Error(err.Error())
   125  		return 1
   126  	}
   127  
   128  	hasResources := stateMgr.State().HasResources()
   129  
   130  	if hasResources && !force {
   131  		// We need to release the lock before exit
   132  		stateLocker.Unlock(nil)
   133  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), workspace))
   134  		return 1
   135  	}
   136  
   137  	// We need to release the lock just before deleting the state, in case
   138  	// the backend can't remove the resource while holding the lock. This
   139  	// is currently true for Windows local files.
   140  	//
   141  	// TODO: While there is little safety in locking while deleting the
   142  	// state, it might be nice to be able to coordinate processes around
   143  	// state deletion, i.e. in a CI environment. Adding Delete() as a
   144  	// required method of States would allow the removal of the resource to
   145  	// be delegated from the Backend to the State itself.
   146  	stateLocker.Unlock(nil)
   147  
   148  	err = b.DeleteWorkspace(workspace)
   149  	if err != nil {
   150  		c.Ui.Error(err.Error())
   151  		return 1
   152  	}
   153  
   154  	c.Ui.Output(
   155  		c.Colorize().Color(
   156  			fmt.Sprintf(envDeleted, workspace),
   157  		),
   158  	)
   159  
   160  	if hasResources {
   161  		c.Ui.Output(
   162  			c.Colorize().Color(
   163  				fmt.Sprintf(envWarnNotEmpty, workspace),
   164  			),
   165  		)
   166  	}
   167  
   168  	return 0
   169  }
   170  
   171  func (c *WorkspaceDeleteCommand) AutocompleteArgs() complete.Predictor {
   172  	return completePredictSequence{
   173  		complete.PredictNothing, // the "select" subcommand itself (already matched)
   174  		c.completePredictWorkspaceName(),
   175  		complete.PredictDirs(""),
   176  	}
   177  }
   178  
   179  func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags {
   180  	return complete.Flags{
   181  		"-force": complete.PredictNothing,
   182  	}
   183  }
   184  
   185  func (c *WorkspaceDeleteCommand) Help() string {
   186  	helpText := `
   187  Usage: terraform workspace delete [OPTIONS] NAME [DIR]
   188  
   189    Delete a Terraform workspace
   190  
   191  
   192  Options:
   193  
   194      -force    remove a non-empty workspace.
   195  
   196      -lock=true          Lock the state file when locking is supported.
   197  
   198      -lock-timeout=0s    Duration to retry a state lock.
   199  
   200  `
   201  	return strings.TrimSpace(helpText)
   202  }
   203  
   204  func (c *WorkspaceDeleteCommand) Synopsis() string {
   205  	return "Delete a workspace"
   206  }