2017-02-23 19:13:28 +01:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2017-04-01 21:42:13 +02:00
|
|
|
"context"
|
2017-02-23 19:13:28 +01:00
|
|
|
"fmt"
|
|
|
|
"strings"
|
|
|
|
|
2017-04-01 20:58:19 +02:00
|
|
|
"github.com/hashicorp/terraform/command/clistate"
|
2018-03-28 00:31:05 +02:00
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
2017-02-23 19:13:28 +01:00
|
|
|
"github.com/mitchellh/cli"
|
2017-09-26 04:02:12 +02:00
|
|
|
"github.com/posener/complete"
|
2017-02-23 19:13:28 +01:00
|
|
|
)
|
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
type WorkspaceDeleteCommand struct {
|
2017-02-23 19:13:28 +01:00
|
|
|
Meta
|
2017-05-31 00:06:13 +02:00
|
|
|
LegacyName bool
|
2017-02-23 19:13:28 +01:00
|
|
|
}
|
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
func (c *WorkspaceDeleteCommand) Run(args []string) int {
|
2017-03-08 05:09:48 +01:00
|
|
|
args, err := c.Meta.process(args, true)
|
|
|
|
if err != nil {
|
|
|
|
return 1
|
|
|
|
}
|
2017-02-23 19:13:28 +01:00
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
envCommandShowWarning(c.Ui, c.LegacyName)
|
|
|
|
|
2017-02-23 19:13:28 +01:00
|
|
|
force := false
|
2017-05-31 00:06:13 +02:00
|
|
|
cmdFlags := c.Meta.flagSet("workspace")
|
|
|
|
cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace")
|
2017-02-23 19:13:28 +01:00
|
|
|
cmdFlags.Usage = func() { c.Ui.Error(c.Help()) }
|
|
|
|
if err := cmdFlags.Parse(args); err != nil {
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
args = cmdFlags.Args()
|
2017-02-23 20:14:51 +01:00
|
|
|
if len(args) == 0 {
|
2017-02-23 19:13:28 +01:00
|
|
|
c.Ui.Error("expected NAME.\n")
|
|
|
|
return cli.RunResultHelp
|
|
|
|
}
|
|
|
|
|
|
|
|
delEnv := args[0]
|
|
|
|
|
2017-05-31 02:13:43 +02:00
|
|
|
if !validWorkspaceName(delEnv) {
|
2017-03-27 22:16:09 +02:00
|
|
|
c.Ui.Error(fmt.Sprintf(envInvalidName, delEnv))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-02-23 20:14:51 +01:00
|
|
|
configPath, err := ModulePath(args[1:])
|
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-03-28 00:31:05 +02:00
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
|
|
|
backendConfig, backendDiags := c.loadBackendConfig(configPath)
|
|
|
|
diags = diags.Append(backendDiags)
|
|
|
|
if diags.HasErrors() {
|
|
|
|
c.showDiagnostics(diags)
|
2017-05-01 23:47:53 +02:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-02-23 19:13:28 +01:00
|
|
|
// Load the backend
|
2018-03-28 00:31:05 +02:00
|
|
|
b, backendDiags := c.Backend(&BackendOpts{
|
|
|
|
Config: backendConfig,
|
2017-05-01 23:47:53 +02:00
|
|
|
})
|
2018-03-28 00:31:05 +02:00
|
|
|
diags = diags.Append(backendDiags)
|
|
|
|
if backendDiags.HasErrors() {
|
|
|
|
c.showDiagnostics(diags)
|
2017-02-23 19:13:28 +01:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-02-28 19:13:03 +01:00
|
|
|
states, err := b.States()
|
2017-02-23 19:13:28 +01:00
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
exists := false
|
|
|
|
for _, s := range states {
|
|
|
|
if delEnv == s {
|
|
|
|
exists = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !exists {
|
2017-03-01 21:59:40 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), delEnv))
|
2017-02-23 19:13:28 +01:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-05-31 02:13:43 +02:00
|
|
|
if delEnv == c.Workspace() {
|
2017-03-01 21:59:40 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDelCurrent), delEnv))
|
2017-02-28 19:13:03 +01:00
|
|
|
return 1
|
2017-02-23 19:13:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// we need the actual state to see if it's empty
|
2017-02-28 19:13:03 +01:00
|
|
|
sMgr, err := b.State(delEnv)
|
2017-02-23 19:13:28 +01:00
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-02-23 17:28:47 +01:00
|
|
|
var stateLocker clistate.Locker
|
|
|
|
if c.stateLock {
|
|
|
|
stateLocker = clistate.NewLocker(context.Background(), c.stateLockTimeout, c.Ui, c.Colorize())
|
|
|
|
if err := stateLocker.Lock(sMgr, "workspace_delete"); err != nil {
|
|
|
|
c.Ui.Error(fmt.Sprintf("Error locking state: %s", err))
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
stateLocker = clistate.NewNoopLocker()
|
|
|
|
}
|
|
|
|
|
2017-02-23 19:13:28 +01:00
|
|
|
if err := sMgr.RefreshState(); err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2017-02-24 15:26:14 +01:00
|
|
|
hasResources := sMgr.State().HasResources()
|
2017-02-23 19:13:28 +01:00
|
|
|
|
2017-02-24 15:26:14 +01:00
|
|
|
if hasResources && !force {
|
2017-03-01 21:59:40 +01:00
|
|
|
c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envNotEmpty), delEnv))
|
2017-02-23 19:13:28 +01:00
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
2018-02-23 17:28:47 +01:00
|
|
|
// We need to release the lock just before deleting the state, in case
|
|
|
|
// the backend can't remove the resource while holding the lock. This
|
|
|
|
// is currently true for Windows local files.
|
|
|
|
//
|
|
|
|
// TODO: While there is little safety in locking while deleting the
|
|
|
|
// state, it might be nice to be able to coordinate processes around
|
|
|
|
// state deletion, i.e. in a CI environment. Adding Delete() as a
|
|
|
|
// required method of States would allow the removal of the resource to
|
|
|
|
// be delegated from the Backend to the State itself.
|
|
|
|
stateLocker.Unlock(nil)
|
2017-02-23 19:13:28 +01:00
|
|
|
|
2017-02-28 19:13:03 +01:00
|
|
|
err = b.DeleteState(delEnv)
|
2017-02-23 19:13:28 +01:00
|
|
|
if err != nil {
|
|
|
|
c.Ui.Error(err.Error())
|
|
|
|
return 1
|
|
|
|
}
|
|
|
|
|
|
|
|
c.Ui.Output(
|
|
|
|
c.Colorize().Color(
|
|
|
|
fmt.Sprintf(envDeleted, delEnv),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
|
2017-02-24 15:26:14 +01:00
|
|
|
if hasResources {
|
2017-02-23 19:13:28 +01:00
|
|
|
c.Ui.Output(
|
|
|
|
c.Colorize().Color(
|
|
|
|
fmt.Sprintf(envWarnNotEmpty, delEnv),
|
|
|
|
),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0
|
|
|
|
}
|
2017-09-26 04:02:12 +02:00
|
|
|
|
|
|
|
func (c *WorkspaceDeleteCommand) AutocompleteArgs() complete.Predictor {
|
|
|
|
return completePredictSequence{
|
|
|
|
complete.PredictNothing, // the "select" subcommand itself (already matched)
|
|
|
|
c.completePredictWorkspaceName(),
|
|
|
|
complete.PredictDirs(""),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags {
|
|
|
|
return complete.Flags{
|
|
|
|
"-force": complete.PredictNothing,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
func (c *WorkspaceDeleteCommand) Help() string {
|
2017-02-23 19:13:28 +01:00
|
|
|
helpText := `
|
2017-05-31 00:06:13 +02:00
|
|
|
Usage: terraform workspace delete [OPTIONS] NAME [DIR]
|
2017-02-23 19:13:28 +01:00
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
Delete a Terraform workspace
|
2017-02-23 19:13:28 +01:00
|
|
|
|
|
|
|
|
|
|
|
Options:
|
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
-force remove a non-empty workspace.
|
2017-02-23 19:13:28 +01:00
|
|
|
`
|
|
|
|
return strings.TrimSpace(helpText)
|
|
|
|
}
|
|
|
|
|
2017-05-31 00:06:13 +02:00
|
|
|
func (c *WorkspaceDeleteCommand) Synopsis() string {
|
|
|
|
return "Delete a workspace"
|
2017-02-23 19:13:28 +01:00
|
|
|
}
|