mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
Pass context to all statemgr.Locker operations (#789)
Signed-off-by: Marcin Wyszynski <marcin.pixie@gmail.com>
This commit is contained in:
parent
275dd116f9
commit
772ac1fc35
@ -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")
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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:
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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 {
|
||||
|
7
internal/command/testdata/statelocker.go
vendored
7
internal/command/testdata/statelocker.go
vendored
@ -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())
|
||||
}
|
||||
}()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
|
@ -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 == "" {
|
||||
|
@ -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)
|
||||
}
|
||||
}()
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user