Pass context to all statemgr.Locker operations (#789)

Signed-off-by: Marcin Wyszynski <marcin.pixie@gmail.com>
This commit is contained in:
Marcin Wyszynski 2023-10-25 14:22:11 +02:00 committed by GitHub
parent 275dd116f9
commit 772ac1fc35
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 300 additions and 308 deletions

View File

@ -243,7 +243,7 @@ type stateStorageThatFailsRefresh struct {
locked bool
}
func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, error) {
func (s *stateStorageThatFailsRefresh) Lock(_ context.Context, info *statemgr.LockInfo) (string, error) {
if s.locked {
return "", fmt.Errorf("already locked")
}
@ -251,7 +251,7 @@ func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, er
return "locked", nil
}
func (s *stateStorageThatFailsRefresh) Unlock(id string) error {
func (s *stateStorageThatFailsRefresh) Unlock(_ context.Context, id string) error {
if !s.locked {
return fmt.Errorf("not locked")
}

View File

@ -208,7 +208,7 @@ func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
ctx := context.Background()
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Errorf("state is already locked: %s", err.Error())
return false
}
@ -224,7 +224,7 @@ func assertBackendStateLocked(t *testing.T, b *Local) bool {
ctx := context.Background()
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
return true
}
t.Error("unexpected success locking state")

View File

@ -259,7 +259,7 @@ func (b *Backend) configure(ctx context.Context) error {
UseAzureADAuthentication: data.Get("use_azuread_auth").(bool),
}
armClient, err := buildArmClient(context.TODO(), config)
armClient, err := buildArmClient(ctx, config)
if err != nil {
return err
}

View File

@ -105,14 +105,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
lockId, err := client.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock azure state: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent

View File

@ -13,6 +13,7 @@ import (
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/go-uuid"
"github.com/opentofu/opentofu/internal/states/remote"
"github.com/opentofu/opentofu/internal/states/statemgr"
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
@ -115,7 +116,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
stateName := fmt.Sprintf("%s/%s", c.containerName, c.keyName)
info.Path = stateName
@ -129,7 +130,7 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}
getLockInfoErr := func(err error) error {
lockInfo, infoErr := c.getLockInfo()
lockInfo, infoErr := c.getLockInfo(ctx)
if infoErr != nil {
err = multierror.Append(err, infoErr)
}
@ -144,7 +145,6 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
ProposedLeaseID: &info.ID,
LeaseDuration: -1,
}
ctx := context.TODO()
// obtain properties to see if the blob lease is already in use. If the blob doesn't exist, create it
properties, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, blobs.GetPropertiesInput{})
@ -179,20 +179,19 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
info.ID = leaseID.LeaseID
c.leaseID = leaseID.LeaseID
if err := c.writeLockInfo(info); err != nil {
if err := c.writeLockInfo(ctx, info); err != nil {
return "", err
}
return info.ID, nil
}
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
func (c *RemoteClient) getLockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
options := blobs.GetPropertiesInput{}
if c.leaseID != "" {
options.LeaseID = &c.leaseID
}
ctx := context.TODO()
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, options)
if err != nil {
return nil, err
@ -218,8 +217,7 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
}
// writes info to blob meta data, deletes metadata entry if info is nil
func (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {
ctx := context.TODO()
func (c *RemoteClient) writeLockInfo(ctx context.Context, info *statemgr.LockInfo) error {
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, blobs.GetPropertiesInput{LeaseID: &c.leaseID})
if err != nil {
return err
@ -244,10 +242,10 @@ func (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {
return err
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
lockErr := &statemgr.LockError{}
lockInfo, err := c.getLockInfo()
lockInfo, err := c.getLockInfo(ctx)
if err != nil {
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %w", err)
return lockErr
@ -260,12 +258,11 @@ func (c *RemoteClient) Unlock(id string) error {
}
c.leaseID = lockInfo.ID
if err := c.writeLockInfo(nil); err != nil {
if err := c.writeLockInfo(ctx, nil); err != nil {
lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %w", err)
return lockErr
}
ctx := context.TODO()
_, err = c.giovanniBlobClient.ReleaseLease(ctx, c.accountName, c.containerName, c.keyName, id)
if err != nil {
lockErr.Err = err

View File

@ -98,14 +98,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// so States() knows it exists.
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := stateMgr.Lock(lockInfo)
lockId, err := stateMgr.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock state in Consul: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}

View File

@ -353,7 +353,7 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
return li, nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
@ -377,15 +377,15 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}
}
return c.lock()
return c.lock(ctx)
}
// the lock implementation.
// Only to be called while holding Client.mu
func (c *RemoteClient) lock() (string, error) {
func (c *RemoteClient) lock(ctx context.Context) (string, error) {
// We create a new session here, so it can be canceled when the lock is
// lost or unlocked.
lockSession, err := c.createSession()
lockSession, err := c.createSession(ctx)
if err != nil {
return "", err
}
@ -460,7 +460,7 @@ func (c *RemoteClient) lock() (string, error) {
// If we lose the lock to due communication issues with the consul agent,
// attempt to immediately reacquire the lock. Put will verify the integrity
// of the state by using a CAS operation.
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
c.monitorCancel = cancel
c.monitorWG.Add(1)
go func() {
@ -477,7 +477,7 @@ func (c *RemoteClient) lock() (string, error) {
c.sessionCancel()
c.consulLock = nil
_, err := c.lock()
_, err := c.lock(ctx)
c.mu.Unlock()
if err != nil {
@ -516,10 +516,10 @@ func (c *RemoteClient) lock() (string, error) {
// called after a lock is acquired
var testLockHook func()
func (c *RemoteClient) createSession() (string, error) {
func (c *RemoteClient) createSession(ctx context.Context) (string, error) {
// create the context first. Even if the session creation fails, we assume
// that the CancelFunc is always callable.
ctx, cancel := context.WithCancel(context.Background())
ctx, cancel := context.WithCancel(ctx)
c.sessionCancel = cancel
session := c.Client.Session()
@ -542,7 +542,7 @@ func (c *RemoteClient) createSession() (string, error) {
return id, nil
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(_ context.Context, id string) error {
c.mu.Lock()
defer c.mu.Unlock()

View File

@ -314,14 +314,14 @@ func TestConsul_destroyLock(t *testing.T) {
clientA := s.(*remote.State).Client.(*RemoteClient)
info := statemgr.NewLockInfo()
id, err := clientA.Lock(info)
id, err := clientA.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
lockPath := clientA.Path + lockSuffix
if err := clientA.Unlock(id); err != nil {
if err := clientA.Unlock(ctx, id); err != nil {
t.Fatal(err)
}
@ -337,18 +337,18 @@ func TestConsul_destroyLock(t *testing.T) {
clientB := s.(*remote.State).Client.(*RemoteClient)
info = statemgr.NewLockInfo()
id, err = clientA.Lock(info)
id, err = clientA.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
if err := clientB.Unlock(id); err != nil {
if err := clientB.Unlock(ctx, id); err != nil {
t.Fatal(err)
}
testLock(clientA, lockPath)
err = clientA.Unlock(id)
err = clientA.Unlock(ctx, id)
if err == nil {
t.Fatal("consul lock should have been lost")
@ -386,7 +386,7 @@ func TestConsul_lostLock(t *testing.T) {
info := statemgr.NewLockInfo()
info.Operation = "test-lost-lock"
id, err := sA.Lock(info)
id, err := sA.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
@ -406,7 +406,7 @@ func TestConsul_lostLock(t *testing.T) {
<-reLocked
if err := sA.Unlock(id); err != nil {
if err := sA.Unlock(ctx, id); err != nil {
t.Fatal(err)
}
}
@ -439,7 +439,7 @@ func TestConsul_lostLockConnection(t *testing.T) {
info := statemgr.NewLockInfo()
info.Operation = "test-lost-lock-connection"
id, err := s.Lock(info)
id, err := s.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
@ -453,7 +453,7 @@ func TestConsul_lostLockConnection(t *testing.T) {
<-dialed
}
if err := s.Unlock(id); err != nil {
if err := s.Unlock(ctx, id); err != nil {
t.Fatal("unlock error:", err)
}
}

View File

@ -38,10 +38,9 @@ type Backend struct {
*schema.Backend
credential *common.Credential
cosContext context.Context
cosClient *cos.Client
tagClient *tag.Client
stsClient *sts.Client
cosClient *cos.Client
tagClient *tag.Client
stsClient *sts.Client
region string
bucket string
@ -207,8 +206,7 @@ func (b *Backend) configure(ctx context.Context) error {
return nil
}
b.cosContext = ctx
data := schema.FromContextBackendConfig(b.cosContext)
data := schema.FromContextBackendConfig(ctx)
b.region = data.Get("region").(string)
b.bucket = data.Get("bucket").(string)

View File

@ -105,14 +105,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := c.Lock(lockInfo)
lockId, err := c.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("Failed to lock cos state: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(e error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf(unlockErrMsg, err, lockId)
}
return e
@ -152,14 +152,13 @@ func (b *Backend) client(name string) (*remoteClient, error) {
}
return &remoteClient{
cosContext: b.cosContext,
cosClient: b.cosClient,
tagClient: b.tagClient,
bucket: b.bucket,
stateFile: b.stateFile(name),
lockFile: b.lockFile(name),
encrypt: b.encrypt,
acl: b.acl,
cosClient: b.cosClient,
tagClient: b.tagClient,
bucket: b.bucket,
stateFile: b.stateFile(name),
lockFile: b.lockFile(name),
encrypt: b.encrypt,
acl: b.acl,
}, nil
}

View File

@ -28,9 +28,6 @@ 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
@ -77,74 +74,74 @@ func (c *remoteClient) Delete(ctx context.Context) error {
}
// Lock lock remote state file for writing
func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *remoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
log.Printf("[DEBUG] lock remote state file %s", c.lockFile)
err := c.cosLock(c.bucket, c.lockFile)
if err != nil {
return "", c.lockError(err)
return "", c.lockError(ctx, err)
}
defer c.cosUnlock(c.bucket, c.lockFile)
exists, _, _, err := c.getObject(c.cosContext, c.lockFile)
exists, _, _, err := c.getObject(ctx, c.lockFile)
if err != nil {
return "", c.lockError(err)
return "", c.lockError(ctx, err)
}
if exists {
return "", c.lockError(fmt.Errorf("lock file %s exists", c.lockFile))
return "", c.lockError(ctx, fmt.Errorf("lock file %s exists", c.lockFile))
}
info.Path = c.lockFile
data, err := json.Marshal(info)
if err != nil {
return "", c.lockError(err)
return "", c.lockError(ctx, err)
}
check := fmt.Sprintf("%x", md5.Sum(data))
err = c.putObject(c.cosContext, c.lockFile, data)
err = c.putObject(ctx, c.lockFile, data)
if err != nil {
return "", c.lockError(err)
return "", c.lockError(ctx, err)
}
return check, nil
}
// Unlock unlock remote state file
func (c *remoteClient) Unlock(check string) error {
// Unlock unlocks remote state file
func (c *remoteClient) Unlock(ctx context.Context, check string) error {
log.Printf("[DEBUG] unlock remote state file %s", c.lockFile)
info, err := c.lockInfo()
info, err := c.lockInfo(ctx)
if err != nil {
return c.lockError(err)
return c.lockError(ctx, err)
}
if info.ID != check {
return c.lockError(fmt.Errorf("lock id mismatch, %v != %v", info.ID, check))
return c.lockError(ctx, fmt.Errorf("lock id mismatch, %v != %v", info.ID, check))
}
err = c.deleteObject(c.cosContext, c.lockFile)
err = c.deleteObject(ctx, c.lockFile)
if err != nil {
return c.lockError(err)
return c.lockError(ctx, err)
}
err = c.cosUnlock(c.bucket, c.lockFile)
if err != nil {
return c.lockError(err)
return c.lockError(ctx, err)
}
return nil
}
// lockError returns statemgr.LockError
func (c *remoteClient) lockError(err error) *statemgr.LockError {
func (c *remoteClient) lockError(ctx context.Context, err error) *statemgr.LockError {
log.Printf("[DEBUG] failed to lock or unlock %s: %v", c.lockFile, err)
lockErr := &statemgr.LockError{
Err: err,
}
info, infoErr := c.lockInfo()
info, infoErr := c.lockInfo(ctx)
if infoErr != nil {
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
} else {
@ -155,8 +152,8 @@ func (c *remoteClient) lockError(err error) *statemgr.LockError {
}
// lockInfo returns LockInfo from lock file
func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
exists, data, checksum, err := c.getObject(c.cosContext, c.lockFile)
func (c *remoteClient) lockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
exists, data, checksum, err := c.getObject(ctx, c.lockFile)
if err != nil {
return nil, err
}
@ -238,7 +235,7 @@ func (c *remoteClient) putObject(ctx context.Context, cosFile string, data []byt
}
r := bytes.NewReader(data)
rsp, err := c.cosClient.Object.Put(c.cosContext, cosFile, r, opt)
rsp, err := c.cosClient.Object.Put(ctx, cosFile, r, opt)
if rsp == nil {
log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err)
return fmt.Errorf("failed to save file to %v: %w", cosFile, err)

View File

@ -30,9 +30,6 @@ type Backend struct {
storageClient *storage.Client
// TODO: Remove storageContext once all methods are accepting a context.
storageContext context.Context
bucketName string
prefix string
@ -129,13 +126,7 @@ func (b *Backend) configure(ctx context.Context) error {
return nil
}
// ctx is a background context with the backend config added.
// Since no context is passed to remoteClient.Get(), .Lock(), etc. but
// one is required for calling the GCP API, we're holding on to this
// context here and re-use it later.
b.storageContext = ctx
data := schema.FromContextBackendConfig(b.storageContext)
data := schema.FromContextBackendConfig(ctx)
b.bucketName = data.Get("bucket").(string)
b.prefix = strings.TrimLeft(data.Get("prefix").(string), "/")
@ -217,7 +208,7 @@ func (b *Backend) configure(ctx context.Context) error {
endpoint := option.WithEndpoint(storageEndpoint.(string))
opts = append(opts, endpoint)
}
client, err := storage.NewClient(b.storageContext, opts...)
client, err := storage.NewClient(ctx, opts...)
if err != nil {
return fmt.Errorf("storage.NewClient() failed: %w", err)
}

View File

@ -79,13 +79,12 @@ func (b *Backend) client(name string) (*remoteClient, error) {
}
return &remoteClient{
storageContext: b.storageContext,
storageClient: b.storageClient,
bucketName: b.bucketName,
stateFilePath: b.stateFile(name),
lockFilePath: b.lockFile(name),
encryptionKey: b.encryptionKey,
kmsKeyName: b.kmsKeyName,
storageClient: b.storageClient,
bucketName: b.bucketName,
stateFilePath: b.stateFile(name),
lockFilePath: b.lockFile(name),
encryptionKey: b.encryptionKey,
kmsKeyName: b.kmsKeyName,
}, nil
}
@ -109,14 +108,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockID, err := st.Lock(lockInfo)
lockID, err := st.Lock(ctx, lockInfo)
if err != nil {
return nil, err
}
// Local helper function so we can call it multiple places
unlock := func(baseErr error) error {
if err := st.Unlock(lockID); err != nil {
if err := st.Unlock(ctx, lockID); err != nil {
const unlockErrMsg = `%v
Additionally, unlocking the state file on Google Cloud Storage failed:

View File

@ -244,10 +244,11 @@ func setupBackend(t *testing.T, bucket, prefix, key, kmsName string) backend.Bac
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config))
be := b.(*Backend)
ctx := context.Background()
// create the bucket if it doesn't exist
bkt := be.storageClient.Bucket(bucket)
_, err := bkt.Attrs(be.storageContext)
_, err := bkt.Attrs(ctx)
if err != nil {
if err != storage.ErrBucketNotExist {
t.Fatal(err)
@ -256,7 +257,7 @@ func setupBackend(t *testing.T, bucket, prefix, key, kmsName string) backend.Bac
attrs := &storage.BucketAttrs{
Location: os.Getenv("GOOGLE_REGION"),
}
err := bkt.Create(be.storageContext, projectID, attrs)
err := bkt.Create(ctx, projectID, attrs)
if err != nil {
t.Fatal(err)
}
@ -386,7 +387,7 @@ func teardownBackend(t *testing.T, be backend.Backend, prefix string) {
if !ok {
t.Fatalf("be is a %T, want a *gcsBackend", be)
}
ctx := gcsBE.storageContext
ctx := context.Background()
bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
objs := bucket.Objects(ctx, nil)

View File

@ -20,9 +20,6 @@ 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
@ -47,7 +44,7 @@ func (c *remoteClient) Get(ctx context.Context) (payload *remote.Payload, err er
return nil, fmt.Errorf("Failed to read state file from %v: %w", c.stateFileURL(), err)
}
stateFileAttrs, err := c.stateFile().Attrs(c.storageContext)
stateFileAttrs, err := c.stateFile().Attrs(ctx)
if err != nil {
return nil, fmt.Errorf("Failed to read state file attrs from %v: %w", c.stateFileURL(), err)
}
@ -88,7 +85,7 @@ func (c *remoteClient) Delete(ctx context.Context) error {
// Lock writes to a lock file, ensuring file creation. Returns the generation
// number, which must be passed to Unlock().
func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *remoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
// update the path we're using
// we can't set the ID until the info is written
info.Path = c.lockFileURL()
@ -99,7 +96,7 @@ func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}
lockFile := c.lockFile()
w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(c.storageContext)
w := lockFile.If(storage.Conditions{DoesNotExist: true}).NewWriter(ctx)
err = func() error {
if _, err := w.Write(infoJson); err != nil {
return err
@ -108,7 +105,7 @@ func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}()
if err != nil {
return "", c.lockError(fmt.Errorf("writing %q failed: %w", c.lockFileURL(), err))
return "", c.lockError(ctx, fmt.Errorf("writing %q failed: %w", c.lockFileURL(), err))
}
info.ID = strconv.FormatInt(w.Attrs().Generation, 10)
@ -116,25 +113,25 @@ func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
return info.ID, nil
}
func (c *remoteClient) Unlock(id string) error {
func (c *remoteClient) Unlock(ctx context.Context, id string) error {
gen, err := strconv.ParseInt(id, 10, 64)
if err != nil {
return fmt.Errorf("Lock ID should be numerical value, got '%s'", id)
}
if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(c.storageContext); err != nil {
return c.lockError(err)
if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(ctx); err != nil {
return c.lockError(ctx, err)
}
return nil
}
func (c *remoteClient) lockError(err error) *statemgr.LockError {
func (c *remoteClient) lockError(ctx context.Context, err error) *statemgr.LockError {
lockErr := &statemgr.LockError{
Err: err,
}
info, infoErr := c.lockInfo()
info, infoErr := c.lockInfo(ctx)
if infoErr != nil {
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
} else {
@ -145,8 +142,8 @@ func (c *remoteClient) lockError(err error) *statemgr.LockError {
// lockInfo reads the lock file, parses its contents and returns the parsed
// LockInfo struct.
func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
r, err := c.lockFile().NewReader(c.storageContext)
func (c *remoteClient) lockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
r, err := c.lockFile().NewReader(ctx)
if err != nil {
return nil, err
}
@ -165,7 +162,7 @@ func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
// We use the Generation as the ID, so overwrite the ID in the json.
// This can't be written into the Info, since the generation isn't known
// until it's written.
attrs, err := c.lockFile().Attrs(c.storageContext)
attrs, err := c.lockFile().Attrs(ctx)
if err != nil {
return nil, err
}

View File

@ -77,14 +77,14 @@ func (c *httpClient) httpRequest(ctx context.Context, method string, url *url.UR
return resp, nil
}
func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *httpClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
if c.LockURL == nil {
return "", nil
}
c.lockID = ""
jsonLockInfo := info.Marshal()
resp, err := c.httpRequest(context.TODO(), c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
resp, err := c.httpRequest(ctx, c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
if err != nil {
return "", err
}
@ -125,12 +125,12 @@ func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
}
}
func (c *httpClient) Unlock(id string) error {
func (c *httpClient) Unlock(ctx context.Context, id string) error {
if c.UnlockURL == nil {
return nil
}
resp, err := c.httpRequest(context.TODO(), c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
resp, err := c.httpRequest(ctx, c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
if err != nil {
return err
}

View File

@ -133,11 +133,11 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// take a lock and create a new state if it doesn't exist.
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockID, err := s.Lock(lockInfo)
lockID, err := s.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock inmem state: %w", err)
}
defer s.Unlock(lockID)
defer s.Unlock(ctx, lockID)
// If we have no state, we have to create an empty state
if v := s.State(); v == nil {

View File

@ -43,9 +43,9 @@ func (c *RemoteClient) Delete(context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(_ context.Context, info *statemgr.LockInfo) (string, error) {
return locks.lock(c.Name, info)
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(_ context.Context, id string) error {
return locks.unlock(c.Name, id)
}

View File

@ -94,7 +94,7 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockID, err := stateMgr.Lock(lockInfo)
lockID, err := stateMgr.Lock(ctx, lockInfo)
if err != nil {
return nil, err
}
@ -106,7 +106,7 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// Local helper function so we can call it multiple places
unlock := func(baseErr error) error {
if err := stateMgr.Unlock(lockID); err != nil {
if err := stateMgr.Unlock(ctx, lockID); err != nil {
const unlockErrMsg = `%v
Additionally, unlocking the state in Kubernetes failed:

View File

@ -101,6 +101,8 @@ func TestBackendLocksSoak(t *testing.T) {
wg := sync.WaitGroup{}
for i, l := range lockers {
ctx := context.Background()
wg.Add(1)
go func(locker statemgr.Locker, n int) {
defer wg.Done()
@ -110,7 +112,7 @@ func TestBackendLocksSoak(t *testing.T) {
li.Who = fmt.Sprintf("client-%v", n)
for i := 0; i < lockAttempts; i++ {
id, err := locker.Lock(li)
id, err := locker.Lock(ctx, li)
if err != nil {
continue
}
@ -118,7 +120,7 @@ func TestBackendLocksSoak(t *testing.T) {
// hold onto the lock for a little bit
time.Sleep(time.Duration(rand.Intn(10)) * time.Microsecond)
err = locker.Unlock(id)
err = locker.Unlock(ctx, id)
if err != nil {
t.Errorf("failed to unlock: %v", err)
}

View File

@ -92,7 +92,7 @@ func (c *RemoteClient) Put(ctx context.Context, data []byte) error {
return err
}
secret, err := c.getSecret(secretName)
secret, err := c.getSecret(ctx, secretName)
if err != nil {
if !k8serrors.IsNotFound(err) {
return err
@ -121,13 +121,13 @@ func (c *RemoteClient) Put(ctx context.Context, data []byte) error {
}
// Delete the state secret
func (c *RemoteClient) Delete(context.Context) error {
func (c *RemoteClient) Delete(ctx context.Context) error {
secretName, err := c.createSecretName()
if err != nil {
return err
}
err = c.deleteSecret(secretName)
err = c.deleteSecret(ctx, secretName)
if err != nil {
if !k8serrors.IsNotFound(err) {
return err
@ -139,7 +139,7 @@ func (c *RemoteClient) Delete(context.Context) error {
return err
}
err = c.deleteLease(leaseName)
err = c.deleteLease(ctx, leaseName)
if err != nil {
if !k8serrors.IsNotFound(err) {
return err
@ -148,14 +148,13 @@ func (c *RemoteClient) Delete(context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
ctx := context.Background()
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
leaseName, err := c.createLeaseName()
if err != nil {
return "", err
}
lease, err := c.getLease(leaseName)
lease, err := c.getLease(ctx, leaseName)
if err != nil {
if !k8serrors.IsNotFound(err) {
return "", err
@ -210,13 +209,13 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
return info.ID, err
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
leaseName, err := c.createLeaseName()
if err != nil {
return err
}
lease, err := c.getLease(leaseName)
lease, err := c.getLease(ctx, leaseName)
if err != nil {
return err
}
@ -239,7 +238,7 @@ func (c *RemoteClient) Unlock(id string) error {
lease.Spec.HolderIdentity = nil
removeLockInfo(lease)
_, err = c.kubernetesLeaseClient.Update(context.Background(), lease, metav1.UpdateOptions{})
_, err = c.kubernetesLeaseClient.Update(ctx, lease, metav1.UpdateOptions{})
if err != nil {
lockErr.Err = err
return lockErr
@ -280,16 +279,16 @@ func (c *RemoteClient) getLabels() map[string]string {
return l
}
func (c *RemoteClient) getSecret(name string) (*unstructured.Unstructured, error) {
return c.kubernetesSecretClient.Get(context.Background(), name, metav1.GetOptions{})
func (c *RemoteClient) getSecret(ctx context.Context, name string) (*unstructured.Unstructured, error) {
return c.kubernetesSecretClient.Get(ctx, name, metav1.GetOptions{})
}
func (c *RemoteClient) getLease(name string) (*coordinationv1.Lease, error) {
return c.kubernetesLeaseClient.Get(context.Background(), name, metav1.GetOptions{})
func (c *RemoteClient) getLease(ctx context.Context, name string) (*coordinationv1.Lease, error) {
return c.kubernetesLeaseClient.Get(ctx, name, metav1.GetOptions{})
}
func (c *RemoteClient) deleteSecret(name string) error {
secret, err := c.getSecret(name)
func (c *RemoteClient) deleteSecret(ctx context.Context, name string) error {
secret, err := c.getSecret(ctx, name)
if err != nil {
return err
}
@ -302,11 +301,11 @@ func (c *RemoteClient) deleteSecret(name string) error {
delProp := metav1.DeletePropagationBackground
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
return c.kubernetesSecretClient.Delete(context.Background(), name, delOps)
return c.kubernetesSecretClient.Delete(ctx, name, delOps)
}
func (c *RemoteClient) deleteLease(name string) error {
secret, err := c.getLease(name)
func (c *RemoteClient) deleteLease(ctx context.Context, name string) error {
secret, err := c.getLease(ctx, name)
if err != nil {
return err
}
@ -319,7 +318,7 @@ func (c *RemoteClient) deleteLease(name string) error {
delProp := metav1.DeletePropagationBackground
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
return c.kubernetesLeaseClient.Delete(context.Background(), name, delOps)
return c.kubernetesLeaseClient.Delete(ctx, name, delOps)
}
func (c *RemoteClient) createSecretName() (string, error) {

View File

@ -86,7 +86,7 @@ func TestForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err := s1.Lock(info)
lockID, err := s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -97,7 +97,7 @@ func TestForceUnlock(t *testing.T) {
t.Fatal("failed to get default state to force unlock:", err)
}
if err := s2.Unlock(lockID); err != nil {
if err := s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock default state")
}
@ -112,7 +112,7 @@ func TestForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err = s1.Lock(info)
lockID, err = s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -123,7 +123,7 @@ func TestForceUnlock(t *testing.T) {
t.Fatal("failed to get named state to force unlock:", err)
}
if err = s2.Unlock(lockID); err != nil {
if err = s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock named state")
}
}

View File

@ -139,14 +139,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
lockId, err := client.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock OSS state: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(e error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
}
return e

View File

@ -148,7 +148,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(_ context.Context, info *statemgr.LockInfo) (string, error) {
if c.otsTable == "" {
return "", nil
}
@ -359,7 +359,7 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
}
return lockInfo, nil
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
if c.otsTable == "" {
return nil
}

View File

@ -127,7 +127,7 @@ func TestRemoteClientLocks_multipleStates(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if _, err := s1.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := s1.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatal("failed to get lock for s1:", err)
}
@ -136,7 +136,7 @@ func TestRemoteClientLocks_multipleStates(t *testing.T) {
if err != nil {
t.Fatal(err)
}
if _, err := s2.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := s2.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatal("failed to get lock for s2:", err)
}
}
@ -181,7 +181,7 @@ func TestRemoteForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err := s1.Lock(info)
lockID, err := s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -192,7 +192,7 @@ func TestRemoteForceUnlock(t *testing.T) {
t.Fatal("failed to get default state to force unlock:", err)
}
if err := s2.Unlock(lockID); err != nil {
if err := s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock default state")
}
@ -207,7 +207,7 @@ func TestRemoteForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err = s1.Lock(info)
lockID, err = s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -218,7 +218,7 @@ func TestRemoteForceUnlock(t *testing.T) {
t.Fatal("failed to get named state to force unlock:", err)
}
if err = s2.Unlock(lockID); err != nil {
if err = s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock named state")
}
}

View File

@ -85,14 +85,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
if !exists {
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := stateMgr.Lock(lockInfo)
lockId, err := stateMgr.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock state in Postgres: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf("error unlocking Postgres state: %w", err)
}
return parent

View File

@ -439,6 +439,8 @@ func TestBackendConcurrentLock(t *testing.T) {
t.Fatal(err)
}
ctx := context.Background()
getStateMgr := func(schemaName string) (statemgr.Full, *statemgr.LockInfo) {
defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
config := backend.TestWrapConfig(map[string]interface{}{
@ -451,8 +453,6 @@ func TestBackendConcurrentLock(t *testing.T) {
t.Fatal("Backend could not be configured")
}
ctx := context.Background()
stateMgr, err := b.StateMgr(ctx, backend.DefaultStateName)
if err != nil {
t.Fatalf("Failed to get the state manager: %v", err)
@ -470,22 +470,20 @@ func TestBackendConcurrentLock(t *testing.T) {
// First we need to create the workspace as the lock for creating them is
// global
lockID1, err := s1.Lock(i1)
lockID1, err := s1.Lock(ctx, i1)
if err != nil {
t.Fatalf("failed to lock first state: %v", err)
}
ctx := context.Background()
if err = s1.PersistState(ctx, nil); err != nil {
t.Fatalf("failed to persist state: %v", err)
}
if err := s1.Unlock(lockID1); err != nil {
if err := s1.Unlock(ctx, lockID1); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
lockID2, err := s2.Lock(i2)
lockID2, err := s2.Lock(ctx, i2)
if err != nil {
t.Fatalf("failed to lock second state: %v", err)
}
@ -494,26 +492,26 @@ func TestBackendConcurrentLock(t *testing.T) {
t.Fatalf("failed to persist state: %v", err)
}
if err := s2.Unlock(lockID2); err != nil {
if err := s2.Unlock(ctx, lockID2); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
// Now we can test concurrent lock
lockID1, err = s1.Lock(i1)
lockID1, err = s1.Lock(ctx, i1)
if err != nil {
t.Fatalf("failed to lock first state: %v", err)
}
lockID2, err = s2.Lock(i2)
lockID2, err = s2.Lock(ctx, i2)
if err != nil {
t.Fatalf("failed to lock second state: %v", err)
}
if err := s1.Unlock(lockID1); err != nil {
if err := s1.Unlock(ctx, lockID1); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
if err := s2.Unlock(lockID2); err != nil {
if err := s2.Unlock(ctx, lockID2); err != nil {
t.Fatalf("failed to unlock first state: %v", err)
}
}

View File

@ -64,7 +64,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
var err error
var lockID string
@ -80,7 +80,7 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
//
lockUnlock := func(pgLockId string) error {
query := `SELECT pg_advisory_unlock(%s)`
row := c.Client.QueryRow(fmt.Sprintf(query, pgLockId))
row := c.Client.QueryRowContext(ctx, fmt.Sprintf(query, pgLockId))
var didUnlock []byte
err := row.Scan(&didUnlock)
if err != nil {
@ -91,13 +91,13 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
// Try to acquire locks for the existing row `id` and the creation lock `-1`.
query := `SELECT %s.id, pg_try_advisory_lock(%s.id), pg_try_advisory_lock(-1) FROM %s.%s WHERE %s.name = $1`
row := c.Client.QueryRow(fmt.Sprintf(query, statesTableName, statesTableName, c.SchemaName, statesTableName, statesTableName), c.Name)
row := c.Client.QueryRowContext(ctx, fmt.Sprintf(query, statesTableName, statesTableName, c.SchemaName, statesTableName, statesTableName), c.Name)
var pgLockId, didLock, didLockForCreate []byte
err = row.Scan(&pgLockId, &didLock, &didLockForCreate)
switch {
case err == sql.ErrNoRows:
// No rows means we're creating the workspace. Take the creation lock.
innerRow := c.Client.QueryRow(`SELECT pg_try_advisory_lock(-1)`)
innerRow := c.Client.QueryRowContext(ctx, `SELECT pg_try_advisory_lock(-1)`)
var innerDidLock []byte
err := innerRow.Scan(&innerDidLock)
if err != nil {
@ -131,10 +131,10 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
return c.info, nil
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
if c.info != nil && c.info.Path != "" {
query := `SELECT pg_advisory_unlock(%s)`
row := c.Client.QueryRow(fmt.Sprintf(query, c.info.Path))
row := c.Client.QueryRowContext(ctx, fmt.Sprintf(query, c.info.Path))
var didUnlock []byte
err := row.Scan(&didUnlock)
if err != nil {

View File

@ -168,14 +168,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
// take a lock on this state while we write it
lockInfo := statemgr.NewLockInfo()
lockInfo.Operation = "init"
lockId, err := client.Lock(lockInfo)
lockId, err := client.Lock(ctx, lockInfo)
if err != nil {
return nil, fmt.Errorf("failed to lock s3 state: %w", err)
}
// Local helper function so we can call it multiple places
lockUnlock := func(parent error) error {
if err := stateMgr.Unlock(lockId); err != nil {
if err := stateMgr.Unlock(ctx, lockId); err != nil {
return fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
}
return parent

View File

@ -217,7 +217,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
return nil
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
if c.ddbTable == "" {
return "", nil
}
@ -242,7 +242,6 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
}
ctx := context.TODO()
_, err := c.dynClient.PutItem(ctx, putParams)
if err != nil {
lockInfo, infoErr := c.getLockInfo(ctx)
@ -368,13 +367,12 @@ func (c *RemoteClient) getLockInfo(ctx context.Context) (*statemgr.LockInfo, err
return lockInfo, nil
}
func (c *RemoteClient) Unlock(id string) error {
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
if c.ddbTable == "" {
return nil
}
lockErr := &statemgr.LockError{}
ctx := context.TODO()
// TODO: store the path and lock ID in separate fields, and have proper
// projection expression only delete the lock if both match, rather than

View File

@ -104,7 +104,7 @@ func TestForceUnlock(t *testing.T) {
"dynamodb_table": bucketName,
})).(*Backend)
ctx := context.TODO()
ctx := context.Background()
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
@ -120,7 +120,7 @@ func TestForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err := s1.Lock(info)
lockID, err := s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -131,7 +131,7 @@ func TestForceUnlock(t *testing.T) {
t.Fatal("failed to get default state to force unlock:", err)
}
if err := s2.Unlock(lockID); err != nil {
if err := s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock default state")
}
@ -146,7 +146,7 @@ func TestForceUnlock(t *testing.T) {
info.Operation = "test"
info.Who = "clientA"
lockID, err = s1.Lock(info)
lockID, err = s1.Lock(ctx, info)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -157,7 +157,7 @@ func TestForceUnlock(t *testing.T) {
t.Fatal("failed to get named state to force unlock:", err)
}
if err = s2.Unlock(lockID); err != nil {
if err = s2.Unlock(ctx, lockID); err != nil {
t.Fatal("failed to force-unlock named state")
}
}

View File

@ -919,10 +919,10 @@ func (b *Remote) IgnoreVersionConflict() {
// that there are no compatibility concerns, so it returns no diagnostics.
//
// If the versions differ,
func (b *Remote) VerifyWorkspaceTerraformVersion(workspaceName string) tfdiags.Diagnostics {
func (b *Remote) VerifyWorkspaceTerraformVersion(ctx context.Context, workspaceName string) tfdiags.Diagnostics {
var diags tfdiags.Diagnostics
workspace, err := b.getRemoteWorkspace(context.Background(), workspaceName)
workspace, err := b.getRemoteWorkspace(ctx, workspaceName)
if err != nil {
// If the workspace doesn't exist, there can be no compatibility
// problem, so we can return. This is most likely to happen when

View File

@ -113,7 +113,7 @@ func TestRemote_applyBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}
@ -144,7 +144,7 @@ func TestRemote_applyCanceled(t *testing.T) {
}
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after cancelling apply: %s", err.Error())
}
}
@ -635,7 +635,7 @@ func TestRemote_applyNoConfig(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after failed apply: %s", err.Error())
}
}

View File

@ -30,13 +30,13 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
},
}
op.StateLocker = op.StateLocker.WithContext(context.Background())
ctx := context.TODO()
op.StateLocker = op.StateLocker.WithContext(ctx)
// Get the remote workspace name.
remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace)
ctx := context.TODO()
// Get the latest state.
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", remoteWorkspaceName)
stateMgr, err := b.StateMgr(ctx, op.Workspace)
@ -97,13 +97,13 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
// The underlying API expects us to use the opaque workspace id to request
// variables, so we'll need to look that up using our organization name
// and workspace name.
remoteWorkspaceID, err := b.getRemoteWorkspaceID(context.Background(), op.Workspace)
remoteWorkspaceID, err := b.getRemoteWorkspaceID(ctx, op.Workspace)
if err != nil {
diags = diags.Append(fmt.Errorf("error finding remote workspace: %w", err))
return nil, nil, diags
}
w, err := b.fetchWorkspace(context.Background(), b.organization, op.Workspace)
w, err := b.fetchWorkspace(ctx, b.organization, op.Workspace)
if err != nil {
diags = diags.Append(fmt.Errorf("error loading workspace: %w", err))
return nil, nil, diags
@ -113,7 +113,7 @@ func (b *Remote) LocalRun(op *backend.Operation) (*backend.LocalRun, statemgr.Fu
log.Printf("[TRACE] skipping retrieving variables from workspace %s/%s (%s), workspace is in Local Execution mode", remoteWorkspaceName, b.organization, remoteWorkspaceID)
} else {
log.Printf("[TRACE] backend/remote: retrieving variables from workspace %s/%s (%s)", remoteWorkspaceName, b.organization, remoteWorkspaceID)
tfeVariables, err := b.client.Variables.List(context.Background(), remoteWorkspaceID, nil)
tfeVariables, err := b.client.Variables.List(ctx, remoteWorkspaceID, nil)
if err != nil && err != tfe.ErrResourceNotFound {
diags = diags.Append(fmt.Errorf("error loading variables: %w", err))
return nil, nil, diags

View File

@ -190,7 +190,9 @@ func TestRemoteContextWithVars(t *testing.T) {
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
defer configCleanup()
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
ctx := context.Background()
workspaceID, err := b.getRemoteWorkspaceID(ctx, backend.DefaultStateName)
if err != nil {
t.Fatal(err)
}
@ -211,8 +213,6 @@ func TestRemoteContextWithVars(t *testing.T) {
v.Key = &key
}
ctx := context.Background()
b.client.Variables.Create(ctx, workspaceID, *v)
_, _, diags := b.LocalRun(op)
@ -228,7 +228,7 @@ func TestRemoteContextWithVars(t *testing.T) {
// When Context() returns an error, it should unlock the state,
// so re-locking it is expected to succeed.
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state: %s", err.Error())
}
} else {
@ -237,7 +237,7 @@ func TestRemoteContextWithVars(t *testing.T) {
}
// When Context() succeeds, this should fail w/ "workspace already locked"
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err == nil {
t.Fatal("unexpected success locking state after Context")
}
}
@ -445,7 +445,7 @@ func TestRemoteVariablesDoNotOverride(t *testing.T) {
}
// When Context() succeeds, this should fail w/ "workspace already locked"
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err == nil {
t.Fatal("unexpected success locking state after Context")
}

View File

@ -98,7 +98,7 @@ func TestRemote_planBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
}
}
@ -130,7 +130,7 @@ func TestRemote_planCanceled(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after cancelled plan: %s", err.Error())
}
}

View File

@ -153,9 +153,7 @@ func (r *remoteClient) EnableForcePush() {
}
// Lock the remote state.
func (r *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
ctx := context.Background()
func (r *remoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
lockErr := &statemgr.LockError{Info: r.lockInfo}
// Lock the workspace.
@ -177,9 +175,7 @@ func (r *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}
// Unlock the remote state.
func (r *remoteClient) Unlock(id string) error {
ctx := context.Background()
func (r *remoteClient) Unlock(ctx context.Context, id string) error {
// We first check if there was an error while uploading the latest
// state. If so, we will not unlock the workspace to prevent any
// changes from being applied until the correct state is uploaded.

View File

@ -621,10 +621,12 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) {
tfversion.Version = local.String()
tfversion.SemVer = local
ctx := context.Background()
// Update the mock remote workspace OpenTofu version to the
// specified remote version
if _, err := b.client.Workspaces.Update(
context.Background(),
ctx,
b.organization,
b.workspace,
tfe.WorkspaceUpdateOptions{
@ -635,7 +637,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) {
t.Fatalf("error: %v", err)
}
diags := b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
diags := b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
if tc.wantErr {
if len(diags) != 1 {
t.Fatal("expected diag, but none returned")
@ -656,16 +658,18 @@ func TestRemote_VerifyWorkspaceTerraformVersion_workspaceErrors(t *testing.T) {
b, bCleanup := testBackendDefault(t)
defer bCleanup()
ctx := context.Background()
// Attempting to check the version against a workspace which doesn't exist
// should result in no errors
diags := b.VerifyWorkspaceTerraformVersion("invalid-workspace")
diags := b.VerifyWorkspaceTerraformVersion(ctx, "invalid-workspace")
if len(diags) != 0 {
t.Fatalf("unexpected error: %s", diags.Err())
}
// Use a special workspace ID to trigger a 500 error, which should result
// in a failed check
diags = b.VerifyWorkspaceTerraformVersion("network-error")
diags = b.VerifyWorkspaceTerraformVersion(ctx, "network-error")
if len(diags) != 1 {
t.Fatal("expected diag, but none returned")
}
@ -684,7 +688,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion_workspaceErrors(t *testing.T) {
); err != nil {
t.Fatalf("error: %v", err)
}
diags = b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
diags = b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
if len(diags) != 1 {
t.Fatal("expected diag, but none returned")
@ -720,10 +724,12 @@ func TestRemote_VerifyWorkspaceTerraformVersion_ignoreFlagSet(t *testing.T) {
tfversion.Version = local.String()
tfversion.SemVer = local
ctx := context.Background()
// Update the mock remote workspace OpenTofu version to the
// specified remote version
if _, err := b.client.Workspaces.Update(
context.Background(),
ctx,
b.organization,
b.workspace,
tfe.WorkspaceUpdateOptions{
@ -733,7 +739,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion_ignoreFlagSet(t *testing.T) {
t.Fatalf("error: %v", err)
}
diags := b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
diags := b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
if len(diags) != 1 {
t.Fatal("expected diag, but none returned")
}

View File

@ -350,7 +350,7 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
infoB.Operation = "test"
infoB.Who = "clientB"
lockIDA, err := lockerA.Lock(infoA)
lockIDA, err := lockerA.Lock(ctx, infoA)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
@ -369,17 +369,17 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
return
}
_, err = lockerB.Lock(infoB)
_, err = lockerB.Lock(ctx, infoB)
if err == nil {
lockerA.Unlock(lockIDA)
lockerA.Unlock(ctx, lockIDA)
t.Fatal("client B obtained lock while held by client A")
}
if err := lockerA.Unlock(lockIDA); err != nil {
if err := lockerA.Unlock(ctx, lockIDA); err != nil {
t.Fatal("error unlocking client A", err)
}
lockIDB, err := lockerB.Lock(infoB)
lockIDB, err := lockerB.Lock(ctx, infoB)
if err != nil {
t.Fatal("unable to obtain lock from client B")
}
@ -388,7 +388,7 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
t.Errorf("duplicate lock IDs: %q", lockIDB)
}
if err = lockerB.Unlock(lockIDB); err != nil {
if err = lockerB.Unlock(ctx, lockIDB); err != nil {
t.Fatal("error unlocking client B:", err)
}
@ -404,18 +404,18 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
panic(err)
}
lockIDA, err = lockerA.Lock(infoA)
lockIDA, err = lockerA.Lock(ctx, infoA)
if err != nil {
t.Fatal("unable to get re lock A:", err)
}
unlock := func() {
err := lockerA.Unlock(lockIDA)
err := lockerA.Unlock(ctx, lockIDA)
if err != nil {
t.Fatal(err)
}
}
_, err = lockerB.Lock(infoB)
_, err = lockerB.Lock(ctx, infoB)
if err == nil {
unlock()
t.Fatal("client B obtained lock while held by client A")
@ -428,7 +428,7 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
}
// try to unlock with the second unlocker, using the ID from the error
if err := lockerB.Unlock(infoErr.Info.ID); err != nil {
if err := lockerB.Unlock(ctx, infoErr.Info.ID); err != nil {
unlock()
t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
}

View File

@ -117,7 +117,7 @@ func TestCloud_applyBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}
@ -178,7 +178,7 @@ func TestCloud_applyJSONBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}
@ -268,7 +268,7 @@ func TestCloud_applyJSONWithOutputs(t *testing.T) {
}
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}
@ -299,7 +299,7 @@ func TestCloud_applyCanceled(t *testing.T) {
}
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after cancelling apply: %s", err.Error())
}
}
@ -513,7 +513,7 @@ func TestCloud_applyWithCloudPlan(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}
@ -731,7 +731,7 @@ func TestCloud_applyNoConfig(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after failed apply: %s", err.Error())
}
}
@ -1409,7 +1409,7 @@ func TestCloud_applyJSONWithProvisioner(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}

View File

@ -227,7 +227,7 @@ func TestRemoteContextWithVars(t *testing.T) {
// When Context() returns an error, it should unlock the state,
// so re-locking it is expected to succeed.
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state: %s", err.Error())
}
} else {
@ -236,7 +236,7 @@ func TestRemoteContextWithVars(t *testing.T) {
}
// When Context() succeeds, this should fail w/ "workspace already locked"
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err == nil {
t.Fatal("unexpected success locking state after Context")
}
}
@ -444,7 +444,7 @@ func TestRemoteVariablesDoNotOverride(t *testing.T) {
}
// When Context() succeeds, this should fail w/ "workspace already locked"
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err == nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err == nil {
t.Fatal("unexpected success locking state after Context")
}

View File

@ -101,7 +101,7 @@ func TestCloud_planBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
}
}
@ -148,7 +148,7 @@ func TestCloud_planJSONBasic(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
}
}
@ -179,7 +179,7 @@ func TestCloud_planCanceled(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after cancelled plan: %s", err.Error())
}
}
@ -262,7 +262,7 @@ func TestCloud_planJSONFull(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
}
}
@ -1337,7 +1337,7 @@ func TestCloud_planImportConfigGeneration(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after the operation finished
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after successful plan: %s", err.Error())
}

View File

@ -78,7 +78,7 @@ func TestCloud_refreshBasicActuallyRunsApplyRefresh(t *testing.T) {
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
// An error suggests that the state was not unlocked after apply
if _, err := stateMgr.Lock(statemgr.NewLockInfo()); err != nil {
if _, err := stateMgr.Lock(ctx, statemgr.NewLockInfo()); err != nil {
t.Fatalf("unexpected error locking state after apply: %s", err.Error())
}
}

View File

@ -330,14 +330,13 @@ func (s *State) uploadState(ctx context.Context, lineage string, serial uint64,
}
// Lock calls the Client's Lock method if it's implemented.
func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
func (s *State) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
if s.disableLocks {
return "", nil
}
ctx := context.Background()
lockErr := &statemgr.LockError{Info: s.lockInfo}
@ -434,7 +433,7 @@ func (s *State) getStatePayload(ctx context.Context) (*remote.Payload, error) {
}
// Unlock calls the Client's Unlock method if it's implemented.
func (s *State) Unlock(id string) error {
func (s *State) Unlock(ctx context.Context, id string) error {
s.mu.Lock()
defer s.mu.Unlock()
@ -442,8 +441,6 @@ func (s *State) Unlock(id string) error {
return nil
}
ctx := context.Background()
// We first check if there was an error while uploading the latest
// state. If so, we will not unlock the workspace to prevent any
// changes from being applied until the correct state is uploaded.

View File

@ -178,25 +178,25 @@ func TestCloudLocks(t *testing.T) {
infoB.Operation = "test"
infoB.Who = "clientB"
lockIDA, err := lockerA.Lock(infoA)
lockIDA, err := lockerA.Lock(ctx, infoA)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
_, err = lockerB.Lock(infoB)
_, err = lockerB.Lock(ctx, infoB)
if err == nil {
lockerA.Unlock(lockIDA)
lockerA.Unlock(ctx, lockIDA)
t.Fatal("client B obtained lock while held by client A")
}
if _, ok := err.(*statemgr.LockError); !ok {
t.Errorf("expected a LockError, but was %t: %s", err, err)
}
if err := lockerA.Unlock(lockIDA); err != nil {
if err := lockerA.Unlock(ctx, lockIDA); err != nil {
t.Fatal("error unlocking client A", err)
}
lockIDB, err := lockerB.Lock(infoB)
lockIDB, err := lockerB.Lock(ctx, infoB)
if err != nil {
t.Fatal("unable to obtain lock from client B")
}
@ -205,7 +205,7 @@ func TestCloudLocks(t *testing.T) {
t.Fatalf("duplicate lock IDs: %q", lockIDB)
}
if err = lockerB.Unlock(lockIDB); err != nil {
if err = lockerB.Unlock(ctx, lockIDB); err != nil {
t.Fatal("error unlocking client B:", err)
}
}

View File

@ -5,6 +5,7 @@ package clistate
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
@ -175,7 +176,7 @@ func (s *LocalState) RefreshState() error {
}
// Lock implements a local filesystem state.Locker.
func (s *LocalState) Lock(info *statemgr.LockInfo) (string, error) {
func (s *LocalState) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
@ -207,7 +208,7 @@ func (s *LocalState) Lock(info *statemgr.LockInfo) (string, error) {
return s.lockID, s.writeLockInfo(info)
}
func (s *LocalState) Unlock(id string) error {
func (s *LocalState) Unlock(ctx context.Context, id string) error {
s.mu.Lock()
defer s.mu.Unlock()

View File

@ -148,7 +148,7 @@ func (l *locker) Unlock() tfdiags.Diagnostics {
}
err := slowmessage.Do(LockThreshold, func() error {
return l.state.Unlock(l.lockID)
return l.state.Unlock(l.ctx, l.lockID)
}, l.view.Unlocking)
if err != nil {

View File

@ -4,6 +4,7 @@
package main
import (
"context"
"io"
"log"
"os"
@ -28,7 +29,9 @@ func main() {
info.Operation = "test"
info.Info = "state locker"
lockID, err := s.Lock(info)
ctx := context.Background()
lockID, err := s.Lock(ctx, info)
if err != nil {
io.WriteString(os.Stderr, err.Error())
return
@ -38,7 +41,7 @@ func main() {
io.WriteString(os.Stdout, "LOCKID "+lockID)
defer func() {
if err := s.Unlock(lockID); err != nil {
if err := s.Unlock(ctx, lockID); err != nil {
io.WriteString(os.Stderr, err.Error())
}
}()

View File

@ -97,7 +97,7 @@ func (c *UnlockCommand) Run(args []string) int {
"This will allow local OpenTofu commands to modify this state, even though it\n" +
"may still be in use. Only 'yes' will be accepted to confirm."
v, err := c.UIInput().Input(context.Background(), &tofu.InputOpts{
v, err := c.UIInput().Input(ctx, &tofu.InputOpts{
Id: "force-unlock",
Query: "Do you really want to force-unlock?",
Description: desc,
@ -112,7 +112,7 @@ func (c *UnlockCommand) Run(args []string) int {
}
}
if err := stateMgr.Unlock(lockID); err != nil {
if err := stateMgr.Unlock(ctx, lockID); err != nil {
c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
return 1
}

View File

@ -237,7 +237,7 @@ func (s *State) ShouldPersistIntermediateState(info *local.IntermediateStatePers
}
// Lock calls the Client's Lock method if it's implemented.
func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
func (s *State) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
s.mu.Lock()
defer s.mu.Unlock()
@ -246,13 +246,13 @@ func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
}
if c, ok := s.Client.(ClientLocker); ok {
return c.Lock(info)
return c.Lock(ctx, info)
}
return "", nil
}
// Unlock calls the Client's Unlock method if it's implemented.
func (s *State) Unlock(id string) error {
func (s *State) Unlock(ctx context.Context, id string) error {
s.mu.Lock()
defer s.mu.Unlock()
@ -261,7 +261,7 @@ func (s *State) Unlock(id string) error {
}
if c, ok := s.Client.(ClientLocker); ok {
return c.Unlock(id)
return c.Unlock(ctx, id)
}
return nil
}

View File

@ -73,25 +73,27 @@ func TestRemoteLocks(t *testing.T, a, b Client) {
infoB.Operation = "test"
infoB.Who = "clientB"
lockIDA, err := lockerA.Lock(infoA)
ctx := context.Background()
lockIDA, err := lockerA.Lock(ctx, infoA)
if err != nil {
t.Fatal("unable to get initial lock:", err)
}
_, err = lockerB.Lock(infoB)
_, err = lockerB.Lock(ctx, infoB)
if err == nil {
lockerA.Unlock(lockIDA)
lockerA.Unlock(ctx, lockIDA)
t.Fatal("client B obtained lock while held by client A")
}
if _, ok := err.(*statemgr.LockError); !ok {
t.Errorf("expected a LockError, but was %t: %s", err, err)
}
if err := lockerA.Unlock(lockIDA); err != nil {
if err := lockerA.Unlock(ctx, lockIDA); err != nil {
t.Fatal("error unlocking client A", err)
}
lockIDB, err := lockerB.Lock(infoB)
lockIDB, err := lockerB.Lock(ctx, infoB)
if err != nil {
t.Fatal("unable to obtain lock from client B")
}
@ -100,7 +102,7 @@ func TestRemoteLocks(t *testing.T, a, b Client) {
t.Fatalf("duplicate lock IDs: %q", lockIDB)
}
if err = lockerB.Unlock(lockIDB); err != nil {
if err = lockerB.Unlock(ctx, lockIDB); err != nil {
t.Fatal("error unlocking client B:", err)
}

View File

@ -314,7 +314,7 @@ func (s *Filesystem) refreshState() error {
}
// Lock implements Locker using filesystem discretionary locks.
func (s *Filesystem) Lock(info *LockInfo) (string, error) {
func (s *Filesystem) Lock(_ context.Context, info *LockInfo) (string, error) {
defer s.mutex()()
if s.stateFileOut == nil {
@ -345,8 +345,8 @@ func (s *Filesystem) Lock(info *LockInfo) (string, error) {
return s.lockID, s.writeLockInfo(info)
}
// Unlock is the companion to Lock, completing the implemention of Locker.
func (s *Filesystem) Unlock(id string) error {
// Unlock is the companion to Lock, completing the implementation of Locker.
func (s *Filesystem) Unlock(_ context.Context, id string) error {
defer s.mutex()()
if s.lockID == "" {

View File

@ -52,10 +52,12 @@ func TestFilesystemLocks(t *testing.T) {
s := testFilesystem(t)
defer os.Remove(s.readPath)
ctx := context.Background()
// lock first
info := NewLockInfo()
info.Operation = "test"
lockID, err := s.Lock(info)
lockID, err := s.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
@ -80,22 +82,22 @@ func TestFilesystemLocks(t *testing.T) {
}
// a noop, since we unlock on exit
if err := s.Unlock(lockID); err != nil {
if err := s.Unlock(ctx, lockID); err != nil {
t.Fatal(err)
}
// local locks can re-lock
lockID, err = s.Lock(info)
lockID, err = s.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
if err := s.Unlock(lockID); err != nil {
if err := s.Unlock(ctx, lockID); err != nil {
t.Fatal(err)
}
// we should not be able to unlock the same lock twice
if err := s.Unlock(lockID); err == nil {
if err := s.Unlock(ctx, lockID); err == nil {
t.Fatal("unlocking an unlocked state should fail")
}
@ -113,15 +115,17 @@ func TestFilesystem_writeWhileLocked(t *testing.T) {
s := testFilesystem(t)
defer os.Remove(s.readPath)
ctx := context.Background()
// lock first
info := NewLockInfo()
info.Operation = "test"
lockID, err := s.Lock(info)
lockID, err := s.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := s.Unlock(lockID); err != nil {
if err := s.Unlock(ctx, lockID); err != nil {
t.Fatal(err)
}
}()
@ -307,8 +311,10 @@ func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) {
// Delete the just-created tempfile so that Lock recreates it
os.Remove(ls.path)
ctx := context.Background()
// Lock the state, and in doing so recreate the tempfile
lockID, err := ls.Lock(info)
lockID, err := ls.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
@ -317,7 +323,7 @@ func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) {
t.Fatal("should have marked state as created")
}
if err := ls.Unlock(lockID); err != nil {
if err := ls.Unlock(ctx, lockID); err != nil {
t.Fatal(err)
}
@ -391,15 +397,17 @@ func TestFilesystem_refreshWhileLocked(t *testing.T) {
s := NewFilesystem(f.Name())
defer os.Remove(s.path)
ctx := context.Background()
// lock first
info := NewLockInfo()
info.Operation = "test"
lockID, err := s.Lock(info)
lockID, err := s.Lock(ctx, info)
if err != nil {
t.Fatal(err)
}
defer func() {
if err := s.Unlock(lockID); err != nil {
if err := s.Unlock(ctx, lockID); err != nil {
t.Fatal(err)
}
}()

View File

@ -39,10 +39,10 @@ func (s *LockDisabled) PersistState(ctx context.Context, schemas *tofu.Schemas)
return s.Inner.PersistState(ctx, schemas)
}
func (s *LockDisabled) Lock(info *LockInfo) (string, error) {
func (s *LockDisabled) Lock(ctx context.Context, info *LockInfo) (string, error) {
return "", nil
}
func (s *LockDisabled) Unlock(id string) error {
func (s *LockDisabled) Unlock(ctx context.Context, id string) error {
return nil
}

View File

@ -53,7 +53,7 @@ type Locker interface {
// an instance of LockError immediately if the lock is already held,
// and the helper function LockWithContext uses this to automatically
// retry lock acquisition periodically until a timeout is reached.
Lock(info *LockInfo) (string, error)
Lock(ctx context.Context, info *LockInfo) (string, error)
// Unlock releases a lock previously acquired by Lock.
//
@ -61,7 +61,7 @@ type Locker interface {
// another user with some sort of administrative override privilege --
// then an error is returned explaining the situation in a way that
// is suitable for returning to an end-user.
Unlock(id string) error
Unlock(ctx context.Context, id string) error
}
// test hook to verify that LockWithContext has attempted a lock
@ -76,7 +76,7 @@ func LockWithContext(ctx context.Context, s Locker, info *LockInfo) (string, err
delay := time.Second
maxDelay := 16 * time.Second
for {
id, err := s.Lock(info)
id, err := s.Lock(ctx, info)
if err == nil {
return id, nil
}

View File

@ -74,7 +74,7 @@ func (m *fakeFull) GetRootOutputValues(ctx context.Context) (map[string]*states.
return m.State().RootModule().OutputValues, nil
}
func (m *fakeFull) Lock(info *LockInfo) (string, error) {
func (m *fakeFull) Lock(_ context.Context, info *LockInfo) (string, error) {
m.lockLock.Lock()
defer m.lockLock.Unlock()
@ -89,7 +89,7 @@ func (m *fakeFull) Lock(info *LockInfo) (string, error) {
return "placeholder", nil
}
func (m *fakeFull) Unlock(id string) error {
func (m *fakeFull) Unlock(_ context.Context, id string) error {
m.lockLock.Lock()
defer m.lockLock.Unlock()
@ -136,10 +136,10 @@ func (m *fakeErrorFull) PersistState(_ context.Context, schemas *tofu.Schemas) e
return errors.New("fake state manager error")
}
func (m *fakeErrorFull) Lock(info *LockInfo) (string, error) {
func (m *fakeErrorFull) Lock(_ context.Context, info *LockInfo) (string, error) {
return "placeholder", nil
}
func (m *fakeErrorFull) Unlock(id string) error {
func (m *fakeErrorFull) Unlock(_ context.Context, id string) error {
return errors.New("fake state manager error")
}

View File

@ -45,7 +45,7 @@ func TestNewLockInfo(t *testing.T) {
func TestLockWithContext(t *testing.T) {
s := NewFullFake(nil, TestFullInitialState())
id, err := s.Lock(NewLockInfo())
id, err := s.Lock(context.Background(), NewLockInfo())
if err != nil {
t.Fatal(err)
}
@ -61,7 +61,7 @@ func TestLockWithContext(t *testing.T) {
t.Fatal("lock should have failed immediately")
}
// block until LockwithContext has made a first attempt
// block until LockWithContext has made a first attempt
attempted := make(chan struct{})
postLockHook = func() {
close(attempted)
@ -74,7 +74,7 @@ func TestLockWithContext(t *testing.T) {
go func() {
defer close(unlocked)
<-attempted
unlockErr = s.Unlock(id)
unlockErr = s.Unlock(context.Background(), id)
}()
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
@ -85,7 +85,7 @@ func TestLockWithContext(t *testing.T) {
t.Fatal("lock should have completed within 2s:", err)
}
// ensure the goruotine completes
// ensure the goroutine completes
<-unlocked
if unlockErr != nil {
t.Fatal(unlockErr)

View File

@ -1,6 +1,7 @@
package main
import (
"context"
"io"
"log"
"os"
@ -21,7 +22,9 @@ func main() {
info.Operation = "test"
info.Info = "state locker"
_, err := s.Lock(info)
ctx := context.Background()
_, err := s.Lock(ctx, info)
if err != nil {
io.WriteString(os.Stderr, "lock failed")
}