Make backend.DeleteWorkspace accept a context (#782)

Signed-off-by: Marcin Wyszynski <marcin.pixie@gmail.com>
This commit is contained in:
Marcin Wyszynski
2023-10-24 16:55:03 +02:00
committed by GitHub
parent 545e5f0102
commit 925db6dbc5
40 changed files with 127 additions and 104 deletions

View File

@@ -112,7 +112,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, force bool) error
DeleteWorkspace(_ context.Context, name string, force bool) error
// States returns a list of the names of all of the workspaces that exist
// in this backend.

View File

@@ -220,10 +220,10 @@ func (b *Local) Workspaces() ([]string, error) {
// DeleteWorkspace removes a workspace.
//
// The "default" workspace cannot be removed.
func (b *Local) DeleteWorkspace(name string, force bool) error {
func (b *Local) DeleteWorkspace(ctx context.Context, name string, force bool) error {
// If we have a backend handling state, defer to that.
if b.Backend != nil {
return b.Backend.DeleteWorkspace(name, force)
return b.Backend.DeleteWorkspace(ctx, name, force)
}
if name == "" {

View File

@@ -231,7 +231,7 @@ func (b backendWithStateStorageThatFailsRefresh) Configure(context.Context, cty.
return nil
}
func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string, force bool) error {
func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(_ context.Context, name string, force bool) error {
return fmt.Errorf("unimplemented")
}

View File

@@ -139,7 +139,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(expectedA, true); err != nil {
if err := b.DeleteWorkspace(ctx, expectedA, true); err != nil {
t.Fatal(err)
}
@@ -153,7 +153,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(expectedB, true); err != nil {
if err := b.DeleteWorkspace(ctx, expectedB, true); err != nil {
t.Fatal(err)
}
@@ -167,7 +167,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states)
}
if err := b.DeleteWorkspace(dflt, true); err == nil {
if err := b.DeleteWorkspace(ctx, dflt, true); err == nil {
t.Fatal("expected error deleting default state")
}
}
@@ -202,7 +202,7 @@ func (b *testDelegateBackend) Workspaces() ([]string, error) {
return []string{"default"}, nil
}
func (b *testDelegateBackend) DeleteWorkspace(name string, force bool) error {
func (b *testDelegateBackend) DeleteWorkspace(_ context.Context, name string, force bool) error {
if b.deleteErr {
return errTestDelegateDeleteState
}
@@ -218,7 +218,9 @@ func TestLocal_multiStateBackend(t *testing.T) {
deleteErr: true,
})
if _, err := b.StateMgr(context.Background(), "test"); err != errTestDelegateState {
ctx := context.Background()
if _, err := b.StateMgr(ctx, "test"); err != errTestDelegateState {
t.Fatal("expected errTestDelegateState, got:", err)
}
@@ -226,7 +228,7 @@ func TestLocal_multiStateBackend(t *testing.T) {
t.Fatal("expected errTestDelegateStates, got:", err)
}
if err := b.DeleteWorkspace("test", true); err != errTestDelegateDeleteState {
if err := b.DeleteWorkspace(ctx, "test", true); err != errTestDelegateDeleteState {
t.Fatal("expected errTestDelegateDeleteState, got:", err)
}
}

View File

@@ -122,7 +122,7 @@ func (b *TestLocalSingleState) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported
}
func (b *TestLocalSingleState) DeleteWorkspace(string, bool) error {
func (b *TestLocalSingleState) DeleteWorkspace(context.Context, string, bool) error {
return backend.ErrWorkspacesNotSupported
}
@@ -164,11 +164,11 @@ func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
return filtered, nil
}
func (b *TestLocalNoDefaultState) DeleteWorkspace(name string, force bool) error {
func (b *TestLocalNoDefaultState) DeleteWorkspace(ctx context.Context, name string, force bool) error {
if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
return b.Local.DeleteWorkspace(name, force)
return b.Local.DeleteWorkspace(ctx, name, force)
}
func (b *TestLocalNoDefaultState) StateMgr(ctx context.Context, name string) (statemgr.Full, error) {

View File

@@ -61,12 +61,11 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
ctx := context.TODO()
client, err := b.armClient.getBlobClient(ctx)
if err != nil {
return err

View File

@@ -102,14 +102,13 @@ func (c *RemoteClient) Put(data []byte) error {
return err
}
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(ctx context.Context) error {
options := blobs.DeleteInput{}
if c.leaseID != "" {
options.LeaseID = &c.leaseID
}
ctx := context.TODO()
resp, err := c.giovanniBlobClient.Delete(ctx, c.accountName, c.containerName, c.keyName, options)
if err != nil {
if !resp.IsHTTPStatus(http.StatusNotFound) {

View File

@@ -53,7 +53,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(_ context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}

View File

@@ -293,7 +293,7 @@ func (c *RemoteClient) Put(data []byte) error {
return store(payload)
}
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(_ context.Context) error {
c.mu.Lock()
defer c.mu.Unlock()

View File

@@ -235,7 +235,7 @@ func TestConsul_largeState(t *testing.T) {
)
// Deleting the state should remove all chunks
err = c.Delete()
err = c.Delete(ctx)
if err != nil {
t.Fatal(err)
}

View File

@@ -61,7 +61,7 @@ func (b *Backend) Workspaces() ([]string, error) {
}
// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
log.Printf("[DEBUG] delete workspace, workspace: %v", name)
if name == backend.DefaultStateName || name == "" {
@@ -73,7 +73,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err
}
return c.Delete()
return c.Delete(ctx)
}
// StateMgr manage the state, if the named state not exists, a new file will created

View File

@@ -28,9 +28,11 @@ const (
// RemoteClient implements the client of remote state
type remoteClient struct {
// TODO: remove once all methods are using context passed via the CLI.
cosContext context.Context
cosClient *cos.Client
tagClient *tag.Client
cosClient *cos.Client
tagClient *tag.Client
bucket string
stateFile string
@@ -68,10 +70,10 @@ func (c *remoteClient) Put(data []byte) error {
}
// Delete delete remote state file
func (c *remoteClient) Delete() error {
func (c *remoteClient) Delete(ctx context.Context) error {
log.Printf("[DEBUG] delete remote state file %s", c.stateFile)
return c.deleteObject(c.stateFile)
return c.deleteObject(ctx, c.stateFile)
}
// Lock lock remote state file for writing
@@ -121,7 +123,7 @@ func (c *remoteClient) Unlock(check string) error {
return c.lockError(fmt.Errorf("lock id mismatch, %v != %v", info.ID, check))
}
err = c.deleteObject(c.lockFile)
err = c.deleteObject(c.cosContext, c.lockFile)
if err != nil {
return c.lockError(err)
}
@@ -252,8 +254,8 @@ func (c *remoteClient) putObject(cosFile string, data []byte) error {
}
// deleteObject delete remote object
func (c *remoteClient) deleteObject(cosFile string) error {
rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile)
func (c *remoteClient) deleteObject(ctx context.Context, cosFile string) error {
rsp, err := c.cosClient.Object.Delete(ctx, cosFile)
if rsp == nil {
log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err)
return fmt.Errorf("failed to delete file %v: %w", cosFile, err)
@@ -328,7 +330,7 @@ func (c *remoteClient) deleteBucket(recursive bool) error {
return fmt.Errorf("failed to empty bucket %v: %w", c.bucket, err)
}
for _, v := range obs {
c.deleteObject(v.Key)
c.deleteObject(c.cosContext, v.Key)
}
}

View File

@@ -59,7 +59,7 @@ func (b *Backend) Workspaces() ([]string, error) {
}
// DeleteWorkspace deletes the named workspaces. The "default" state cannot be deleted.
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName {
return fmt.Errorf("cowardly refusing to delete the %q state", name)
}
@@ -69,7 +69,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err
}
return c.Delete()
return c.Delete(ctx)
}
// client returns a remoteClient for the named state.

View File

@@ -20,13 +20,15 @@ import (
// blobs representing state.
// Implements "state/remote".ClientLocker
type remoteClient struct {
// TODO: remove this once all methods are accepting an explicit context
storageContext context.Context
storageClient *storage.Client
bucketName string
stateFilePath string
lockFilePath string
encryptionKey []byte
kmsKeyName string
storageClient *storage.Client
bucketName string
stateFilePath string
lockFilePath string
encryptionKey []byte
kmsKeyName string
}
func (c *remoteClient) Get() (payload *remote.Payload, err error) {
@@ -76,8 +78,8 @@ func (c *remoteClient) Put(data []byte) error {
return nil
}
func (c *remoteClient) Delete() error {
if err := c.stateFile().Delete(c.storageContext); err != nil {
func (c *remoteClient) Delete(ctx context.Context) error {
if err := c.stateFile().Delete(ctx); err != nil {
return fmt.Errorf("Failed to delete state file %v: %w", c.stateFileURL(), err)
}

View File

@@ -255,6 +255,6 @@ func (b *Backend) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported
}
func (b *Backend) DeleteWorkspace(string, bool) error {
func (b *Backend) DeleteWorkspace(context.Context, string, bool) error {
return backend.ErrWorkspacesNotSupported
}

View File

@@ -5,6 +5,7 @@ package http
import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"encoding/json"
@@ -39,7 +40,7 @@ type httpClient struct {
jsonLockInfo []byte
}
func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what string) (*http.Response, error) {
func (c *httpClient) httpRequest(ctx context.Context, method string, url *url.URL, data *[]byte, what string) (*http.Response, error) {
// If we have data we need a reader
var reader io.Reader = nil
if data != nil {
@@ -47,7 +48,7 @@ func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what
}
// Create the request
req, err := retryablehttp.NewRequest(method, url.String(), reader)
req, err := retryablehttp.NewRequestWithContext(ctx, method, url.String(), reader)
if err != nil {
return nil, fmt.Errorf("Failed to make %s HTTP request: %w", what, err)
}
@@ -83,7 +84,7 @@ func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
c.lockID = ""
jsonLockInfo := info.Marshal()
resp, err := c.httpRequest(c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
resp, err := c.httpRequest(context.TODO(), c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
if err != nil {
return "", err
}
@@ -129,7 +130,7 @@ func (c *httpClient) Unlock(id string) error {
return nil
}
resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
resp, err := c.httpRequest(context.TODO(), c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
if err != nil {
return err
}
@@ -144,7 +145,7 @@ func (c *httpClient) Unlock(id string) error {
}
func (c *httpClient) Get() (*remote.Payload, error) {
resp, err := c.httpRequest("GET", c.URL, nil, "get state")
resp, err := c.httpRequest(context.TODO(), "GET", c.URL, nil, "get state")
if err != nil {
return nil, err
}
@@ -225,7 +226,7 @@ func (c *httpClient) Put(data []byte) error {
if c.UpdateMethod != "" {
method = c.UpdateMethod
}
resp, err := c.httpRequest(method, &base, &data, "upload state")
resp, err := c.httpRequest(context.TODO(), method, &base, &data, "upload state")
if err != nil {
return err
}
@@ -240,9 +241,9 @@ func (c *httpClient) Put(data []byte) error {
}
}
func (c *httpClient) Delete() error {
func (c *httpClient) Delete(ctx context.Context) error {
// Make the request
resp, err := c.httpRequest("DELETE", c.URL, nil, "delete state")
resp, err := c.httpRequest(ctx, "DELETE", c.URL, nil, "delete state")
if err != nil {
return err
}

View File

@@ -104,7 +104,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return workspaces, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(_ context.Context, name string, _ bool) error {
states.Lock()
defer states.Unlock()

View File

@@ -4,6 +4,7 @@
package inmem
import (
"context"
"crypto/md5"
"github.com/opentofu/opentofu/internal/states/remote"
@@ -36,7 +37,7 @@ func (c *RemoteClient) Put(data []byte) error {
return nil
}
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(context.Context) error {
c.Data = nil
c.MD5 = nil
return nil

View File

@@ -63,7 +63,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return states, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
@@ -73,7 +73,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err
}
return client.Delete()
return client.Delete(ctx)
}
func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, error) {

View File

@@ -122,7 +122,7 @@ func (c *RemoteClient) Put(data []byte) error {
}
// Delete the state secret
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(context.Context) error {
secretName, err := c.createSecretName()
if err != nil {
return err

View File

@@ -100,7 +100,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
@@ -109,7 +109,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
if err != nil {
return err
}
return client.Delete()
return client.Delete(ctx)
}
func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, error) {

View File

@@ -5,6 +5,7 @@ package oss
import (
"bytes"
"context"
"crypto/md5"
"encoding/hex"
"encoding/json"
@@ -129,7 +130,7 @@ func (c *RemoteClient) Put(data []byte) error {
return nil
}
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(ctx context.Context) error {
bucket, err := c.ossClient.Bucket(c.bucketName)
if err != nil {
return fmt.Errorf("error getting bucket %s: %w", c.bucketName, err)

View File

@@ -39,13 +39,13 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
query := `DELETE FROM %s.%s WHERE name = $1`
_, err := b.db.Exec(fmt.Sprintf(query, b.schemaName, statesTableName), name)
_, err := b.db.ExecContext(ctx, fmt.Sprintf(query, b.schemaName, statesTableName), name)
if err != nil {
return err
}

View File

@@ -4,6 +4,7 @@
package pg
import (
"context"
"crypto/md5"
"database/sql"
"fmt"
@@ -54,9 +55,9 @@ func (c *RemoteClient) Put(data []byte) error {
return nil
}
func (c *RemoteClient) Delete() error {
func (c *RemoteClient) Delete(ctx context.Context) error {
query := `DELETE FROM %s.%s WHERE name = $1`
_, err := c.Client.Exec(fmt.Sprintf(query, c.SchemaName, statesTableName), c.Name)
_, err := c.Client.ExecContext(ctx, fmt.Sprintf(query, c.SchemaName, statesTableName), c.Name)
if err != nil {
return err
}

View File

@@ -102,7 +102,7 @@ func (b *Backend) keyEnv(key string) string {
return parts[0]
}
func (b *Backend) DeleteWorkspace(name string, _ bool) error {
func (b *Backend) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state")
}
@@ -112,7 +112,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err
}
return client.Delete()
return client.Delete(ctx)
}
// get a remote client configured for this state

View File

@@ -1107,12 +1107,12 @@ func TestBackendExtraPaths(t *testing.T) {
}
// remove the state with extra subkey
if err := client.Delete(); err != nil {
if err := client.Delete(ctx); err != nil {
t.Fatal(err)
}
// delete the real workspace
if err := b.DeleteWorkspace("s2", true); err != nil {
if err := b.DeleteWorkspace(ctx, "s2", true); err != nil {
t.Fatal(err)
}

View File

@@ -202,8 +202,7 @@ func (c *RemoteClient) Put(data []byte) error {
return nil
}
func (c *RemoteClient) Delete() error {
ctx := context.TODO()
func (c *RemoteClient) Delete(ctx context.Context) error {
_, err := c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: &c.bucketName,
Key: &c.path,

View File

@@ -609,7 +609,7 @@ func (b *Remote) WorkspaceNamePattern() string {
}
// DeleteWorkspace implements backend.Enhanced.
func (b *Remote) DeleteWorkspace(name string, _ bool) error {
func (b *Remote) DeleteWorkspace(ctx context.Context, name string, _ bool) error {
if b.workspace == "" && name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
@@ -633,7 +633,7 @@ func (b *Remote) DeleteWorkspace(name string, _ bool) error {
},
}
return client.Delete()
return client.Delete(ctx)
}
// StateMgr implements backend.Enhanced.

View File

@@ -141,8 +141,8 @@ func (r *remoteClient) Put(state []byte) error {
}
// Delete the remote state.
func (r *remoteClient) Delete() error {
err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name)
func (r *remoteClient) Delete(ctx context.Context) error {
err := r.client.Workspaces.Delete(ctx, r.organization, r.workspace.Name)
if err != nil && err != tfe.ErrResourceNotFound {
return fmt.Errorf("error deleting workspace %s: %w", r.workspace.Name, err)
}

View File

@@ -283,11 +283,11 @@ func TestRemote_addAndRemoveWorkspacesDefault(t *testing.T) {
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
}
if err := b.DeleteWorkspace(backend.DefaultStateName, true); err != nil {
if err := b.DeleteWorkspace(ctx, backend.DefaultStateName, true); err != nil {
t.Fatalf("expected no error, got %v", err)
}
if err := b.DeleteWorkspace("prod", true); err != backend.ErrWorkspacesNotSupported {
if err := b.DeleteWorkspace(ctx, "prod", true); err != backend.ErrWorkspacesNotSupported {
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
}
}
@@ -342,11 +342,11 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
t.Fatalf("expected %#+v, got %#+v", expectedWorkspaces, states)
}
if err := b.DeleteWorkspace(backend.DefaultStateName, true); err != backend.ErrDefaultWorkspaceNotSupported {
if err := b.DeleteWorkspace(ctx, backend.DefaultStateName, true); err != backend.ErrDefaultWorkspaceNotSupported {
t.Fatalf("expected error %v, got %v", backend.ErrDefaultWorkspaceNotSupported, err)
}
if err := b.DeleteWorkspace(expectedA, true); err != nil {
if err := b.DeleteWorkspace(ctx, expectedA, true); err != nil {
t.Fatal(err)
}
@@ -360,7 +360,7 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states)
}
if err := b.DeleteWorkspace(expectedB, true); err != nil {
if err := b.DeleteWorkspace(ctx, expectedB, true); err != nil {
t.Fatal(err)
}

View File

@@ -227,12 +227,12 @@ func TestBackendStates(t *testing.T, b Backend) {
}
// Delete some workspaces
if err := b.DeleteWorkspace("foo", true); err != nil {
if err := b.DeleteWorkspace(ctx, "foo", true); err != nil {
t.Fatalf("err: %s", err)
}
// Verify the default state can't be deleted
if err := b.DeleteWorkspace(DefaultStateName, true); err == nil {
if err := b.DeleteWorkspace(ctx, DefaultStateName, true); err == nil {
t.Fatal("expected error")
}
@@ -250,7 +250,7 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v)
}
// and delete it again
if err := b.DeleteWorkspace("foo", true); err != nil {
if err := b.DeleteWorkspace(ctx, "foo", true); err != nil {
t.Fatalf("err: %s", err)
}

View File

@@ -366,7 +366,7 @@ func (b backendFailsConfigure) StateMgr(context.Context, string) (statemgr.Full,
return nil, fmt.Errorf("StateMgr not implemented")
}
func (b backendFailsConfigure) DeleteWorkspace(name string, _ bool) error {
func (b backendFailsConfigure) DeleteWorkspace(_ context.Context, name string, _ bool) error {
return fmt.Errorf("DeleteWorkspace not implemented")
}

View File

@@ -592,7 +592,7 @@ func (b *Cloud) Workspaces() ([]string, error) {
}
// DeleteWorkspace implements backend.Enhanced.
func (b *Cloud) DeleteWorkspace(name string, force bool) error {
func (b *Cloud) DeleteWorkspace(ctx context.Context, name string, force bool) error {
if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported
}
@@ -601,7 +601,7 @@ func (b *Cloud) DeleteWorkspace(name string, force bool) error {
return backend.ErrWorkspacesNotSupported
}
workspace, err := b.client.Workspaces.Read(context.Background(), b.organization, name)
workspace, err := b.client.Workspaces.Read(ctx, b.organization, name)
if err == tfe.ErrResourceNotFound {
return nil // If the workspace does not exist, succeed
}
@@ -612,7 +612,7 @@ func (b *Cloud) DeleteWorkspace(name string, force bool) error {
// Configure the remote workspace name.
State := &State{tfeClient: b.client, organization: b.organization, workspace: workspace, enableIntermediateSnapshots: false}
return State.Delete(force)
return State.Delete(ctx, force)
}
// StateMgr implements backend.Enhanced.

View File

@@ -45,11 +45,11 @@ func TestCloud_backendWithName(t *testing.T) {
t.Fatalf("expected fetching a state which is NOT the single configured workspace to have an ErrWorkspacesNotSupported error, but got: %v", err)
}
if err := b.DeleteWorkspace(testBackendSingleWorkspaceName, true); err != backend.ErrWorkspacesNotSupported {
if err := b.DeleteWorkspace(ctx, testBackendSingleWorkspaceName, true); err != backend.ErrWorkspacesNotSupported {
t.Fatalf("expected deleting the single configured workspace name to result in an error, but got: %v", err)
}
if err := b.DeleteWorkspace("foo", true); err != backend.ErrWorkspacesNotSupported {
if err := b.DeleteWorkspace(ctx, "foo", true); err != backend.ErrWorkspacesNotSupported {
t.Fatalf("expected deleting a workspace which is NOT the configured workspace name to result in an error, but got: %v", err)
}
}
@@ -1071,7 +1071,7 @@ func TestCloud_addAndRemoveWorkspacesDefault(t *testing.T) {
t.Fatalf("expected no error, got %v", err)
}
if err := b.DeleteWorkspace(testBackendSingleWorkspaceName, true); err != backend.ErrWorkspacesNotSupported {
if err := b.DeleteWorkspace(ctx, testBackendSingleWorkspaceName, true); err != backend.ErrWorkspacesNotSupported {
t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
}
}
@@ -1396,7 +1396,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil {
t.Fatalf("error locking workspace: %v", err)
}
err = b.DeleteWorkspace(safeDeleteWorkspaceName, false)
err = b.DeleteWorkspace(ctx, safeDeleteWorkspaceName, false)
if err == nil {
t.Fatalf("workspace should have failed to safe delete")
}
@@ -1406,7 +1406,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil {
t.Fatalf("error unlocking workspace: %v", err)
}
err = b.DeleteWorkspace(safeDeleteWorkspaceName, false)
err = b.DeleteWorkspace(ctx, safeDeleteWorkspaceName, false)
if err != nil {
t.Fatalf("error safe deleting workspace: %v", err)
}
@@ -1420,7 +1420,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil {
t.Fatalf("error locking workspace: %v", err)
}
err = b.DeleteWorkspace(forceDeleteWorkspaceName, true)
err = b.DeleteWorkspace(ctx, forceDeleteWorkspaceName, true)
if err != nil {
t.Fatalf("error force deleting workspace: %v", err)
}
@@ -1430,7 +1430,9 @@ func TestCloudBackend_DeleteWorkspace_DoesNotExist(t *testing.T) {
b, bCleanup := testBackendWithTags(t)
defer bCleanup()
err := b.DeleteWorkspace("non-existent-workspace", false)
ctx := context.Background()
err := b.DeleteWorkspace(ctx, "non-existent-workspace", false)
if err != nil {
t.Fatalf("expected deleting a workspace which does not exist to succeed")
}

View File

@@ -497,15 +497,15 @@ func (s *State) Unlock(id string) error {
}
// Delete the remote state.
func (s *State) Delete(force bool) error {
func (s *State) Delete(ctx context.Context, force bool) error {
var err error
isSafeDeleteSupported := s.workspace.Permissions.CanForceDelete != nil
if force || !isSafeDeleteSupported {
err = s.tfeClient.Workspaces.Delete(context.Background(), s.organization, s.workspace.Name)
err = s.tfeClient.Workspaces.Delete(ctx, s.organization, s.workspace.Name)
} else {
err = s.tfeClient.Workspaces.SafeDelete(context.Background(), s.organization, s.workspace.Name)
err = s.tfeClient.Workspaces.SafeDelete(ctx, s.organization, s.workspace.Name)
}
if err != nil && err != tfe.ErrResourceNotFound {

View File

@@ -127,7 +127,9 @@ func TestState(t *testing.T) {
t.Fatalf("expected full state %q\n\ngot: %q", string(payload.Data), string(data))
}
if err := state.Delete(true); err != nil {
ctx := context.Background()
if err := state.Delete(ctx, true); err != nil {
t.Fatalf("delete: %s", err)
}
@@ -211,9 +213,11 @@ func TestDelete_SafeDeleteNotSupported(t *testing.T) {
state.workspace.Permissions.CanForceDelete = nil
state.workspace.ResourceCount = 5
ctx := context.Background()
// Typically delete(false) should safe-delete a cloud workspace, which should fail on this workspace with resources
// However, since we have set the workspace canForceDelete permission to nil, we should fall back to force delete
if err := state.Delete(false); err != nil {
if err := state.Delete(ctx, false); err != nil {
t.Fatalf("delete: %s", err)
}
workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId)
@@ -228,7 +232,9 @@ func TestDelete_ForceDelete(t *testing.T) {
state.workspace.Permissions.CanForceDelete = tfe.Bool(true)
state.workspace.ResourceCount = 5
if err := state.Delete(true); err != nil {
ctx := context.Background()
if err := state.Delete(ctx, true); err != nil {
t.Fatalf("delete: %s", err)
}
workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId)
@@ -243,15 +249,17 @@ func TestDelete_SafeDelete(t *testing.T) {
state.workspace.Permissions.CanForceDelete = tfe.Bool(false)
state.workspace.ResourceCount = 5
ctx := context.Background()
// safe-deleting a workspace with resources should fail
err := state.Delete(false)
err := state.Delete(ctx, false)
if err == nil {
t.Fatalf("workspace should have failed to safe delete")
}
// safe-deleting a workspace with resources should succeed once it has no resources
state.workspace.ResourceCount = 0
if err = state.Delete(false); err != nil {
if err = state.Delete(ctx, false); err != nil {
t.Fatalf("workspace safe-delete err: %s", err)
}

View File

@@ -172,7 +172,7 @@ func (c *WorkspaceDeleteCommand) Run(args []string) int {
// be delegated from the Backend to the State itself.
stateLocker.Unlock()
err = b.DeleteWorkspace(workspace, force)
err = b.DeleteWorkspace(ctx, workspace, force)
if err != nil {
c.Ui.Error(err.Error())
return 1

View File

@@ -4,6 +4,8 @@
package remote
import (
"context"
"github.com/opentofu/opentofu/internal/states/statemgr"
)
@@ -13,7 +15,7 @@ import (
type Client interface {
Get() (*Payload, error)
Put([]byte) error
Delete() error
Delete(context.Context) error
}
// ClientForcePusher is an optional interface that allows a remote

View File

@@ -4,6 +4,7 @@
package remote
import (
"context"
"crypto/md5"
"encoding/json"
"testing"
@@ -25,7 +26,7 @@ func (nilClient) Get() (*Payload, error) { return nil, nil }
func (c nilClient) Put([]byte) error { return nil }
func (c nilClient) Delete() error { return nil }
func (c nilClient) Delete(context.Context) error { return nil }
// mockClient is a client that tracks persisted state snapshots only in
// memory and also logs what it has been asked to do for use in test
@@ -58,7 +59,7 @@ func (c *mockClient) Put(data []byte) error {
return nil
}
func (c *mockClient) Delete() error {
func (c *mockClient) Delete(context.Context) error {
c.appendLog("Delete", c.current)
c.current = nil
return nil
@@ -116,7 +117,7 @@ func (c *mockClientForcePusher) EnableForcePush() {
c.force = true
}
func (c *mockClientForcePusher) Delete() error {
func (c *mockClientForcePusher) Delete(context.Context) error {
c.appendLog("Delete", c.current)
c.current = nil
return nil

View File

@@ -5,6 +5,7 @@ package remote
import (
"bytes"
"context"
"testing"
"github.com/opentofu/opentofu/internal/states/statefile"
@@ -34,7 +35,9 @@ func TestClient(t *testing.T, c Client) {
t.Fatalf("expected full state %q\n\ngot: %q", string(p.Data), string(data))
}
if err := c.Delete(); err != nil {
ctx := context.Background()
if err := c.Delete(ctx); err != nil {
t.Fatalf("delete: %s", err)
}