118 lines
2.8 KiB
Go
118 lines
2.8 KiB
Go
package state
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"os/user"
|
|
"time"
|
|
|
|
uuid "github.com/hashicorp/go-uuid"
|
|
|
|
"github.com/hashicorp/terraform/states/statemgr"
|
|
"github.com/hashicorp/terraform/version"
|
|
)
|
|
|
|
var rngSource *rand.Rand
|
|
|
|
func init() {
|
|
rngSource = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
}
|
|
|
|
// State is a deprecated alias for statemgr.Full
|
|
type State = statemgr.Full
|
|
|
|
// StateReader is a deprecated alias for statemgr.Reader
|
|
type StateReader = statemgr.Reader
|
|
|
|
// StateWriter is a deprecated alias for statemgr.Writer
|
|
type StateWriter = statemgr.Writer
|
|
|
|
// StateRefresher is a deprecated alias for statemgr.Refresher
|
|
type StateRefresher = statemgr.Refresher
|
|
|
|
// StatePersister is a deprecated alias for statemgr.Persister
|
|
type StatePersister = statemgr.Persister
|
|
|
|
// Locker is a deprecated alias for statemgr.Locker
|
|
type Locker = statemgr.Locker
|
|
|
|
// test hook to verify that LockWithContext has attempted a lock
|
|
var postLockHook func()
|
|
|
|
// Lock the state, using the provided context for timeout and cancellation.
|
|
// This backs off slightly to an upper limit.
|
|
func LockWithContext(ctx context.Context, s State, info *LockInfo) (string, error) {
|
|
delay := time.Second
|
|
maxDelay := 16 * time.Second
|
|
for {
|
|
id, err := s.Lock(info)
|
|
if err == nil {
|
|
return id, nil
|
|
}
|
|
|
|
le, ok := err.(*LockError)
|
|
if !ok {
|
|
// not a lock error, so we can't retry
|
|
return "", err
|
|
}
|
|
|
|
if le == nil || le.Info == nil || le.Info.ID == "" {
|
|
// If we dont' have a complete LockError, there's something wrong with the lock
|
|
return "", err
|
|
}
|
|
|
|
if postLockHook != nil {
|
|
postLockHook()
|
|
}
|
|
|
|
// there's an existing lock, wait and try again
|
|
select {
|
|
case <-ctx.Done():
|
|
// return the last lock error with the info
|
|
return "", err
|
|
case <-time.After(delay):
|
|
if delay < maxDelay {
|
|
delay *= 2
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate a LockInfo structure, populating the required fields.
|
|
func NewLockInfo() *LockInfo {
|
|
// this doesn't need to be cryptographically secure, just unique.
|
|
// Using math/rand alleviates the need to check handle the read error.
|
|
// Use a uuid format to match other IDs used throughout Terraform.
|
|
buf := make([]byte, 16)
|
|
rngSource.Read(buf)
|
|
|
|
id, err := uuid.FormatUUID(buf)
|
|
if err != nil {
|
|
// this of course shouldn't happen
|
|
panic(err)
|
|
}
|
|
|
|
// don't error out on user and hostname, as we don't require them
|
|
userName := ""
|
|
if userInfo, err := user.Current(); err == nil {
|
|
userName = userInfo.Username
|
|
}
|
|
host, _ := os.Hostname()
|
|
|
|
info := &LockInfo{
|
|
ID: id,
|
|
Who: fmt.Sprintf("%s@%s", userName, host),
|
|
Version: version.Version,
|
|
Created: time.Now().UTC(),
|
|
}
|
|
return info
|
|
}
|
|
|
|
// LockInfo is a deprecated lias for statemgr.LockInfo
|
|
type LockInfo = statemgr.LockInfo
|
|
|
|
// LockError is a deprecated alias for statemgr.LockError
|
|
type LockError = statemgr.LockError
|