github.com/cycloidio/terraform@v1.1.10-0.20220513142504-76d5c768dc63/command/workspace_delete.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "github.com/cycloidio/terraform/command/arguments" 9 "github.com/cycloidio/terraform/command/clistate" 10 "github.com/cycloidio/terraform/command/views" 11 "github.com/cycloidio/terraform/states" 12 "github.com/cycloidio/terraform/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 c.completePredictWorkspaceName(), 194 complete.PredictDirs(""), 195 } 196 } 197 198 func (c *WorkspaceDeleteCommand) AutocompleteFlags() complete.Flags { 199 return complete.Flags{ 200 "-force": complete.PredictNothing, 201 } 202 } 203 204 func (c *WorkspaceDeleteCommand) Help() string { 205 helpText := ` 206 Usage: terraform [global options] workspace delete [OPTIONS] NAME 207 208 Delete a Terraform workspace 209 210 211 Options: 212 213 -force Remove even a non-empty workspace. 214 215 -lock=false Don't hold a state lock during the operation. This is 216 dangerous if others might concurrently run commands 217 against the same workspace. 218 219 -lock-timeout=0s Duration to retry a state lock. 220 221 ` 222 return strings.TrimSpace(helpText) 223 } 224 225 func (c *WorkspaceDeleteCommand) Synopsis() string { 226 return "Delete a workspace" 227 }