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 }