github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/workspace_delete.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/muratcelep/terraform/not-internal/command/arguments" 9 "github.com/muratcelep/terraform/not-internal/command/clistate" 10 "github.com/muratcelep/terraform/not-internal/command/views" 11 "github.com/muratcelep/terraform/not-internal/states" 12 "github.com/muratcelep/terraform/not-internal/tfdiags" 13 "github.com/mitchellh/cli" 14 "github.com/posener/complete" 15 ) 16 17 type WorkspaceDeleteCommand struct { 18 Meta 19 LegacyName bool 20 } 21 22 func (c *WorkspaceDeleteCommand) Run(args []string) int { 23 args = c.Meta.process(args) 24 envCommandShowWarning(c.Ui, c.LegacyName) 25 26 var force bool 27 var stateLock bool 28 var stateLockTimeout time.Duration 29 cmdFlags := c.Meta.defaultFlagSet("workspace delete") 30 cmdFlags.BoolVar(&force, "force", false, "force removal of a non-empty workspace") 31 cmdFlags.BoolVar(&stateLock, "lock", true, "lock state") 32 cmdFlags.DurationVar(&stateLockTimeout, "lock-timeout", 0, "lock timeout") 33 cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } 34 if err := cmdFlags.Parse(args); err != nil { 35 c.Ui.Error(fmt.Sprintf("Error parsing command-line flags: %s\n", err.Error())) 36 return 1 37 } 38 39 args = cmdFlags.Args() 40 if len(args) != 1 { 41 c.Ui.Error("Expected a single argument: NAME.\n") 42 return cli.RunResultHelp 43 } 44 45 configPath, err := ModulePath(args[1:]) 46 if err != nil { 47 c.Ui.Error(err.Error()) 48 return 1 49 } 50 51 var diags tfdiags.Diagnostics 52 53 backendConfig, backendDiags := c.loadBackendConfig(configPath) 54 diags = diags.Append(backendDiags) 55 if diags.HasErrors() { 56 c.showDiagnostics(diags) 57 return 1 58 } 59 60 // Load the backend 61 b, backendDiags := c.Backend(&BackendOpts{ 62 Config: backendConfig, 63 }) 64 diags = diags.Append(backendDiags) 65 if backendDiags.HasErrors() { 66 c.showDiagnostics(diags) 67 return 1 68 } 69 70 // This command will not write state 71 c.ignoreRemoteVersionConflict(b) 72 73 workspaces, err := b.Workspaces() 74 if err != nil { 75 c.Ui.Error(err.Error()) 76 return 1 77 } 78 79 workspace := args[0] 80 exists := false 81 for _, ws := range workspaces { 82 if workspace == ws { 83 exists = true 84 break 85 } 86 } 87 88 if !exists { 89 c.Ui.Error(fmt.Sprintf(strings.TrimSpace(envDoesNotExist), workspace)) 90 return 1 91 } 92 93 currentWorkspace, err := c.Workspace() 94 if err != nil { 95 c.Ui.Error(fmt.Sprintf("Error selecting workspace: %s", err)) 96 return 1 97 } 98 if workspace == currentWorkspace { 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(c.stateLockTimeout, views.NewStateLocker(arguments.ViewHuman, c.View)) 113 if diags := stateLocker.Lock(stateMgr, "state-replace-provider"); diags.HasErrors() { 114 c.showDiagnostics(diags) 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() 124 c.Ui.Error(err.Error()) 125 return 1 126 } 127 128 hasResources := stateMgr.State().HasManagedResourceInstanceObjects() 129 130 if hasResources && !force { 131 // We'll collect a list of what's being managed here as extra context 132 // for the message. 133 var buf strings.Builder 134 for _, obj := range stateMgr.State().AllResourceInstanceObjectAddrs() { 135 if obj.DeposedKey == states.NotDeposed { 136 fmt.Fprintf(&buf, "\n - %s", obj.Instance.String()) 137 } else { 138 fmt.Fprintf(&buf, "\n - %s (deposed object %s)", obj.Instance.String(), obj.DeposedKey) 139 } 140 } 141 142 // We need to release the lock before exit 143 stateLocker.Unlock() 144 145 diags = diags.Append(tfdiags.Sourceless( 146 tfdiags.Error, 147 "Workspace is not empty", 148 fmt.Sprintf( 149 "Workspace %q is currently tracking the following resource instances:%s\n\nDeleting this workspace would cause Terraform to lose track of any associated remote objects, which would then require you to delete them manually outside of Terraform. You should destroy these objects with Terraform before deleting the workspace.\n\nIf you want to delete this workspace anyway, and have Terraform forget about these managed objects, use the -force option to disable this safety check.", 150 workspace, buf.String(), 151 ), 152 )) 153 c.showDiagnostics(diags) 154 return 1 155 } 156 157 // We need to release the lock just before deleting the state, in case 158 // the backend can't remove the resource while holding the lock. This 159 // is currently true for Windows local files. 160 // 161 // TODO: While there is little safety in locking while deleting the 162 // state, it might be nice to be able to coordinate processes around 163 // state deletion, i.e. in a CI environment. Adding Delete() as a 164 // required method of States would allow the removal of the resource to 165 // be delegated from the Backend to the State itself. 166 stateLocker.Unlock() 167 168 err = b.DeleteWorkspace(workspace) 169 if err != nil { 170 c.Ui.Error(err.Error()) 171 return 1 172 } 173 174 c.Ui.Output( 175 c.Colorize().Color( 176 fmt.Sprintf(envDeleted, workspace), 177 ), 178 ) 179 180 if hasResources { 181 c.Ui.Output( 182 c.Colorize().Color( 183 fmt.Sprintf(envWarnNotEmpty, workspace), 184 ), 185 ) 186 } 187 188 return 0 189 } 190 191 func (c *WorkspaceDeleteCommand) AutocompleteArgs() complete.Predictor { 192 return completePredictSequence{ 193 complete.PredictNothing, // the "select" subcommand itself (already matched) 194 c.completePredictWorkspaceName(), 195 complete.PredictDirs(""), 196 } 197 } 198 199 func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags { 200 return complete.Flags{ 201 "-force": complete.PredictNothing, 202 } 203 } 204 205 func (c *WorkspaceDeleteCommand) Help() string { 206 helpText := ` 207 Usage: terraform [global options] workspace delete [OPTIONS] NAME 208 209 Delete a Terraform workspace 210 211 212 Options: 213 214 -force Remove even a non-empty workspace. 215 216 -lock=false Don't hold a state lock during the operation. This is 217 dangerous if others might concurrently run commands 218 against the same workspace. 219 220 -lock-timeout=0s Duration to retry a state lock. 221 222 ` 223 return strings.TrimSpace(helpText) 224 } 225 226 func (c *WorkspaceDeleteCommand) Synopsis() string { 227 return "Delete a workspace" 228 }