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
|
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 {
|
if s.locked {
|
||||||
return "", fmt.Errorf("already locked")
|
return "", fmt.Errorf("already locked")
|
||||||
}
|
}
|
||||||
@ -251,7 +251,7 @@ func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, er
|
|||||||
return "locked", nil
|
return "locked", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stateStorageThatFailsRefresh) Unlock(id string) error {
|
func (s *stateStorageThatFailsRefresh) Unlock(_ context.Context, id string) error {
|
||||||
if !s.locked {
|
if !s.locked {
|
||||||
return fmt.Errorf("not locked")
|
return fmt.Errorf("not locked")
|
||||||
}
|
}
|
||||||
|
@ -208,7 +208,7 @@ func assertBackendStateUnlocked(t *testing.T, b *Local) bool {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
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())
|
t.Errorf("state is already locked: %s", err.Error())
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -224,7 +224,7 @@ func assertBackendStateLocked(t *testing.T, b *Local) bool {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
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
|
return true
|
||||||
}
|
}
|
||||||
t.Error("unexpected success locking state")
|
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),
|
UseAzureADAuthentication: data.Get("use_azuread_auth").(bool),
|
||||||
}
|
}
|
||||||
|
|
||||||
armClient, err := buildArmClient(context.TODO(), config)
|
armClient, err := buildArmClient(ctx, config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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
|
// take a lock on this state while we write it
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := client.Lock(lockInfo)
|
lockId, err := client.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock azure state: %w", err)
|
return nil, fmt.Errorf("failed to lock azure state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(parent error) error {
|
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 fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||||
}
|
}
|
||||||
return parent
|
return parent
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
|
|
||||||
"github.com/hashicorp/go-multierror"
|
"github.com/hashicorp/go-multierror"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
|
|
||||||
"github.com/opentofu/opentofu/internal/states/remote"
|
"github.com/opentofu/opentofu/internal/states/remote"
|
||||||
"github.com/opentofu/opentofu/internal/states/statemgr"
|
"github.com/opentofu/opentofu/internal/states/statemgr"
|
||||||
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
"github.com/tombuildsstuff/giovanni/storage/2018-11-09/blob/blobs"
|
||||||
@ -115,7 +116,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
|
|||||||
return nil
|
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)
|
stateName := fmt.Sprintf("%s/%s", c.containerName, c.keyName)
|
||||||
info.Path = stateName
|
info.Path = stateName
|
||||||
|
|
||||||
@ -129,7 +130,7 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getLockInfoErr := func(err error) error {
|
getLockInfoErr := func(err error) error {
|
||||||
lockInfo, infoErr := c.getLockInfo()
|
lockInfo, infoErr := c.getLockInfo(ctx)
|
||||||
if infoErr != nil {
|
if infoErr != nil {
|
||||||
err = multierror.Append(err, infoErr)
|
err = multierror.Append(err, infoErr)
|
||||||
}
|
}
|
||||||
@ -144,7 +145,6 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
ProposedLeaseID: &info.ID,
|
ProposedLeaseID: &info.ID,
|
||||||
LeaseDuration: -1,
|
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
|
// 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{})
|
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
|
info.ID = leaseID.LeaseID
|
||||||
c.leaseID = leaseID.LeaseID
|
c.leaseID = leaseID.LeaseID
|
||||||
|
|
||||||
if err := c.writeLockInfo(info); err != nil {
|
if err := c.writeLockInfo(ctx, info); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return info.ID, nil
|
return info.ID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
func (c *RemoteClient) getLockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
|
||||||
options := blobs.GetPropertiesInput{}
|
options := blobs.GetPropertiesInput{}
|
||||||
if c.leaseID != "" {
|
if c.leaseID != "" {
|
||||||
options.LeaseID = &c.leaseID
|
options.LeaseID = &c.leaseID
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, options)
|
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// writes info to blob meta data, deletes metadata entry if info is nil
|
||||||
func (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {
|
func (c *RemoteClient) writeLockInfo(ctx context.Context, info *statemgr.LockInfo) error {
|
||||||
ctx := context.TODO()
|
|
||||||
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, blobs.GetPropertiesInput{LeaseID: &c.leaseID})
|
blob, err := c.giovanniBlobClient.GetProperties(ctx, c.accountName, c.containerName, c.keyName, blobs.GetPropertiesInput{LeaseID: &c.leaseID})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -244,10 +242,10 @@ func (c *RemoteClient) writeLockInfo(info *statemgr.LockInfo) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) Unlock(id string) error {
|
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
|
||||||
lockErr := &statemgr.LockError{}
|
lockErr := &statemgr.LockError{}
|
||||||
|
|
||||||
lockInfo, err := c.getLockInfo()
|
lockInfo, err := c.getLockInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %w", err)
|
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %w", err)
|
||||||
return lockErr
|
return lockErr
|
||||||
@ -260,12 +258,11 @@ func (c *RemoteClient) Unlock(id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.leaseID = lockInfo.ID
|
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)
|
lockErr.Err = fmt.Errorf("failed to delete lock info from metadata: %w", err)
|
||||||
return lockErr
|
return lockErr
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
_, err = c.giovanniBlobClient.ReleaseLease(ctx, c.accountName, c.containerName, c.keyName, id)
|
_, err = c.giovanniBlobClient.ReleaseLease(ctx, c.accountName, c.containerName, c.keyName, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lockErr.Err = err
|
lockErr.Err = err
|
||||||
|
@ -98,14 +98,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
|
|||||||
// so States() knows it exists.
|
// so States() knows it exists.
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := stateMgr.Lock(lockInfo)
|
lockId, err := stateMgr.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock state in Consul: %w", err)
|
return nil, fmt.Errorf("failed to lock state in Consul: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(parent error) error {
|
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 fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,7 +353,7 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
|||||||
return li, nil
|
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()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
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.
|
// the lock implementation.
|
||||||
// Only to be called while holding Client.mu
|
// 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
|
// We create a new session here, so it can be canceled when the lock is
|
||||||
// lost or unlocked.
|
// lost or unlocked.
|
||||||
lockSession, err := c.createSession()
|
lockSession, err := c.createSession(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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,
|
// If we lose the lock to due communication issues with the consul agent,
|
||||||
// attempt to immediately reacquire the lock. Put will verify the integrity
|
// attempt to immediately reacquire the lock. Put will verify the integrity
|
||||||
// of the state by using a CAS operation.
|
// of the state by using a CAS operation.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
c.monitorCancel = cancel
|
c.monitorCancel = cancel
|
||||||
c.monitorWG.Add(1)
|
c.monitorWG.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
@ -477,7 +477,7 @@ func (c *RemoteClient) lock() (string, error) {
|
|||||||
c.sessionCancel()
|
c.sessionCancel()
|
||||||
|
|
||||||
c.consulLock = nil
|
c.consulLock = nil
|
||||||
_, err := c.lock()
|
_, err := c.lock(ctx)
|
||||||
c.mu.Unlock()
|
c.mu.Unlock()
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -516,10 +516,10 @@ func (c *RemoteClient) lock() (string, error) {
|
|||||||
// called after a lock is acquired
|
// called after a lock is acquired
|
||||||
var testLockHook func()
|
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
|
// create the context first. Even if the session creation fails, we assume
|
||||||
// that the CancelFunc is always callable.
|
// that the CancelFunc is always callable.
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
c.sessionCancel = cancel
|
c.sessionCancel = cancel
|
||||||
|
|
||||||
session := c.Client.Session()
|
session := c.Client.Session()
|
||||||
@ -542,7 +542,7 @@ func (c *RemoteClient) createSession() (string, error) {
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) Unlock(id string) error {
|
func (c *RemoteClient) Unlock(_ context.Context, id string) error {
|
||||||
c.mu.Lock()
|
c.mu.Lock()
|
||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
|
|
||||||
|
@ -314,14 +314,14 @@ func TestConsul_destroyLock(t *testing.T) {
|
|||||||
clientA := s.(*remote.State).Client.(*RemoteClient)
|
clientA := s.(*remote.State).Client.(*RemoteClient)
|
||||||
|
|
||||||
info := statemgr.NewLockInfo()
|
info := statemgr.NewLockInfo()
|
||||||
id, err := clientA.Lock(info)
|
id, err := clientA.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockPath := clientA.Path + lockSuffix
|
lockPath := clientA.Path + lockSuffix
|
||||||
|
|
||||||
if err := clientA.Unlock(id); err != nil {
|
if err := clientA.Unlock(ctx, id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -337,18 +337,18 @@ func TestConsul_destroyLock(t *testing.T) {
|
|||||||
clientB := s.(*remote.State).Client.(*RemoteClient)
|
clientB := s.(*remote.State).Client.(*RemoteClient)
|
||||||
|
|
||||||
info = statemgr.NewLockInfo()
|
info = statemgr.NewLockInfo()
|
||||||
id, err = clientA.Lock(info)
|
id, err = clientA.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := clientB.Unlock(id); err != nil {
|
if err := clientB.Unlock(ctx, id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
testLock(clientA, lockPath)
|
testLock(clientA, lockPath)
|
||||||
|
|
||||||
err = clientA.Unlock(id)
|
err = clientA.Unlock(ctx, id)
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("consul lock should have been lost")
|
t.Fatal("consul lock should have been lost")
|
||||||
@ -386,7 +386,7 @@ func TestConsul_lostLock(t *testing.T) {
|
|||||||
|
|
||||||
info := statemgr.NewLockInfo()
|
info := statemgr.NewLockInfo()
|
||||||
info.Operation = "test-lost-lock"
|
info.Operation = "test-lost-lock"
|
||||||
id, err := sA.Lock(info)
|
id, err := sA.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -406,7 +406,7 @@ func TestConsul_lostLock(t *testing.T) {
|
|||||||
|
|
||||||
<-reLocked
|
<-reLocked
|
||||||
|
|
||||||
if err := sA.Unlock(id); err != nil {
|
if err := sA.Unlock(ctx, id); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -439,7 +439,7 @@ func TestConsul_lostLockConnection(t *testing.T) {
|
|||||||
|
|
||||||
info := statemgr.NewLockInfo()
|
info := statemgr.NewLockInfo()
|
||||||
info.Operation = "test-lost-lock-connection"
|
info.Operation = "test-lost-lock-connection"
|
||||||
id, err := s.Lock(info)
|
id, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -453,7 +453,7 @@ func TestConsul_lostLockConnection(t *testing.T) {
|
|||||||
<-dialed
|
<-dialed
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Unlock(id); err != nil {
|
if err := s.Unlock(ctx, id); err != nil {
|
||||||
t.Fatal("unlock error:", err)
|
t.Fatal("unlock error:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,10 +38,9 @@ type Backend struct {
|
|||||||
*schema.Backend
|
*schema.Backend
|
||||||
credential *common.Credential
|
credential *common.Credential
|
||||||
|
|
||||||
cosContext context.Context
|
cosClient *cos.Client
|
||||||
cosClient *cos.Client
|
tagClient *tag.Client
|
||||||
tagClient *tag.Client
|
stsClient *sts.Client
|
||||||
stsClient *sts.Client
|
|
||||||
|
|
||||||
region string
|
region string
|
||||||
bucket string
|
bucket string
|
||||||
@ -207,8 +206,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
b.cosContext = ctx
|
data := schema.FromContextBackendConfig(ctx)
|
||||||
data := schema.FromContextBackendConfig(b.cosContext)
|
|
||||||
|
|
||||||
b.region = data.Get("region").(string)
|
b.region = data.Get("region").(string)
|
||||||
b.bucket = data.Get("bucket").(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
|
// take a lock on this state while we write it
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := c.Lock(lockInfo)
|
lockId, err := c.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to lock cos state: %w", err)
|
return nil, fmt.Errorf("Failed to lock cos state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(e error) error {
|
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 fmt.Errorf(unlockErrMsg, err, lockId)
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
@ -152,14 +152,13 @@ func (b *Backend) client(name string) (*remoteClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &remoteClient{
|
return &remoteClient{
|
||||||
cosContext: b.cosContext,
|
cosClient: b.cosClient,
|
||||||
cosClient: b.cosClient,
|
tagClient: b.tagClient,
|
||||||
tagClient: b.tagClient,
|
bucket: b.bucket,
|
||||||
bucket: b.bucket,
|
stateFile: b.stateFile(name),
|
||||||
stateFile: b.stateFile(name),
|
lockFile: b.lockFile(name),
|
||||||
lockFile: b.lockFile(name),
|
encrypt: b.encrypt,
|
||||||
encrypt: b.encrypt,
|
acl: b.acl,
|
||||||
acl: b.acl,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,9 +28,6 @@ const (
|
|||||||
|
|
||||||
// RemoteClient implements the client of remote state
|
// RemoteClient implements the client of remote state
|
||||||
type remoteClient struct {
|
type remoteClient struct {
|
||||||
// TODO: remove once all methods are using context passed via the CLI.
|
|
||||||
cosContext context.Context
|
|
||||||
|
|
||||||
cosClient *cos.Client
|
cosClient *cos.Client
|
||||||
tagClient *tag.Client
|
tagClient *tag.Client
|
||||||
|
|
||||||
@ -77,74 +74,74 @@ func (c *remoteClient) Delete(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock lock remote state file for writing
|
// 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)
|
log.Printf("[DEBUG] lock remote state file %s", c.lockFile)
|
||||||
|
|
||||||
err := c.cosLock(c.bucket, c.lockFile)
|
err := c.cosLock(c.bucket, c.lockFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", c.lockError(err)
|
return "", c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
defer c.cosUnlock(c.bucket, c.lockFile)
|
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 {
|
if err != nil {
|
||||||
return "", c.lockError(err)
|
return "", c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
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
|
info.Path = c.lockFile
|
||||||
data, err := json.Marshal(info)
|
data, err := json.Marshal(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", c.lockError(err)
|
return "", c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
check := fmt.Sprintf("%x", md5.Sum(data))
|
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 {
|
if err != nil {
|
||||||
return "", c.lockError(err)
|
return "", c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return check, nil
|
return check, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock unlock remote state file
|
// Unlock unlocks remote state file
|
||||||
func (c *remoteClient) Unlock(check string) error {
|
func (c *remoteClient) Unlock(ctx context.Context, check string) error {
|
||||||
log.Printf("[DEBUG] unlock remote state file %s", c.lockFile)
|
log.Printf("[DEBUG] unlock remote state file %s", c.lockFile)
|
||||||
|
|
||||||
info, err := c.lockInfo()
|
info, err := c.lockInfo(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.lockError(err)
|
return c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if info.ID != check {
|
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 {
|
if err != nil {
|
||||||
return c.lockError(err)
|
return c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.cosUnlock(c.bucket, c.lockFile)
|
err = c.cosUnlock(c.bucket, c.lockFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.lockError(err)
|
return c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// lockError returns statemgr.LockError
|
// 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)
|
log.Printf("[DEBUG] failed to lock or unlock %s: %v", c.lockFile, err)
|
||||||
|
|
||||||
lockErr := &statemgr.LockError{
|
lockErr := &statemgr.LockError{
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
|
|
||||||
info, infoErr := c.lockInfo()
|
info, infoErr := c.lockInfo(ctx)
|
||||||
if infoErr != nil {
|
if infoErr != nil {
|
||||||
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
|
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
|
||||||
} else {
|
} else {
|
||||||
@ -155,8 +152,8 @@ func (c *remoteClient) lockError(err error) *statemgr.LockError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// lockInfo returns LockInfo from lock file
|
// lockInfo returns LockInfo from lock file
|
||||||
func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
|
func (c *remoteClient) lockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
|
||||||
exists, data, checksum, err := c.getObject(c.cosContext, c.lockFile)
|
exists, data, checksum, err := c.getObject(ctx, c.lockFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -238,7 +235,7 @@ func (c *remoteClient) putObject(ctx context.Context, cosFile string, data []byt
|
|||||||
}
|
}
|
||||||
|
|
||||||
r := bytes.NewReader(data)
|
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 {
|
if rsp == nil {
|
||||||
log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err)
|
log.Printf("[DEBUG] putObject %s: error: %v", cosFile, err)
|
||||||
return fmt.Errorf("failed to save file to %v: %w", cosFile, err)
|
return fmt.Errorf("failed to save file to %v: %w", cosFile, err)
|
||||||
|
@ -30,9 +30,6 @@ type Backend struct {
|
|||||||
|
|
||||||
storageClient *storage.Client
|
storageClient *storage.Client
|
||||||
|
|
||||||
// TODO: Remove storageContext once all methods are accepting a context.
|
|
||||||
storageContext context.Context
|
|
||||||
|
|
||||||
bucketName string
|
bucketName string
|
||||||
prefix string
|
prefix string
|
||||||
|
|
||||||
@ -129,13 +126,7 @@ func (b *Backend) configure(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ctx is a background context with the backend config added.
|
data := schema.FromContextBackendConfig(ctx)
|
||||||
// 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)
|
|
||||||
|
|
||||||
b.bucketName = data.Get("bucket").(string)
|
b.bucketName = data.Get("bucket").(string)
|
||||||
b.prefix = strings.TrimLeft(data.Get("prefix").(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))
|
endpoint := option.WithEndpoint(storageEndpoint.(string))
|
||||||
opts = append(opts, endpoint)
|
opts = append(opts, endpoint)
|
||||||
}
|
}
|
||||||
client, err := storage.NewClient(b.storageContext, opts...)
|
client, err := storage.NewClient(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("storage.NewClient() failed: %w", err)
|
return fmt.Errorf("storage.NewClient() failed: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -79,13 +79,12 @@ func (b *Backend) client(name string) (*remoteClient, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &remoteClient{
|
return &remoteClient{
|
||||||
storageContext: b.storageContext,
|
storageClient: b.storageClient,
|
||||||
storageClient: b.storageClient,
|
bucketName: b.bucketName,
|
||||||
bucketName: b.bucketName,
|
stateFilePath: b.stateFile(name),
|
||||||
stateFilePath: b.stateFile(name),
|
lockFilePath: b.lockFile(name),
|
||||||
lockFilePath: b.lockFile(name),
|
encryptionKey: b.encryptionKey,
|
||||||
encryptionKey: b.encryptionKey,
|
kmsKeyName: b.kmsKeyName,
|
||||||
kmsKeyName: b.kmsKeyName,
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,14 +108,14 @@ func (b *Backend) StateMgr(ctx context.Context, name string) (statemgr.Full, err
|
|||||||
|
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockID, err := st.Lock(lockInfo)
|
lockID, err := st.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
unlock := func(baseErr error) error {
|
unlock := func(baseErr error) error {
|
||||||
if err := st.Unlock(lockID); err != nil {
|
if err := st.Unlock(ctx, lockID); err != nil {
|
||||||
const unlockErrMsg = `%v
|
const unlockErrMsg = `%v
|
||||||
Additionally, unlocking the state file on Google Cloud Storage failed:
|
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))
|
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config))
|
||||||
be := b.(*Backend)
|
be := b.(*Backend)
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// create the bucket if it doesn't exist
|
// create the bucket if it doesn't exist
|
||||||
bkt := be.storageClient.Bucket(bucket)
|
bkt := be.storageClient.Bucket(bucket)
|
||||||
_, err := bkt.Attrs(be.storageContext)
|
_, err := bkt.Attrs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err != storage.ErrBucketNotExist {
|
if err != storage.ErrBucketNotExist {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@ -256,7 +257,7 @@ func setupBackend(t *testing.T, bucket, prefix, key, kmsName string) backend.Bac
|
|||||||
attrs := &storage.BucketAttrs{
|
attrs := &storage.BucketAttrs{
|
||||||
Location: os.Getenv("GOOGLE_REGION"),
|
Location: os.Getenv("GOOGLE_REGION"),
|
||||||
}
|
}
|
||||||
err := bkt.Create(be.storageContext, projectID, attrs)
|
err := bkt.Create(ctx, projectID, attrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -386,7 +387,7 @@ func teardownBackend(t *testing.T, be backend.Backend, prefix string) {
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("be is a %T, want a *gcsBackend", be)
|
t.Fatalf("be is a %T, want a *gcsBackend", be)
|
||||||
}
|
}
|
||||||
ctx := gcsBE.storageContext
|
ctx := context.Background()
|
||||||
|
|
||||||
bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
|
bucket := gcsBE.storageClient.Bucket(gcsBE.bucketName)
|
||||||
objs := bucket.Objects(ctx, nil)
|
objs := bucket.Objects(ctx, nil)
|
||||||
|
@ -20,9 +20,6 @@ import (
|
|||||||
// blobs representing state.
|
// blobs representing state.
|
||||||
// Implements "state/remote".ClientLocker
|
// Implements "state/remote".ClientLocker
|
||||||
type remoteClient struct {
|
type remoteClient struct {
|
||||||
// TODO: remove this once all methods are accepting an explicit context
|
|
||||||
storageContext context.Context
|
|
||||||
|
|
||||||
storageClient *storage.Client
|
storageClient *storage.Client
|
||||||
bucketName string
|
bucketName string
|
||||||
stateFilePath 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)
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Failed to read state file attrs from %v: %w", c.stateFileURL(), err)
|
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
|
// Lock writes to a lock file, ensuring file creation. Returns the generation
|
||||||
// number, which must be passed to Unlock().
|
// 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
|
// update the path we're using
|
||||||
// we can't set the ID until the info is written
|
// we can't set the ID until the info is written
|
||||||
info.Path = c.lockFileURL()
|
info.Path = c.lockFileURL()
|
||||||
@ -99,7 +96,7 @@ func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
lockFile := c.lockFile()
|
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 {
|
err = func() error {
|
||||||
if _, err := w.Write(infoJson); err != nil {
|
if _, err := w.Write(infoJson); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -108,7 +105,7 @@ func (c *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
if err != nil {
|
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)
|
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
|
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)
|
gen, err := strconv.ParseInt(id, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Lock ID should be numerical value, got '%s'", id)
|
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 {
|
if err := c.lockFile().If(storage.Conditions{GenerationMatch: gen}).Delete(ctx); err != nil {
|
||||||
return c.lockError(err)
|
return c.lockError(ctx, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *remoteClient) lockError(err error) *statemgr.LockError {
|
func (c *remoteClient) lockError(ctx context.Context, err error) *statemgr.LockError {
|
||||||
lockErr := &statemgr.LockError{
|
lockErr := &statemgr.LockError{
|
||||||
Err: err,
|
Err: err,
|
||||||
}
|
}
|
||||||
|
|
||||||
info, infoErr := c.lockInfo()
|
info, infoErr := c.lockInfo(ctx)
|
||||||
if infoErr != nil {
|
if infoErr != nil {
|
||||||
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
|
lockErr.Err = multierror.Append(lockErr.Err, infoErr)
|
||||||
} else {
|
} 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 reads the lock file, parses its contents and returns the parsed
|
||||||
// LockInfo struct.
|
// LockInfo struct.
|
||||||
func (c *remoteClient) lockInfo() (*statemgr.LockInfo, error) {
|
func (c *remoteClient) lockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
|
||||||
r, err := c.lockFile().NewReader(c.storageContext)
|
r, err := c.lockFile().NewReader(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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
|
// This can't be written into the Info, since the generation isn't known
|
||||||
// until it's written.
|
// until it's written.
|
||||||
attrs, err := c.lockFile().Attrs(c.storageContext)
|
attrs, err := c.lockFile().Attrs(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -77,14 +77,14 @@ func (c *httpClient) httpRequest(ctx context.Context, method string, url *url.UR
|
|||||||
return resp, nil
|
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 {
|
if c.LockURL == nil {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
c.lockID = ""
|
c.lockID = ""
|
||||||
|
|
||||||
jsonLockInfo := info.Marshal()
|
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 {
|
if err != nil {
|
||||||
return "", err
|
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 {
|
if c.UnlockURL == nil {
|
||||||
return 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 {
|
if err != nil {
|
||||||
return err
|
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.
|
// take a lock and create a new state if it doesn't exist.
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockID, err := s.Lock(lockInfo)
|
lockID, err := s.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock inmem state: %w", err)
|
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 we have no state, we have to create an empty state
|
||||||
if v := s.State(); v == nil {
|
if v := s.State(); v == nil {
|
||||||
|
@ -43,9 +43,9 @@ func (c *RemoteClient) Delete(context.Context) error {
|
|||||||
return nil
|
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)
|
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)
|
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 := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockID, err := stateMgr.Lock(lockInfo)
|
lockID, err := stateMgr.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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
|
// Local helper function so we can call it multiple places
|
||||||
unlock := func(baseErr error) error {
|
unlock := func(baseErr error) error {
|
||||||
if err := stateMgr.Unlock(lockID); err != nil {
|
if err := stateMgr.Unlock(ctx, lockID); err != nil {
|
||||||
const unlockErrMsg = `%v
|
const unlockErrMsg = `%v
|
||||||
Additionally, unlocking the state in Kubernetes failed:
|
Additionally, unlocking the state in Kubernetes failed:
|
||||||
|
|
||||||
|
@ -101,6 +101,8 @@ func TestBackendLocksSoak(t *testing.T) {
|
|||||||
|
|
||||||
wg := sync.WaitGroup{}
|
wg := sync.WaitGroup{}
|
||||||
for i, l := range lockers {
|
for i, l := range lockers {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(locker statemgr.Locker, n int) {
|
go func(locker statemgr.Locker, n int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@ -110,7 +112,7 @@ func TestBackendLocksSoak(t *testing.T) {
|
|||||||
li.Who = fmt.Sprintf("client-%v", n)
|
li.Who = fmt.Sprintf("client-%v", n)
|
||||||
|
|
||||||
for i := 0; i < lockAttempts; i++ {
|
for i := 0; i < lockAttempts; i++ {
|
||||||
id, err := locker.Lock(li)
|
id, err := locker.Lock(ctx, li)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -118,7 +120,7 @@ func TestBackendLocksSoak(t *testing.T) {
|
|||||||
// hold onto the lock for a little bit
|
// hold onto the lock for a little bit
|
||||||
time.Sleep(time.Duration(rand.Intn(10)) * time.Microsecond)
|
time.Sleep(time.Duration(rand.Intn(10)) * time.Microsecond)
|
||||||
|
|
||||||
err = locker.Unlock(id)
|
err = locker.Unlock(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("failed to unlock: %v", err)
|
t.Errorf("failed to unlock: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ func (c *RemoteClient) Put(ctx context.Context, data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
secret, err := c.getSecret(secretName)
|
secret, err := c.getSecret(ctx, secretName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !k8serrors.IsNotFound(err) {
|
if !k8serrors.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
@ -121,13 +121,13 @@ func (c *RemoteClient) Put(ctx context.Context, data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Delete the state secret
|
// Delete the state secret
|
||||||
func (c *RemoteClient) Delete(context.Context) error {
|
func (c *RemoteClient) Delete(ctx context.Context) error {
|
||||||
secretName, err := c.createSecretName()
|
secretName, err := c.createSecretName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.deleteSecret(secretName)
|
err = c.deleteSecret(ctx, secretName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !k8serrors.IsNotFound(err) {
|
if !k8serrors.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
@ -139,7 +139,7 @@ func (c *RemoteClient) Delete(context.Context) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.deleteLease(leaseName)
|
err = c.deleteLease(ctx, leaseName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !k8serrors.IsNotFound(err) {
|
if !k8serrors.IsNotFound(err) {
|
||||||
return err
|
return err
|
||||||
@ -148,14 +148,13 @@ func (c *RemoteClient) Delete(context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
func (c *RemoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
leaseName, err := c.createLeaseName()
|
leaseName, err := c.createLeaseName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
lease, err := c.getLease(leaseName)
|
lease, err := c.getLease(ctx, leaseName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !k8serrors.IsNotFound(err) {
|
if !k8serrors.IsNotFound(err) {
|
||||||
return "", err
|
return "", err
|
||||||
@ -210,13 +209,13 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
return info.ID, err
|
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()
|
leaseName, err := c.createLeaseName()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
lease, err := c.getLease(leaseName)
|
lease, err := c.getLease(ctx, leaseName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -239,7 +238,7 @@ func (c *RemoteClient) Unlock(id string) error {
|
|||||||
lease.Spec.HolderIdentity = nil
|
lease.Spec.HolderIdentity = nil
|
||||||
removeLockInfo(lease)
|
removeLockInfo(lease)
|
||||||
|
|
||||||
_, err = c.kubernetesLeaseClient.Update(context.Background(), lease, metav1.UpdateOptions{})
|
_, err = c.kubernetesLeaseClient.Update(ctx, lease, metav1.UpdateOptions{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lockErr.Err = err
|
lockErr.Err = err
|
||||||
return lockErr
|
return lockErr
|
||||||
@ -280,16 +279,16 @@ func (c *RemoteClient) getLabels() map[string]string {
|
|||||||
return l
|
return l
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) getSecret(name string) (*unstructured.Unstructured, error) {
|
func (c *RemoteClient) getSecret(ctx context.Context, name string) (*unstructured.Unstructured, error) {
|
||||||
return c.kubernetesSecretClient.Get(context.Background(), name, metav1.GetOptions{})
|
return c.kubernetesSecretClient.Get(ctx, name, metav1.GetOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) getLease(name string) (*coordinationv1.Lease, error) {
|
func (c *RemoteClient) getLease(ctx context.Context, name string) (*coordinationv1.Lease, error) {
|
||||||
return c.kubernetesLeaseClient.Get(context.Background(), name, metav1.GetOptions{})
|
return c.kubernetesLeaseClient.Get(ctx, name, metav1.GetOptions{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) deleteSecret(name string) error {
|
func (c *RemoteClient) deleteSecret(ctx context.Context, name string) error {
|
||||||
secret, err := c.getSecret(name)
|
secret, err := c.getSecret(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -302,11 +301,11 @@ func (c *RemoteClient) deleteSecret(name string) error {
|
|||||||
|
|
||||||
delProp := metav1.DeletePropagationBackground
|
delProp := metav1.DeletePropagationBackground
|
||||||
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
|
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 {
|
func (c *RemoteClient) deleteLease(ctx context.Context, name string) error {
|
||||||
secret, err := c.getLease(name)
|
secret, err := c.getLease(ctx, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -319,7 +318,7 @@ func (c *RemoteClient) deleteLease(name string) error {
|
|||||||
|
|
||||||
delProp := metav1.DeletePropagationBackground
|
delProp := metav1.DeletePropagationBackground
|
||||||
delOps := metav1.DeleteOptions{PropagationPolicy: &delProp}
|
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) {
|
func (c *RemoteClient) createSecretName() (string, error) {
|
||||||
|
@ -86,7 +86,7 @@ func TestForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err := s1.Lock(info)
|
lockID, err := s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
t.Fatal("failed to force-unlock default state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +112,7 @@ func TestForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err = s1.Lock(info)
|
lockID, err = s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
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
|
// take a lock on this state while we write it
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := client.Lock(lockInfo)
|
lockId, err := client.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock OSS state: %w", err)
|
return nil, fmt.Errorf("failed to lock OSS state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(e error) error {
|
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 fmt.Errorf(strings.TrimSpace(stateUnlockError), lockId, err)
|
||||||
}
|
}
|
||||||
return e
|
return e
|
||||||
|
@ -148,7 +148,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
|
|||||||
return nil
|
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 == "" {
|
if c.otsTable == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -359,7 +359,7 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
|||||||
}
|
}
|
||||||
return lockInfo, nil
|
return lockInfo, nil
|
||||||
}
|
}
|
||||||
func (c *RemoteClient) Unlock(id string) error {
|
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
|
||||||
if c.otsTable == "" {
|
if c.otsTable == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -127,7 +127,7 @@ func TestRemoteClientLocks_multipleStates(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
t.Fatal("failed to get lock for s1:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +136,7 @@ func TestRemoteClientLocks_multipleStates(t *testing.T) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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)
|
t.Fatal("failed to get lock for s2:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -181,7 +181,7 @@ func TestRemoteForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err := s1.Lock(info)
|
lockID, err := s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
t.Fatal("failed to force-unlock default state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +207,7 @@ func TestRemoteForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err = s1.Lock(info)
|
lockID, err = s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
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 {
|
if !exists {
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := stateMgr.Lock(lockInfo)
|
lockId, err := stateMgr.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock state in Postgres: %w", err)
|
return nil, fmt.Errorf("failed to lock state in Postgres: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(parent error) error {
|
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 fmt.Errorf("error unlocking Postgres state: %w", err)
|
||||||
}
|
}
|
||||||
return parent
|
return parent
|
||||||
|
@ -439,6 +439,8 @@ func TestBackendConcurrentLock(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
getStateMgr := func(schemaName string) (statemgr.Full, *statemgr.LockInfo) {
|
getStateMgr := func(schemaName string) (statemgr.Full, *statemgr.LockInfo) {
|
||||||
defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
|
defer dbCleaner.Query(fmt.Sprintf("DROP SCHEMA IF EXISTS %s CASCADE", schemaName))
|
||||||
config := backend.TestWrapConfig(map[string]interface{}{
|
config := backend.TestWrapConfig(map[string]interface{}{
|
||||||
@ -451,8 +453,6 @@ func TestBackendConcurrentLock(t *testing.T) {
|
|||||||
t.Fatal("Backend could not be configured")
|
t.Fatal("Backend could not be configured")
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
stateMgr, err := b.StateMgr(ctx, backend.DefaultStateName)
|
stateMgr, err := b.StateMgr(ctx, backend.DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to get the state manager: %v", err)
|
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
|
// First we need to create the workspace as the lock for creating them is
|
||||||
// global
|
// global
|
||||||
lockID1, err := s1.Lock(i1)
|
lockID1, err := s1.Lock(ctx, i1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to lock first state: %v", err)
|
t.Fatalf("failed to lock first state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
if err = s1.PersistState(ctx, nil); err != nil {
|
if err = s1.PersistState(ctx, nil); err != nil {
|
||||||
t.Fatalf("failed to persist state: %v", err)
|
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)
|
t.Fatalf("failed to unlock first state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockID2, err := s2.Lock(i2)
|
lockID2, err := s2.Lock(ctx, i2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to lock second state: %v", err)
|
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)
|
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)
|
t.Fatalf("failed to unlock first state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we can test concurrent lock
|
// Now we can test concurrent lock
|
||||||
lockID1, err = s1.Lock(i1)
|
lockID1, err = s1.Lock(ctx, i1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to lock first state: %v", err)
|
t.Fatalf("failed to lock first state: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockID2, err = s2.Lock(i2)
|
lockID2, err = s2.Lock(ctx, i2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to lock second state: %v", err)
|
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)
|
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)
|
t.Fatalf("failed to unlock first state: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,7 +64,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
|
|||||||
return nil
|
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 err error
|
||||||
var lockID string
|
var lockID string
|
||||||
|
|
||||||
@ -80,7 +80,7 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
//
|
//
|
||||||
lockUnlock := func(pgLockId string) error {
|
lockUnlock := func(pgLockId string) error {
|
||||||
query := `SELECT pg_advisory_unlock(%s)`
|
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
|
var didUnlock []byte
|
||||||
err := row.Scan(&didUnlock)
|
err := row.Scan(&didUnlock)
|
||||||
if err != nil {
|
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`.
|
// 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`
|
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
|
var pgLockId, didLock, didLockForCreate []byte
|
||||||
err = row.Scan(&pgLockId, &didLock, &didLockForCreate)
|
err = row.Scan(&pgLockId, &didLock, &didLockForCreate)
|
||||||
switch {
|
switch {
|
||||||
case err == sql.ErrNoRows:
|
case err == sql.ErrNoRows:
|
||||||
// No rows means we're creating the workspace. Take the creation lock.
|
// 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
|
var innerDidLock []byte
|
||||||
err := innerRow.Scan(&innerDidLock)
|
err := innerRow.Scan(&innerDidLock)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -131,10 +131,10 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
|
|||||||
return c.info, nil
|
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 != "" {
|
if c.info != nil && c.info.Path != "" {
|
||||||
query := `SELECT pg_advisory_unlock(%s)`
|
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
|
var didUnlock []byte
|
||||||
err := row.Scan(&didUnlock)
|
err := row.Scan(&didUnlock)
|
||||||
if err != nil {
|
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
|
// take a lock on this state while we write it
|
||||||
lockInfo := statemgr.NewLockInfo()
|
lockInfo := statemgr.NewLockInfo()
|
||||||
lockInfo.Operation = "init"
|
lockInfo.Operation = "init"
|
||||||
lockId, err := client.Lock(lockInfo)
|
lockId, err := client.Lock(ctx, lockInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to lock s3 state: %w", err)
|
return nil, fmt.Errorf("failed to lock s3 state: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Local helper function so we can call it multiple places
|
// Local helper function so we can call it multiple places
|
||||||
lockUnlock := func(parent error) error {
|
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 fmt.Errorf(strings.TrimSpace(errStateUnlock), lockId, err)
|
||||||
}
|
}
|
||||||
return parent
|
return parent
|
||||||
|
@ -217,7 +217,7 @@ func (c *RemoteClient) Delete(ctx context.Context) error {
|
|||||||
return nil
|
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 == "" {
|
if c.ddbTable == "" {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
@ -242,7 +242,6 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
_, err := c.dynClient.PutItem(ctx, putParams)
|
_, err := c.dynClient.PutItem(ctx, putParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lockInfo, infoErr := c.getLockInfo(ctx)
|
lockInfo, infoErr := c.getLockInfo(ctx)
|
||||||
@ -368,13 +367,12 @@ func (c *RemoteClient) getLockInfo(ctx context.Context) (*statemgr.LockInfo, err
|
|||||||
return lockInfo, nil
|
return lockInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *RemoteClient) Unlock(id string) error {
|
func (c *RemoteClient) Unlock(ctx context.Context, id string) error {
|
||||||
if c.ddbTable == "" {
|
if c.ddbTable == "" {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
lockErr := &statemgr.LockError{}
|
lockErr := &statemgr.LockError{}
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
// TODO: store the path and lock ID in separate fields, and have proper
|
// TODO: store the path and lock ID in separate fields, and have proper
|
||||||
// projection expression only delete the lock if both match, rather than
|
// projection expression only delete the lock if both match, rather than
|
||||||
|
@ -104,7 +104,7 @@ func TestForceUnlock(t *testing.T) {
|
|||||||
"dynamodb_table": bucketName,
|
"dynamodb_table": bucketName,
|
||||||
})).(*Backend)
|
})).(*Backend)
|
||||||
|
|
||||||
ctx := context.TODO()
|
ctx := context.Background()
|
||||||
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
|
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
|
||||||
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
|
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
|
||||||
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
|
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
|
||||||
@ -120,7 +120,7 @@ func TestForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err := s1.Lock(info)
|
lockID, err := s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
t.Fatal("failed to force-unlock default state")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,7 +146,7 @@ func TestForceUnlock(t *testing.T) {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Who = "clientA"
|
info.Who = "clientA"
|
||||||
|
|
||||||
lockID, err = s1.Lock(info)
|
lockID, err = s1.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
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)
|
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")
|
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.
|
// that there are no compatibility concerns, so it returns no diagnostics.
|
||||||
//
|
//
|
||||||
// If the versions differ,
|
// 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
|
var diags tfdiags.Diagnostics
|
||||||
|
|
||||||
workspace, err := b.getRemoteWorkspace(context.Background(), workspaceName)
|
workspace, err := b.getRemoteWorkspace(ctx, workspaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the workspace doesn't exist, there can be no compatibility
|
// If the workspace doesn't exist, there can be no compatibility
|
||||||
// problem, so we can return. This is most likely to happen when
|
// 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)
|
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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.
|
// Get the remote workspace name.
|
||||||
remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace)
|
remoteWorkspaceName := b.getRemoteWorkspaceName(op.Workspace)
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
|
|
||||||
// Get the latest state.
|
// Get the latest state.
|
||||||
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", remoteWorkspaceName)
|
log.Printf("[TRACE] backend/remote: requesting state manager for workspace %q", remoteWorkspaceName)
|
||||||
stateMgr, err := b.StateMgr(ctx, op.Workspace)
|
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
|
// 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
|
// variables, so we'll need to look that up using our organization name
|
||||||
// and workspace name.
|
// and workspace name.
|
||||||
remoteWorkspaceID, err := b.getRemoteWorkspaceID(context.Background(), op.Workspace)
|
remoteWorkspaceID, err := b.getRemoteWorkspaceID(ctx, op.Workspace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
diags = diags.Append(fmt.Errorf("error finding remote workspace: %w", err))
|
diags = diags.Append(fmt.Errorf("error finding remote workspace: %w", err))
|
||||||
return nil, nil, diags
|
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 {
|
if err != nil {
|
||||||
diags = diags.Append(fmt.Errorf("error loading workspace: %w", err))
|
diags = diags.Append(fmt.Errorf("error loading workspace: %w", err))
|
||||||
return nil, nil, diags
|
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)
|
log.Printf("[TRACE] skipping retrieving variables from workspace %s/%s (%s), workspace is in Local Execution mode", remoteWorkspaceName, b.organization, remoteWorkspaceID)
|
||||||
} else {
|
} else {
|
||||||
log.Printf("[TRACE] backend/remote: retrieving variables from workspace %s/%s (%s)", remoteWorkspaceName, b.organization, remoteWorkspaceID)
|
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 {
|
if err != nil && err != tfe.ErrResourceNotFound {
|
||||||
diags = diags.Append(fmt.Errorf("error loading variables: %w", err))
|
diags = diags.Append(fmt.Errorf("error loading variables: %w", err))
|
||||||
return nil, nil, diags
|
return nil, nil, diags
|
||||||
|
@ -190,7 +190,9 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||||||
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
_, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir, "tests")
|
||||||
defer configCleanup()
|
defer configCleanup()
|
||||||
|
|
||||||
workspaceID, err := b.getRemoteWorkspaceID(context.Background(), backend.DefaultStateName)
|
ctx := context.Background()
|
||||||
|
|
||||||
|
workspaceID, err := b.getRemoteWorkspaceID(ctx, backend.DefaultStateName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -211,8 +213,6 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||||||
v.Key = &key
|
v.Key = &key
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
b.client.Variables.Create(ctx, workspaceID, *v)
|
b.client.Variables.Create(ctx, workspaceID, *v)
|
||||||
|
|
||||||
_, _, diags := b.LocalRun(op)
|
_, _, diags := b.LocalRun(op)
|
||||||
@ -228,7 +228,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||||||
// When Context() returns an error, it should unlock the state,
|
// When Context() returns an error, it should unlock the state,
|
||||||
// so re-locking it is expected to succeed.
|
// so re-locking it is expected to succeed.
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
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())
|
t.Fatalf("unexpected error locking state: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -237,7 +237,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// When Context() succeeds, this should fail w/ "workspace already locked"
|
// When Context() succeeds, this should fail w/ "workspace already locked"
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
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")
|
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"
|
// When Context() succeeds, this should fail w/ "workspace already locked"
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
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")
|
t.Fatal("unexpected success locking state after Context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +98,7 @@ func TestRemote_planBasic(t *testing.T) {
|
|||||||
|
|
||||||
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, backend.DefaultStateName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
t.Fatalf("unexpected error locking state after cancelled plan: %s", err.Error())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,9 +153,7 @@ func (r *remoteClient) EnableForcePush() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock the remote state.
|
// Lock the remote state.
|
||||||
func (r *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
func (r *remoteClient) Lock(ctx context.Context, info *statemgr.LockInfo) (string, error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
lockErr := &statemgr.LockError{Info: r.lockInfo}
|
lockErr := &statemgr.LockError{Info: r.lockInfo}
|
||||||
|
|
||||||
// Lock the workspace.
|
// Lock the workspace.
|
||||||
@ -177,9 +175,7 @@ func (r *remoteClient) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Unlock the remote state.
|
// Unlock the remote state.
|
||||||
func (r *remoteClient) Unlock(id string) error {
|
func (r *remoteClient) Unlock(ctx context.Context, id string) error {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// We first check if there was an error while uploading the latest
|
// We first check if there was an error while uploading the latest
|
||||||
// state. If so, we will not unlock the workspace to prevent any
|
// state. If so, we will not unlock the workspace to prevent any
|
||||||
// changes from being applied until the correct state is uploaded.
|
// 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.Version = local.String()
|
||||||
tfversion.SemVer = local
|
tfversion.SemVer = local
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// Update the mock remote workspace OpenTofu version to the
|
// Update the mock remote workspace OpenTofu version to the
|
||||||
// specified remote version
|
// specified remote version
|
||||||
if _, err := b.client.Workspaces.Update(
|
if _, err := b.client.Workspaces.Update(
|
||||||
context.Background(),
|
ctx,
|
||||||
b.organization,
|
b.organization,
|
||||||
b.workspace,
|
b.workspace,
|
||||||
tfe.WorkspaceUpdateOptions{
|
tfe.WorkspaceUpdateOptions{
|
||||||
@ -635,7 +637,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion(t *testing.T) {
|
|||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diags := b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
|
diags := b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
|
||||||
if tc.wantErr {
|
if tc.wantErr {
|
||||||
if len(diags) != 1 {
|
if len(diags) != 1 {
|
||||||
t.Fatal("expected diag, but none returned")
|
t.Fatal("expected diag, but none returned")
|
||||||
@ -656,16 +658,18 @@ func TestRemote_VerifyWorkspaceTerraformVersion_workspaceErrors(t *testing.T) {
|
|||||||
b, bCleanup := testBackendDefault(t)
|
b, bCleanup := testBackendDefault(t)
|
||||||
defer bCleanup()
|
defer bCleanup()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// Attempting to check the version against a workspace which doesn't exist
|
// Attempting to check the version against a workspace which doesn't exist
|
||||||
// should result in no errors
|
// should result in no errors
|
||||||
diags := b.VerifyWorkspaceTerraformVersion("invalid-workspace")
|
diags := b.VerifyWorkspaceTerraformVersion(ctx, "invalid-workspace")
|
||||||
if len(diags) != 0 {
|
if len(diags) != 0 {
|
||||||
t.Fatalf("unexpected error: %s", diags.Err())
|
t.Fatalf("unexpected error: %s", diags.Err())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a special workspace ID to trigger a 500 error, which should result
|
// Use a special workspace ID to trigger a 500 error, which should result
|
||||||
// in a failed check
|
// in a failed check
|
||||||
diags = b.VerifyWorkspaceTerraformVersion("network-error")
|
diags = b.VerifyWorkspaceTerraformVersion(ctx, "network-error")
|
||||||
if len(diags) != 1 {
|
if len(diags) != 1 {
|
||||||
t.Fatal("expected diag, but none returned")
|
t.Fatal("expected diag, but none returned")
|
||||||
}
|
}
|
||||||
@ -684,7 +688,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion_workspaceErrors(t *testing.T) {
|
|||||||
); err != nil {
|
); err != nil {
|
||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
diags = b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
|
diags = b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
|
||||||
|
|
||||||
if len(diags) != 1 {
|
if len(diags) != 1 {
|
||||||
t.Fatal("expected diag, but none returned")
|
t.Fatal("expected diag, but none returned")
|
||||||
@ -720,10 +724,12 @@ func TestRemote_VerifyWorkspaceTerraformVersion_ignoreFlagSet(t *testing.T) {
|
|||||||
tfversion.Version = local.String()
|
tfversion.Version = local.String()
|
||||||
tfversion.SemVer = local
|
tfversion.SemVer = local
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// Update the mock remote workspace OpenTofu version to the
|
// Update the mock remote workspace OpenTofu version to the
|
||||||
// specified remote version
|
// specified remote version
|
||||||
if _, err := b.client.Workspaces.Update(
|
if _, err := b.client.Workspaces.Update(
|
||||||
context.Background(),
|
ctx,
|
||||||
b.organization,
|
b.organization,
|
||||||
b.workspace,
|
b.workspace,
|
||||||
tfe.WorkspaceUpdateOptions{
|
tfe.WorkspaceUpdateOptions{
|
||||||
@ -733,7 +739,7 @@ func TestRemote_VerifyWorkspaceTerraformVersion_ignoreFlagSet(t *testing.T) {
|
|||||||
t.Fatalf("error: %v", err)
|
t.Fatalf("error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
diags := b.VerifyWorkspaceTerraformVersion(backend.DefaultStateName)
|
diags := b.VerifyWorkspaceTerraformVersion(ctx, backend.DefaultStateName)
|
||||||
if len(diags) != 1 {
|
if len(diags) != 1 {
|
||||||
t.Fatal("expected diag, but none returned")
|
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.Operation = "test"
|
||||||
infoB.Who = "clientB"
|
infoB.Who = "clientB"
|
||||||
|
|
||||||
lockIDA, err := lockerA.Lock(infoA)
|
lockIDA, err := lockerA.Lock(ctx, infoA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
t.Fatal("unable to get initial lock:", err)
|
||||||
}
|
}
|
||||||
@ -369,17 +369,17 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = lockerB.Lock(infoB)
|
_, err = lockerB.Lock(ctx, infoB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lockerA.Unlock(lockIDA)
|
lockerA.Unlock(ctx, lockIDA)
|
||||||
t.Fatal("client B obtained lock while held by client A")
|
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)
|
t.Fatal("error unlocking client A", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockIDB, err := lockerB.Lock(infoB)
|
lockIDB, err := lockerB.Lock(ctx, infoB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to obtain lock from client B")
|
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)
|
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)
|
t.Fatal("error unlocking client B:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -404,18 +404,18 @@ func testLocksInWorkspace(t *testing.T, b1, b2 Backend, testForceUnlock bool, wo
|
|||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockIDA, err = lockerA.Lock(infoA)
|
lockIDA, err = lockerA.Lock(ctx, infoA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get re lock A:", err)
|
t.Fatal("unable to get re lock A:", err)
|
||||||
}
|
}
|
||||||
unlock := func() {
|
unlock := func() {
|
||||||
err := lockerA.Unlock(lockIDA)
|
err := lockerA.Unlock(ctx, lockIDA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = lockerB.Lock(infoB)
|
_, err = lockerB.Lock(ctx, infoB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
unlock()
|
unlock()
|
||||||
t.Fatal("client B obtained lock while held by client A")
|
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
|
// 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()
|
unlock()
|
||||||
t.Fatalf("could not unlock with the reported ID %q: %s", infoErr.Info.ID, err)
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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,
|
// When Context() returns an error, it should unlock the state,
|
||||||
// so re-locking it is expected to succeed.
|
// so re-locking it is expected to succeed.
|
||||||
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
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())
|
t.Fatalf("unexpected error locking state: %s", err.Error())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -236,7 +236,7 @@ func TestRemoteContextWithVars(t *testing.T) {
|
|||||||
}
|
}
|
||||||
// When Context() succeeds, this should fail w/ "workspace already locked"
|
// When Context() succeeds, this should fail w/ "workspace already locked"
|
||||||
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
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")
|
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"
|
// When Context() succeeds, this should fail w/ "workspace already locked"
|
||||||
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
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")
|
t.Fatal("unexpected success locking state after Context")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ func TestCloud_planBasic(t *testing.T) {
|
|||||||
|
|
||||||
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after the operation finished
|
// 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())
|
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)
|
stateMgr, _ := b.StateMgr(ctx, testBackendSingleWorkspaceName)
|
||||||
// An error suggests that the state was not unlocked after apply
|
// 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())
|
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.
|
// 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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
if s.disableLocks {
|
if s.disableLocks {
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
lockErr := &statemgr.LockError{Info: s.lockInfo}
|
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.
|
// 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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@ -442,8 +441,6 @@ func (s *State) Unlock(id string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// We first check if there was an error while uploading the latest
|
// We first check if there was an error while uploading the latest
|
||||||
// state. If so, we will not unlock the workspace to prevent any
|
// state. If so, we will not unlock the workspace to prevent any
|
||||||
// changes from being applied until the correct state is uploaded.
|
// changes from being applied until the correct state is uploaded.
|
||||||
|
@ -178,25 +178,25 @@ func TestCloudLocks(t *testing.T) {
|
|||||||
infoB.Operation = "test"
|
infoB.Operation = "test"
|
||||||
infoB.Who = "clientB"
|
infoB.Who = "clientB"
|
||||||
|
|
||||||
lockIDA, err := lockerA.Lock(infoA)
|
lockIDA, err := lockerA.Lock(ctx, infoA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
t.Fatal("unable to get initial lock:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = lockerB.Lock(infoB)
|
_, err = lockerB.Lock(ctx, infoB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lockerA.Unlock(lockIDA)
|
lockerA.Unlock(ctx, lockIDA)
|
||||||
t.Fatal("client B obtained lock while held by client A")
|
t.Fatal("client B obtained lock while held by client A")
|
||||||
}
|
}
|
||||||
if _, ok := err.(*statemgr.LockError); !ok {
|
if _, ok := err.(*statemgr.LockError); !ok {
|
||||||
t.Errorf("expected a LockError, but was %t: %s", err, err)
|
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)
|
t.Fatal("error unlocking client A", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockIDB, err := lockerB.Lock(infoB)
|
lockIDB, err := lockerB.Lock(ctx, infoB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to obtain lock from client B")
|
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)
|
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)
|
t.Fatal("error unlocking client B:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ package clistate
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -175,7 +176,7 @@ func (s *LocalState) RefreshState() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock implements a local filesystem state.Locker.
|
// 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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ func (s *LocalState) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
return s.lockID, s.writeLockInfo(info)
|
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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ func (l *locker) Unlock() tfdiags.Diagnostics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := slowmessage.Do(LockThreshold, func() error {
|
err := slowmessage.Do(LockThreshold, func() error {
|
||||||
return l.state.Unlock(l.lockID)
|
return l.state.Unlock(l.ctx, l.lockID)
|
||||||
}, l.view.Unlocking)
|
}, l.view.Unlocking)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
7
internal/command/testdata/statelocker.go
vendored
7
internal/command/testdata/statelocker.go
vendored
@ -4,6 +4,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -28,7 +29,9 @@ func main() {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Info = "state locker"
|
info.Info = "state locker"
|
||||||
|
|
||||||
lockID, err := s.Lock(info)
|
ctx := context.Background()
|
||||||
|
|
||||||
|
lockID, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
io.WriteString(os.Stderr, err.Error())
|
io.WriteString(os.Stderr, err.Error())
|
||||||
return
|
return
|
||||||
@ -38,7 +41,7 @@ func main() {
|
|||||||
io.WriteString(os.Stdout, "LOCKID "+lockID)
|
io.WriteString(os.Stdout, "LOCKID "+lockID)
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := s.Unlock(lockID); err != nil {
|
if err := s.Unlock(ctx, lockID); err != nil {
|
||||||
io.WriteString(os.Stderr, err.Error())
|
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" +
|
"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."
|
"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",
|
Id: "force-unlock",
|
||||||
Query: "Do you really want to force-unlock?",
|
Query: "Do you really want to force-unlock?",
|
||||||
Description: desc,
|
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))
|
c.Ui.Error(fmt.Sprintf("Failed to unlock state: %s", err))
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
@ -237,7 +237,7 @@ func (s *State) ShouldPersistIntermediateState(info *local.IntermediateStatePers
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock calls the Client's Lock method if it's implemented.
|
// 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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@ -246,13 +246,13 @@ func (s *State) Lock(info *statemgr.LockInfo) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c, ok := s.Client.(ClientLocker); ok {
|
if c, ok := s.Client.(ClientLocker); ok {
|
||||||
return c.Lock(info)
|
return c.Lock(ctx, info)
|
||||||
}
|
}
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock calls the Client's Unlock method if it's implemented.
|
// 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()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
@ -261,7 +261,7 @@ func (s *State) Unlock(id string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if c, ok := s.Client.(ClientLocker); ok {
|
if c, ok := s.Client.(ClientLocker); ok {
|
||||||
return c.Unlock(id)
|
return c.Unlock(ctx, id)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -73,25 +73,27 @@ func TestRemoteLocks(t *testing.T, a, b Client) {
|
|||||||
infoB.Operation = "test"
|
infoB.Operation = "test"
|
||||||
infoB.Who = "clientB"
|
infoB.Who = "clientB"
|
||||||
|
|
||||||
lockIDA, err := lockerA.Lock(infoA)
|
ctx := context.Background()
|
||||||
|
|
||||||
|
lockIDA, err := lockerA.Lock(ctx, infoA)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to get initial lock:", err)
|
t.Fatal("unable to get initial lock:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = lockerB.Lock(infoB)
|
_, err = lockerB.Lock(ctx, infoB)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
lockerA.Unlock(lockIDA)
|
lockerA.Unlock(ctx, lockIDA)
|
||||||
t.Fatal("client B obtained lock while held by client A")
|
t.Fatal("client B obtained lock while held by client A")
|
||||||
}
|
}
|
||||||
if _, ok := err.(*statemgr.LockError); !ok {
|
if _, ok := err.(*statemgr.LockError); !ok {
|
||||||
t.Errorf("expected a LockError, but was %t: %s", err, err)
|
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)
|
t.Fatal("error unlocking client A", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lockIDB, err := lockerB.Lock(infoB)
|
lockIDB, err := lockerB.Lock(ctx, infoB)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal("unable to obtain lock from client B")
|
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)
|
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)
|
t.Fatal("error unlocking client B:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,7 +314,7 @@ func (s *Filesystem) refreshState() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Lock implements Locker using filesystem discretionary locks.
|
// 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()()
|
defer s.mutex()()
|
||||||
|
|
||||||
if s.stateFileOut == nil {
|
if s.stateFileOut == nil {
|
||||||
@ -345,8 +345,8 @@ func (s *Filesystem) Lock(info *LockInfo) (string, error) {
|
|||||||
return s.lockID, s.writeLockInfo(info)
|
return s.lockID, s.writeLockInfo(info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Unlock is the companion to Lock, completing the implemention of Locker.
|
// Unlock is the companion to Lock, completing the implementation of Locker.
|
||||||
func (s *Filesystem) Unlock(id string) error {
|
func (s *Filesystem) Unlock(_ context.Context, id string) error {
|
||||||
defer s.mutex()()
|
defer s.mutex()()
|
||||||
|
|
||||||
if s.lockID == "" {
|
if s.lockID == "" {
|
||||||
|
@ -52,10 +52,12 @@ func TestFilesystemLocks(t *testing.T) {
|
|||||||
s := testFilesystem(t)
|
s := testFilesystem(t)
|
||||||
defer os.Remove(s.readPath)
|
defer os.Remove(s.readPath)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// lock first
|
// lock first
|
||||||
info := NewLockInfo()
|
info := NewLockInfo()
|
||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
lockID, err := s.Lock(info)
|
lockID, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -80,22 +82,22 @@ func TestFilesystemLocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// a noop, since we unlock on exit
|
// 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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// local locks can re-lock
|
// local locks can re-lock
|
||||||
lockID, err = s.Lock(info)
|
lockID, err = s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := s.Unlock(lockID); err != nil {
|
if err := s.Unlock(ctx, lockID); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// we should not be able to unlock the same lock twice
|
// 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")
|
t.Fatal("unlocking an unlocked state should fail")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,15 +115,17 @@ func TestFilesystem_writeWhileLocked(t *testing.T) {
|
|||||||
s := testFilesystem(t)
|
s := testFilesystem(t)
|
||||||
defer os.Remove(s.readPath)
|
defer os.Remove(s.readPath)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// lock first
|
// lock first
|
||||||
info := NewLockInfo()
|
info := NewLockInfo()
|
||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
lockID, err := s.Lock(info)
|
lockID, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := s.Unlock(lockID); err != nil {
|
if err := s.Unlock(ctx, lockID); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
@ -307,8 +311,10 @@ func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) {
|
|||||||
// Delete the just-created tempfile so that Lock recreates it
|
// Delete the just-created tempfile so that Lock recreates it
|
||||||
os.Remove(ls.path)
|
os.Remove(ls.path)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// Lock the state, and in doing so recreate the tempfile
|
// Lock the state, and in doing so recreate the tempfile
|
||||||
lockID, err := ls.Lock(info)
|
lockID, err := ls.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -317,7 +323,7 @@ func TestFilesystem_lockUnlockWithoutWrite(t *testing.T) {
|
|||||||
t.Fatal("should have marked state as created")
|
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)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -391,15 +397,17 @@ func TestFilesystem_refreshWhileLocked(t *testing.T) {
|
|||||||
s := NewFilesystem(f.Name())
|
s := NewFilesystem(f.Name())
|
||||||
defer os.Remove(s.path)
|
defer os.Remove(s.path)
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
// lock first
|
// lock first
|
||||||
info := NewLockInfo()
|
info := NewLockInfo()
|
||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
lockID, err := s.Lock(info)
|
lockID, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := s.Unlock(lockID); err != nil {
|
if err := s.Unlock(ctx, lockID); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -39,10 +39,10 @@ func (s *LockDisabled) PersistState(ctx context.Context, schemas *tofu.Schemas)
|
|||||||
return s.Inner.PersistState(ctx, 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
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *LockDisabled) Unlock(id string) error {
|
func (s *LockDisabled) Unlock(ctx context.Context, id string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ type Locker interface {
|
|||||||
// an instance of LockError immediately if the lock is already held,
|
// an instance of LockError immediately if the lock is already held,
|
||||||
// and the helper function LockWithContext uses this to automatically
|
// and the helper function LockWithContext uses this to automatically
|
||||||
// retry lock acquisition periodically until a timeout is reached.
|
// 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.
|
// Unlock releases a lock previously acquired by Lock.
|
||||||
//
|
//
|
||||||
@ -61,7 +61,7 @@ type Locker interface {
|
|||||||
// another user with some sort of administrative override privilege --
|
// another user with some sort of administrative override privilege --
|
||||||
// then an error is returned explaining the situation in a way that
|
// then an error is returned explaining the situation in a way that
|
||||||
// is suitable for returning to an end-user.
|
// 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
|
// 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
|
delay := time.Second
|
||||||
maxDelay := 16 * time.Second
|
maxDelay := 16 * time.Second
|
||||||
for {
|
for {
|
||||||
id, err := s.Lock(info)
|
id, err := s.Lock(ctx, info)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ func (m *fakeFull) GetRootOutputValues(ctx context.Context) (map[string]*states.
|
|||||||
return m.State().RootModule().OutputValues, nil
|
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()
|
m.lockLock.Lock()
|
||||||
defer m.lockLock.Unlock()
|
defer m.lockLock.Unlock()
|
||||||
|
|
||||||
@ -89,7 +89,7 @@ func (m *fakeFull) Lock(info *LockInfo) (string, error) {
|
|||||||
return "placeholder", nil
|
return "placeholder", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *fakeFull) Unlock(id string) error {
|
func (m *fakeFull) Unlock(_ context.Context, id string) error {
|
||||||
m.lockLock.Lock()
|
m.lockLock.Lock()
|
||||||
defer m.lockLock.Unlock()
|
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")
|
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
|
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")
|
return errors.New("fake state manager error")
|
||||||
}
|
}
|
||||||
|
@ -45,7 +45,7 @@ func TestNewLockInfo(t *testing.T) {
|
|||||||
func TestLockWithContext(t *testing.T) {
|
func TestLockWithContext(t *testing.T) {
|
||||||
s := NewFullFake(nil, TestFullInitialState())
|
s := NewFullFake(nil, TestFullInitialState())
|
||||||
|
|
||||||
id, err := s.Lock(NewLockInfo())
|
id, err := s.Lock(context.Background(), NewLockInfo())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -61,7 +61,7 @@ func TestLockWithContext(t *testing.T) {
|
|||||||
t.Fatal("lock should have failed immediately")
|
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{})
|
attempted := make(chan struct{})
|
||||||
postLockHook = func() {
|
postLockHook = func() {
|
||||||
close(attempted)
|
close(attempted)
|
||||||
@ -74,7 +74,7 @@ func TestLockWithContext(t *testing.T) {
|
|||||||
go func() {
|
go func() {
|
||||||
defer close(unlocked)
|
defer close(unlocked)
|
||||||
<-attempted
|
<-attempted
|
||||||
unlockErr = s.Unlock(id)
|
unlockErr = s.Unlock(context.Background(), id)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
ctx, cancel = context.WithTimeout(context.Background(), 2*time.Second)
|
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)
|
t.Fatal("lock should have completed within 2s:", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ensure the goruotine completes
|
// ensure the goroutine completes
|
||||||
<-unlocked
|
<-unlocked
|
||||||
if unlockErr != nil {
|
if unlockErr != nil {
|
||||||
t.Fatal(unlockErr)
|
t.Fatal(unlockErr)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@ -21,7 +22,9 @@ func main() {
|
|||||||
info.Operation = "test"
|
info.Operation = "test"
|
||||||
info.Info = "state locker"
|
info.Info = "state locker"
|
||||||
|
|
||||||
_, err := s.Lock(info)
|
ctx := context.Background()
|
||||||
|
|
||||||
|
_, err := s.Lock(ctx, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
io.WriteString(os.Stderr, "lock failed")
|
io.WriteString(os.Stderr, "lock failed")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user