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 // 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 // the responsibility of the caller to hold a Lock for the state manager
// belonging to this workspace before calling this method. // 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 // States returns a list of the names of all of the workspaces that exist
// in this backend. // in this backend.

View File

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

View File

@@ -231,7 +231,7 @@ func (b backendWithStateStorageThatFailsRefresh) Configure(context.Context, cty.
return nil 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") return fmt.Errorf("unimplemented")
} }

View File

@@ -139,7 +139,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) 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) t.Fatal(err)
} }
@@ -153,7 +153,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) 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) t.Fatal(err)
} }
@@ -167,7 +167,7 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
t.Fatalf("expected %q, got %q", expectedStates, states) 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") t.Fatal("expected error deleting default state")
} }
} }
@@ -202,7 +202,7 @@ func (b *testDelegateBackend) Workspaces() ([]string, error) {
return []string{"default"}, nil 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 { if b.deleteErr {
return errTestDelegateDeleteState return errTestDelegateDeleteState
} }
@@ -218,7 +218,9 @@ func TestLocal_multiStateBackend(t *testing.T) {
deleteErr: true, 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) t.Fatal("expected errTestDelegateState, got:", err)
} }
@@ -226,7 +228,7 @@ func TestLocal_multiStateBackend(t *testing.T) {
t.Fatal("expected errTestDelegateStates, got:", err) 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) t.Fatal("expected errTestDelegateDeleteState, got:", err)
} }
} }

View File

@@ -122,7 +122,7 @@ func (b *TestLocalSingleState) Workspaces() ([]string, error) {
return nil, backend.ErrWorkspacesNotSupported return nil, backend.ErrWorkspacesNotSupported
} }
func (b *TestLocalSingleState) DeleteWorkspace(string, bool) error { func (b *TestLocalSingleState) DeleteWorkspace(context.Context, string, bool) error {
return backend.ErrWorkspacesNotSupported return backend.ErrWorkspacesNotSupported
} }
@@ -164,11 +164,11 @@ func (b *TestLocalNoDefaultState) Workspaces() ([]string, error) {
return filtered, nil 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 { if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported 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) { 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 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 == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
ctx := context.TODO()
client, err := b.armClient.getBlobClient(ctx) client, err := b.armClient.getBlobClient(ctx)
if err != nil { if err != nil {
return err return err

View File

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

View File

@@ -53,7 +53,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return result, nil 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 == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }

View File

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

View File

@@ -235,7 +235,7 @@ func TestConsul_largeState(t *testing.T) {
) )
// Deleting the state should remove all chunks // Deleting the state should remove all chunks
err = c.Delete() err = c.Delete(ctx)
if err != nil { if err != nil {
t.Fatal(err) 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. // 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) log.Printf("[DEBUG] delete workspace, workspace: %v", name)
if name == backend.DefaultStateName || name == "" { if name == backend.DefaultStateName || name == "" {
@@ -73,7 +73,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err return err
} }
return c.Delete() return c.Delete(ctx)
} }
// StateMgr manage the state, if the named state not exists, a new file will created // StateMgr manage the state, if the named state not exists, a new file will created

View File

@@ -28,7 +28,9 @@ const (
// RemoteClient implements the client of remote state // RemoteClient implements the client of remote state
type remoteClient struct { type remoteClient struct {
// TODO: remove once all methods are using context passed via the CLI.
cosContext context.Context cosContext context.Context
cosClient *cos.Client cosClient *cos.Client
tagClient *tag.Client tagClient *tag.Client
@@ -68,10 +70,10 @@ func (c *remoteClient) Put(data []byte) error {
} }
// Delete delete remote state file // 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) 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 // 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)) 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 { if err != nil {
return c.lockError(err) return c.lockError(err)
} }
@@ -252,8 +254,8 @@ func (c *remoteClient) putObject(cosFile string, data []byte) error {
} }
// deleteObject delete remote object // deleteObject delete remote object
func (c *remoteClient) deleteObject(cosFile string) error { func (c *remoteClient) deleteObject(ctx context.Context, cosFile string) error {
rsp, err := c.cosClient.Object.Delete(c.cosContext, cosFile) rsp, err := c.cosClient.Object.Delete(ctx, cosFile)
if rsp == nil { if rsp == nil {
log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err) log.Printf("[DEBUG] deleteObject %s: error: %v", cosFile, err)
return fmt.Errorf("failed to delete file %v: %w", 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) return fmt.Errorf("failed to empty bucket %v: %w", c.bucket, err)
} }
for _, v := range obs { 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. // 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 { if name == backend.DefaultStateName {
return fmt.Errorf("cowardly refusing to delete the %q state", name) 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 err
} }
return c.Delete() return c.Delete(ctx)
} }
// client returns a remoteClient for the named state. // client returns a remoteClient for the named state.

