kubeform.dev/terraform-backend-sdk@v0.0.0-20220310143633-45f07fe731c5/command/workspace_delete.go (about)

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