github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/command/workspace_delete.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/iaas-resource-provision/iaas-rpc/internal/command/arguments" 9 "github.com/iaas-resource-provision/iaas-rpc/internal/command/clistate" 10 "github.com/iaas-resource-provision/iaas-rpc/internal/command/views" 11 "github.com/iaas-resource-provision/iaas-rpc/internal/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 }