View File

@@ -20,7 +20,9 @@ import (
// blobs representing state. // blobs representing state.
// Implements "state/remote".ClientLocker // Implements "state/remote".ClientLocker
type remoteClient struct { type remoteClient struct {
// TODO: remove this once all methods are accepting an explicit context
storageContext context.Context storageContext context.Context
storageClient *storage.Client storageClient *storage.Client
bucketName string bucketName string
stateFilePath string stateFilePath string
@@ -76,8 +78,8 @@ func (c *remoteClient) Put(data []byte) error {
return nil return nil
} }
func (c *remoteClient) Delete() error { func (c *remoteClient) Delete(ctx context.Context) error {
if err := c.stateFile().Delete(c.storageContext); err != nil { if err := c.stateFile().Delete(ctx); err != nil {
return fmt.Errorf("Failed to delete state file %v: %w", c.stateFileURL(), err) 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 return nil, backend.ErrWorkspacesNotSupported
} }
func (b *Backend) DeleteWorkspace(string, bool) error { func (b *Backend) DeleteWorkspace(context.Context, string, bool) error {
return backend.ErrWorkspacesNotSupported return backend.ErrWorkspacesNotSupported
} }

View File

@@ -5,6 +5,7 @@ package http
import ( import (
"bytes" "bytes"
"context"
"crypto/md5" "crypto/md5"
"encoding/base64" "encoding/base64"
"encoding/json" "encoding/json"
@@ -39,7 +40,7 @@ type httpClient struct {
jsonLockInfo []byte 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 // If we have data we need a reader
var reader io.Reader = nil var reader io.Reader = nil
if data != nil { if data != nil {
@@ -47,7 +48,7 @@ func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what
} }
// Create the request // Create the request
req, err := retryablehttp.NewRequest(method, url.String(), reader) req, err := retryablehttp.NewRequestWithContext(ctx, method, url.String(), reader)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to make %s HTTP request: %w", what, err) 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 = "" c.lockID = ""
jsonLockInfo := info.Marshal() 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 { if err != nil {
return "", err return "", err
} }
@@ -129,7 +130,7 @@ func (c *httpClient) Unlock(id string) error {
return nil 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 { if err != nil {
return err return err
} }
@@ -144,7 +145,7 @@ func (c *httpClient) Unlock(id string) error {
} }
func (c *httpClient) Get() (*remote.Payload, 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 { if err != nil {
return nil, err return nil, err
} }
@@ -225,7 +226,7 @@ func (c *httpClient) Put(data []byte) error {
if c.UpdateMethod != "" { if c.UpdateMethod != "" {
method = 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 { if err != nil {
return err 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 // 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 { if err != nil {
return err return err
} }

View File

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

View File

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

View File

@@ -63,7 +63,7 @@ func (b *Backend) Workspaces() ([]string, error) {
return states, nil 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 == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@@ -73,7 +73,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err return err
} }
return client.Delete() return client.Delete(ctx)
} }
func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, error) { 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 // Delete the state secret
func (c *RemoteClient) Delete() error { func (c *RemoteClient) Delete(context.Context) error {
secretName, err := c.createSecretName() secretName, err := c.createSecretName()
if err != nil { if err != nil {
return err return err

View File

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

View File

@@ -5,6 +5,7 @@ package oss
import ( import (
"bytes" "bytes"
"context"
"crypto/md5" "crypto/md5"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
@@ -129,7 +130,7 @@ func (c *RemoteClient) Put(data []byte) error {
return nil return nil
} }
func (c *RemoteClient) Delete() error { func (c *RemoteClient) Delete(ctx context.Context) error {
bucket, err := c.ossClient.Bucket(c.bucketName) bucket, err := c.ossClient.Bucket(c.bucketName)
if err != nil { if err != nil {
return fmt.Errorf("error getting bucket %s: %w", c.bucketName, err) 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 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 == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
query := `DELETE FROM %s.%s WHERE name = $1` 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 { if err != nil {
return err return err
} }

View File

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

View File

@@ -102,7 +102,7 @@ func (b *Backend) keyEnv(key string) string {
return parts[0] 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 == "" { if name == backend.DefaultStateName || name == "" {
return fmt.Errorf("can't delete default state") return fmt.Errorf("can't delete default state")
} }
@@ -112,7 +112,7 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) error {
return err return err
} }
return client.Delete() return client.Delete(ctx)
} }
// get a remote client configured for this state // 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 // remove the state with extra subkey
if err := client.Delete(); err != nil { if err := client.Delete(ctx); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// delete the real workspace // delete the real workspace
if err := b.DeleteWorkspace("s2", true); err != nil { if err := b.DeleteWorkspace(ctx, "s2", true); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

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

View File

@@ -609,7 +609,7 @@ func (b *Remote) WorkspaceNamePattern() string {
} }
// DeleteWorkspace implements backend.Enhanced. // 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 { if b.workspace == "" && name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported 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. // StateMgr implements backend.Enhanced.

View File

@@ -141,8 +141,8 @@ func (r *remoteClient) Put(state []byte) error {
} }
// Delete the remote state. // Delete the remote state.
func (r *remoteClient) Delete() error { func (r *remoteClient) Delete(ctx context.Context) error {
err := r.client.Workspaces.Delete(context.Background(), r.organization, r.workspace.Name) err := r.client.Workspaces.Delete(ctx, r.organization, r.workspace.Name)
if err != nil && err != tfe.ErrResourceNotFound { if err != nil && err != tfe.ErrResourceNotFound {
return fmt.Errorf("error deleting workspace %s: %w", r.workspace.Name, err) 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) 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) 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) 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) 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) 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) t.Fatal(err)
} }
@@ -360,7 +360,7 @@ func TestRemote_addAndRemoveWorkspacesNoDefault(t *testing.T) {
t.Fatalf("expected %#+v got %#+v", expectedWorkspaces, states) 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) t.Fatal(err)
} }

View File

@@ -227,12 +227,12 @@ func TestBackendStates(t *testing.T, b Backend) {
} }
// Delete some workspaces // 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) t.Fatalf("err: %s", err)
} }
// Verify the default state can't be deleted // 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") t.Fatal("expected error")
} }
@@ -250,7 +250,7 @@ func TestBackendStates(t *testing.T, b Backend) {
t.Fatalf("should be empty: %s", v) t.Fatalf("should be empty: %s", v)
} }
// and delete it again // 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) 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") 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") return fmt.Errorf("DeleteWorkspace not implemented")
} }

