github.com/alexaandru/terraform@v0.11.1-0.20171120185746-28632790b723/command/workspace_delete.go (about)

     1  package command
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/hashicorp/terraform/command/clistate"
     9  	"github.com/hashicorp/terraform/state"
    10  	"github.com/mitchellh/cli"
    11  	"github.com/posener/complete"
    12  )
    13  
    14  type WorkspaceDeleteCommand struct {
    15  	Meta
    16  	LegacyName bool
    17  }
    18  
    19  func (c *WorkspaceDeleteCommand) Run(args []string) int {
    20  	args, err := c.Meta.process(args, true)
    21  	if err != nil {
    22  		return 1
    23  	}
    24  
    25  	envCommandShowWarning(c.Ui, c.LegacyName)
    26  
    27  	force := false
    28  	cmdFlags := c.Meta.flagSet("workspace")
    29  	cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace")
    30  	cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
    31  	if err := cmdFlags.Parse(args); err != nil {
    32  		return 1
    33  	}
    34  	args = cmdFlags.Args()
    35  	if len(args) == 0 {
    36  		c.Ui.Error("expected NAME.\n")
    37  		return cli.RunResultHelp
    38  	}
    39  
    40  	delEnv := args[0]
    41  
    42  	if !validWorkspaceName(delEnv) {
    43  		c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv))
    44  		return 1
    45  	}
    46  
    47  	configPath, err := ModulePath(args[1:])
    48  	if err != nil {
    49  		c.Ui.Error(err.Error())
    50  		return 1
    51  	}
    52  
    53  	cfg, err := c.Config(configPath)
    54  	if err != nil {
    55  		c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err))
    56  		return 1
    57  	}
    58  
    59  	// Load the backend
    60  	b, err := c.Backend(&BackendOpts{
    61  		Config: cfg,
    62  	})
    63  
    64  	if err != nil {
    65  		c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err))
    66  		return 1
    67  	}
    68  
    69  	states, err := b.States()
    70  	if err != nil {
    71  		c.Ui.Error(err.Error())
    72  		return 1
    73  	}
    74  
    75  	exists := false
    76  	for _, s := range states {
    77  		if delEnv == s {
    78  			exists = true
    79  			break
    80  		}
    81  	}
    82  
    83  	if !exists {
    84  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), delEnv))
    85  		return 1
    86  	}
    87  
    88  	if delEnv == c.Workspace() {
    89  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), delEnv))
    90  		return 1
    91  	}
    92  
    93  	// we need the actual state to see if it's empty
    94  	sMgr, err := b.State(delEnv)
    95  	if err != nil {
    96  		c.Ui.Error(err.Error())
    97  		return 1
    98  	}
    99  
   100  	if err := sMgr.RefreshState(); err != nil {
   101  		c.Ui.Error(err.Error())
   102  		return 1
   103  	}
   104  
   105  	hasResources := sMgr.State().HasResources()
   106  
   107  	if hasResources && !force {
   108  		c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), delEnv))
   109  		return 1
   110  	}
   111  
   112  	// Honor the lock request, for consistency and one final safety check.
   113  	if c.stateLock {
   114  		lockCtx, cancel := context.WithTimeout(context.Background(), c.stateLockTimeout)
   115  		defer cancel()
   116  
   117  		// Lock the state if we can
   118  		lockInfo := state.NewLockInfo()
   119  		lockInfo.Operation = "workspace delete"
   120  		lockID, err := clistate.Lock(lockCtx, sMgr, lockInfo, c.Ui, c.Colorize())
   121  		if err != nil {
   122  			c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
   123  			return 1
   124  		}
   125  
   126  		// We need to release the lock just before deleting the state, in case
   127  		// the backend can't remove the resource while holding the lock. This
   128  		// is currently true for Windows local files.
   129  		//
   130  		// TODO: While there is little safety in locking while deleting the
   131  		// state, it might be nice to be able to coordinate processes around
   132  		// state deletion, i.e. in a CI environment. Adding Delete() as a
   133  		// required method of States would allow the removal of the resource to
   134  		// be delegated from the Backend to the State itself.
   135  		clistate.Unlock(sMgr, lockID, c.Ui, c.Colorize())
   136  	}
   137  
   138  	err = b.DeleteState(delEnv)
   139  	if err != nil {
   140  		c.Ui.Error(err.Error())
   141  		return 1
   142  	}
   143  
   144  	c.Ui.Output(
   145  		c.Colorize().Color(
   146  			fmt.Sprintf(envDeleted, delEnv),
   147  		),
   148  	)
   149  
   150  	if hasResources {
   151  		c.Ui.Output(
   152  			c.Colorize().Color(
   153  				fmt.Sprintf(envWarnNotEmpty, delEnv),
   154  			),
   155  		)
   156  	}
   157  
   158  	return 0
   159  }
   160  
   161  func (c *WorkspaceDeleteCommand) AutocompleteArgs() complete.Predictor {
   162  	return completePredictSequence{
   163  		complete.PredictNothing, // the "select" subcommand itself (already matched)
   164  		c.completePredictWorkspaceName(),
   165  		complete.PredictDirs(""),
   166  	}
   167  }
   168  
   169  func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags {
   170  	return complete.Flags{
   171  		"-force": complete.PredictNothing,
   172  	}
   173  }
   174  
   175  func (c *WorkspaceDeleteCommand) Help() string {
   176  	helpText := `
   177  Usage: terraform workspace delete [OPTIONS] NAME [DIR]
   178  
   179    Delete a Terraform workspace
   180  
   181  
   182  Options:
   183  
   184      -force    remove a non-empty workspace.
   185  `
   186  	return strings.TrimSpace(helpText)
   187  }
   188  
   189  func (c *WorkspaceDeleteCommand) Synopsis() string {
   190  	return "Delete a workspace"
   191  }