Skip to content

Commit

Permalink
Merge pull request #31949 from JarrettSpiker/jspiker/safe-delete-work…
Browse files Browse the repository at this point in the history
…space

Use safe or force workspace delete for cloud backend
  • Loading branch information
brandonc committed Nov 21, 2022
2 parents c096443 + 0ae6eff commit 401fa66
Show file tree
Hide file tree
Showing 29 changed files with 244 additions and 54 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ ENHANCEMENTS:
* backend/gcs: Add `storage_custom_endpoint` argument, to allow communication with the backend via a Private Service Connect endpoint. ([#28856](https://github.com/hashicorp/terraform/issues/28856))
* backend/gcs: Update documentation for usage of `gcs` with `terraform_remote_state` ([#32065](https://github.com/hashicorp/terraform/issues/32065))
* backed/gcs: Update storage package to v1.28.0 ([#29656](https://github.com/hashicorp/terraform/issues/29656))
* When removing a workspace from the `cloud` backend `terraform workspace delete` will use Terraform Cloud's [Safe Delete](https://developer.hashicorp.com/terraform/cloud-docs/api-docs/workspaces#safe-delete-a-workspace) API if the `-force` flag is not provided. ([#31949](https://github.com/hashicorp/terraform/pull/31949))

EXPERIMENTS:

Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-plugin v1.4.3
github.com/hashicorp/go-retryablehttp v0.7.1
github.com/hashicorp/go-tfe v1.10.0
github.com/hashicorp/go-tfe v1.12.0
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.6.0
github.com/hashicorp/hcl v0.0.0-20170504190234-a4b07c25de5f
Expand Down Expand Up @@ -175,7 +175,7 @@ require (
github.com/vmihailenco/tagparser v0.1.1 // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 // indirect
golang.org/x/time v0.1.0 // indirect
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
Expand Down
10 changes: 5 additions & 5 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -376,8 +376,8 @@ github.com/hashicorp/go-slug v0.10.0/go.mod h1:Ib+IWBYfEfJGI1ZyXMGNbu2BU+aa3Dzu4
github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-tfe v1.10.0 h1:mkEge/DSca8VQeBSAQbjEy8fWFHbrJA76M7dny5XlYc=
github.com/hashicorp/go-tfe v1.10.0/go.mod h1:uSWi2sPw7tLrqNIiASid9j3SprbbkPSJ/2s3X0mMemg=
github.com/hashicorp/go-tfe v1.12.0 h1:2l7emKW8rNTTbnxYHNVj6b46iJzOEp2G/3xIHfGSDnc=
github.com/hashicorp/go-tfe v1.12.0/go.mod h1:thYtIxtgBpDDNdf/2yYPdBJ94Fz5yT5XCNZvGtTGHAU=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
Expand Down Expand Up @@ -602,7 +602,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.194/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232 h1:kwsWbh4rEw42ZDe9/812ebhbwNZxlQyZ2sTmxBOKhN4=
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.232/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y=
Expand Down Expand Up @@ -861,8 +861,8 @@ golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxb
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9 h1:ftMN5LMiBFjbzleLqtoBZk7KdJwhuybIU+FckUHgoyQ=
golang.org/x/time v0.0.0-20220722155302-e5dcc9cfc0b9/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.1.0 h1:xYY+Bajn2a7VBmTM5GikTmnK8ZuX8YgnQCqZpbBNtmA=
golang.org/x/time v0.1.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ type Backend interface {
// DeleteWorkspace cannot prevent deleting a state that is in use. It is
// the responsibility of the caller to hold a Lock for the state manager
// belonging to this workspace before calling this method.
DeleteWorkspace(name string) error
DeleteWorkspace(name string, force bool) error

// States returns a list of the names of all of the workspaces that exist
// in this backend.
Expand Down
4 changes: 2 additions & 2 deletions internal/backend/local/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,10 +214,10 @@ func (b *Local) Workspaces() ([]string, error) {
// DeleteWorkspace removes a workspace.
//
// The "default" workspace cannot be removed.
func (b *Local) DeleteWorkspace(name string) error {
func (b *Local) DeleteWorkspace(name string, force bool) error {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.DeleteWorkspace(name)
return b.Backend.DeleteWorkspace(name, force)
}

if name == "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/local/backend_local_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ func (b backendWithStateStorageThatFailsRefresh) Configure(cty.Value) tfdiags.Di
return nil
}

func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string) error {
func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string, force bool) error {
return fmt.Errorf("unimplemented")
}

Expand Down
10 changes: 5 additions & 5 deletions internal/backend/local/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}

if err := b.DeleteWorkspace(expectedA); err != nil {
if err := b.DeleteWorkspace(expectedA, true); err != nil {
t.Fatal(err)
}

Expand All @@ -147,7 +147,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}

if err := b.DeleteWorkspace(expectedB); err != nil {
if err := b.DeleteWorkspace(expectedB, true); err != nil {
t.Fatal(err)
}

Expand All @@ -161,7 +161,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}

if err := b.DeleteWorkspace(dflt); err == nil {
if err := b.DeleteWorkspace(dflt, true); err == nil {
t.Fatal("expected error deleting default state")
}
}
Expand Down Expand Up @@ -196,7 +196,7 @@ func (b *testDelegateBackend) Workspaces() ([]string, error) {
return []string{"default"}, nil
}

func (b *testDelegateBackend) DeleteWorkspace(name string) error {
func (b *testDelegateBackend) DeleteWorkspace(name string, force bool) error {
if b.deleteErr {
return errTestDelegateDeleteState
}
Expand All @@ -220,7 +220,7 @@ func TestLocal_multiStateBackend(t *testing.T) {
t.Fatal("expected errTestDelegateStates, got:", err)
}

if err := b.DeleteWorkspace("test"); err != errTestDelegateDeleteState {
if err := b.DeleteWorkspace("test", true); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err)
}
}
Expand Down
6 changes: 3 additions & 3 deletions internal/backend/local/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func (b *TestLocalSingleState) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported
}

func (b *TestLocalSingleState) DeleteWorkspace(string) error {
func (b *TestLocalSingleState) DeleteWorkspace(string, bool) error {
return backend.ErrWorkspacesNotSupported
}

Expand Down Expand Up @@ -177,11 +177,11 @@ func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
return filtered, nil
}

func (b *TestLocalNoDefaultState) DeleteWorkspace(name string) error {
func (b *TestLocalNoDefaultState) DeleteWorkspace(name string, force bool) error {
if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
return b.Local.DeleteWorkspace(name)
return b.Local.DeleteWorkspace(name, force)
}

func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/azure/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/consul/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/cos/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func (b *Backend) Workspaces() ([]string, error) {
}

// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
log.Printf("[DEBUG] delete workspace, workspace: %v", name)

if name == backend.DefaultStateName || name == "" {
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/gcs/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func (b *Backend) Workspaces() ([]string, error) {
}

// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName {
return fmt.Errorf("cowardly refusing to delete the %q state", name)
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/http/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,6 @@ func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported
}

func (b *Backend) DeleteWorkspace(string) error {
func (b *Backend) DeleteWorkspace(string, bool) error {
return backend.ErrWorkspacesNotSupported
}
2 changes: 1 addition & 1 deletion internal/backend/remote-state/inmem/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return workspaces, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
states.Lock()
defer states.Unlock()

Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/kubernetes/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return states, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/oss/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/pg/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/s3/backend_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (b *Backend) keyEnv(key string) string {
return parts[0]
}

func (b *Backend) DeleteWorkspace(name string) error {
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote-state/s3/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -537,7 +537,7 @@ func TestBackendExtraPaths(t *testing.T) {
}

// delete the real workspace
if err := b.DeleteWorkspace("s2"); err != nil {
if err := b.DeleteWorkspace("s2", true); err != nil {
t.Fatal(err)
}

Expand Down
2 changes: 1 addition & 1 deletion internal/backend/remote/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -582,7 +582,7 @@ func (b *Remote) WorkspaceNamePattern() string {
}

// DeleteWorkspace implements backend.Enhanced.
func (b *Remote) DeleteWorkspace(name string) error {
func (b *Remote) DeleteWorkspace(name string, _ bool) error {
if b.workspace == "" && name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
Expand Down
10 changes: 5 additions & 5 deletions internal/backend/remote/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,11 +262,11 @@ func TestRemote_addAndRemoveWorkspacesDefault(t *testing.T) {
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
}

if err := b.DeleteWorkspace(backend.DefaultStateName); err != nil {
if err := b.DeleteWorkspace(backend.DefaultStateName, true); err != nil {
t.Fatalf("expected no error, got %v", err)
}

if err := b.DeleteWorkspace("prod"); err != backend.ErrWorkspacesNotSupported {
if err := b.DeleteWorkspace("prod", true); err != backend.ErrWorkspacesNotSupported {
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
}
}
Expand Down Expand Up @@ -319,11 +319,11 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
}

if err := b.DeleteWorkspace(backend.DefaultStateName); err != backend.ErrDefaultWorkspaceNotSupported {
if err := b.DeleteWorkspace(backend.DefaultStateName, true); err != backend.ErrDefaultWorkspaceNotSupported {
t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
}

if err := b.DeleteWorkspace(expectedA); err != nil {
if err := b.DeleteWorkspace(expectedA, true); err != nil {
t.Fatal(err)
}

Expand All @@ -337,7 +337,7 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states)
}

if err := b.DeleteWorkspace(expectedB); err != nil {
if err := b.DeleteWorkspace(expectedB, true); err != nil {
t.Fatal(err)
}

Expand Down
6 changes: 3 additions & 3 deletions internal/backend/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,12 +219,12 @@ func TestBackendStates(t *testing.T, b Backend) {
}

// Delete some workspaces
if err := b.DeleteWorkspace("foo"); err != nil {
if err := b.DeleteWorkspace("foo", true); err != nil {
t.Fatalf("err: %s", err)
}

// Verify the default state can't be deleted
if err := b.DeleteWorkspace(DefaultStateName); err == nil {
if err := b.DeleteWorkspace(DefaultStateName, true); err == nil {
t.Fatal("expected error")
}

Expand All @@ -242,7 +242,7 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v)
}
// and delete it again
if err := b.DeleteWorkspace("foo"); err != nil {
if err := b.DeleteWorkspace("foo", true); err != nil {
t.Fatalf("err: %s", err)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func (b backendFailsConfigure) StateMgr(workspace string) (statemgr.Full, error)
return nil, fmt.Errorf("StateMgr not implemented")
}

func (b backendFailsConfigure) DeleteWorkspace(name string) error {
func (b backendFailsConfigure) DeleteWorkspace(name string, _ bool) error {
return fmt.Errorf("DeleteWorkspace not implemented")
}

Expand Down
17 changes: 12 additions & 5 deletions internal/cloud/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,7 +516,7 @@ func (b *Cloud) Workspaces() ([]string, error) {
}

// DeleteWorkspace implements backend.Enhanced.
func (b *Cloud) DeleteWorkspace(name string) error {
func (b *Cloud) DeleteWorkspace(name string, force bool) error {
if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
Expand All @@ -525,11 +525,18 @@ func (b *Cloud) DeleteWorkspace(name string) error {
return backend.ErrWorkspacesNotSupported
}

workspace, err := b.client.Workspaces.Read(context.Background(), b.organization, name)
if err == tfe.ErrResourceNotFound {
return nil // If the workspace does not exist, succeed
}

if err != nil {
return fmt.Errorf("failed to retrieve workspace %s: %v", name, err)
}

// Configure the remote workspace name.
State := &State{tfeClient: b.client, organization: b.organization, workspace: &tfe.Workspace{
Name: name,
}}
return State.Delete()
State := &State{tfeClient: b.client, organization: b.organization, workspace: workspace}
return State.Delete(force)
}

// StateMgr implements backend.Enhanced.
Expand Down

0 comments on commit 401fa66

Please sign in to comment.