View File

@@ -592,7 +592,7 @@ func (b *Cloud) Workspaces() ([]string, error) {
} }
// DeleteWorkspace implements backend.Enhanced. // 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 { if name == backend.DefaultStateName {
return backend.ErrDefaultWorkspaceNotSupported return backend.ErrDefaultWorkspaceNotSupported
} }
@@ -601,7 +601,7 @@ func (b *Cloud) DeleteWorkspace(name string, force bool) error {
return backend.ErrWorkspacesNotSupported 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 { if err == tfe.ErrResourceNotFound {
return nil // If the workspace does not exist, succeed 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. // Configure the remote workspace name.
State := &State{tfeClient: b.client, organization: b.organization, workspace: workspace, enableIntermediateSnapshots: false} 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. // 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) 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) 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) 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) 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) t.Fatalf("expected error %v, got %v", backend.ErrWorkspacesNotSupported, err)
} }
} }
@@ -1396,7 +1396,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error locking workspace: %v", err) t.Fatalf("error locking workspace: %v", err)
} }
err = b.DeleteWorkspace(safeDeleteWorkspaceName, false) err = b.DeleteWorkspace(ctx, safeDeleteWorkspaceName, false)
if err == nil { if err == nil {
t.Fatalf("workspace should have failed to safe delete") t.Fatalf("workspace should have failed to safe delete")
} }
@@ -1406,7 +1406,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error unlocking workspace: %v", err) t.Fatalf("error unlocking workspace: %v", err)
} }
err = b.DeleteWorkspace(safeDeleteWorkspaceName, false) err = b.DeleteWorkspace(ctx, safeDeleteWorkspaceName, false)
if err != nil { if err != nil {
t.Fatalf("error safe deleting workspace: %v", err) t.Fatalf("error safe deleting workspace: %v", err)
} }
@@ -1420,7 +1420,7 @@ func TestCloudBackend_DeleteWorkspace_SafeAndForce(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("error locking workspace: %v", err) t.Fatalf("error locking workspace: %v", err)
} }
err = b.DeleteWorkspace(forceDeleteWorkspaceName, true) err = b.DeleteWorkspace(ctx, forceDeleteWorkspaceName, true)
if err != nil { if err != nil {
t.Fatalf("error force deleting workspace: %v", err) t.Fatalf("error force deleting workspace: %v", err)
} }
@@ -1430,7 +1430,9 @@ func TestCloudBackend_DeleteWorkspace_DoesNotExist(t *testing.T) {
b, bCleanup := testBackendWithTags(t) b, bCleanup := testBackendWithTags(t)
defer bCleanup() defer bCleanup()
err := b.DeleteWorkspace("non-existent-workspace", false) ctx := context.Background()
err := b.DeleteWorkspace(ctx, "non-existent-workspace", false)
if err != nil { if err != nil {
t.Fatalf("expected deleting a workspace which does not exist to succeed") 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. // Delete the remote state.
func (s *State) Delete(force bool) error { func (s *State) Delete(ctx context.Context, force bool) error {
var err error var err error
isSafeDeleteSupported := s.workspace.Permissions.CanForceDelete != nil isSafeDeleteSupported := s.workspace.Permissions.CanForceDelete != nil
if force || !isSafeDeleteSupported { 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 { } 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 { 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)) 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) t.Fatalf("delete: %s", err)
} }
@@ -211,9 +213,11 @@ func TestDelete_SafeDeleteNotSupported(t *testing.T) {
state.workspace.Permissions.CanForceDelete = nil state.workspace.Permissions.CanForceDelete = nil
state.workspace.ResourceCount = 5 state.workspace.ResourceCount = 5
ctx := context.Background()
// Typically delete(false) should safe-delete a cloud workspace, which should fail on this workspace with resources // 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 // 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) t.Fatalf("delete: %s", err)
} }
workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId) 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.Permissions.CanForceDelete = tfe.Bool(true)
state.workspace.ResourceCount = 5 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) t.Fatalf("delete: %s", err)
} }
workspace, err := state.tfeClient.Workspaces.ReadByID(context.Background(), workspaceId) 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.Permissions.CanForceDelete = tfe.Bool(false)
state.workspace.ResourceCount = 5 state.workspace.ResourceCount = 5
ctx := context.Background()
// safe-deleting a workspace with resources should fail // safe-deleting a workspace with resources should fail
err := state.Delete(false) err := state.Delete(ctx, false)
if err == nil { if err == nil {
t.Fatalf("workspace should have failed to safe delete") t.Fatalf("workspace should have failed to safe delete")
} }
// safe-deleting a workspace with resources should succeed once it has no resources // safe-deleting a workspace with resources should succeed once it has no resources
state.workspace.ResourceCount = 0 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) 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. // be delegated from the Backend to the State itself.
stateLocker.Unlock() stateLocker.Unlock()
err = b.DeleteWorkspace(workspace, force) err = b.DeleteWorkspace(ctx, workspace, force)
if err != nil { if err != nil {
c.Ui.Error(err.Error()) c.Ui.Error(err.Error())
return 1 return 1

View File

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

View File

@@ -4,6 +4,7 @@
package remote package remote
import ( import (
"context"
"crypto/md5" "crypto/md5"
"encoding/json" "encoding/json"
"testing" "testing"
@@ -25,7 +26,7 @@ func (nilClient) Get() (*Payload, error) { return nil, nil }
func (c nilClient) Put([]byte) error { return 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 // 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 // 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 return nil
} }
func (c *mockClient) Delete() error { func (c *mockClient) Delete(context.Context) error {
c.appendLog("Delete", c.current) c.appendLog("Delete", c.current)
c.current = nil c.current = nil
return nil return nil
@@ -116,7 +117,7 @@ func (c *mockClientForcePusher) EnableForcePush() {
c.force = true c.force = true
} }
func (c *mockClientForcePusher) Delete() error { func (c *mockClientForcePusher) Delete(context.Context) error {
c.appendLog("Delete", c.current) c.appendLog("Delete", c.current)
c.current = nil c.current = nil
return nil return nil

View File

@@ -5,6 +5,7 @@ package remote
import ( import (
"bytes" "bytes"
"context"
"testing" "testing"
"github.com/opentofu/opentofu/internal/states/statefile" "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)) 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) t.Fatalf("delete: %s", err)
} }