2015-02-21 20:52:55 +01:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
2017-02-14 23:43:42 +01:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
2017-02-15 16:25:04 +01:00
|
|
|
"math/rand"
|
|
|
|
"os"
|
|
|
|
"os/user"
|
2017-02-14 23:43:42 +01:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2017-02-15 16:25:04 +01:00
|
|
|
uuid "github.com/hashicorp/go-uuid"
|
2015-02-21 20:52:55 +01:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
2017-02-15 16:25:04 +01:00
|
|
|
var rngSource *rand.Rand
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
rngSource = rand.New(rand.NewSource(time.Now().UnixNano()))
|
|
|
|
}
|
|
|
|
|
2015-02-22 01:04:32 +01:00
|
|
|
// State is the collection of all state interfaces.
|
|
|
|
type State interface {
|
|
|
|
StateReader
|
|
|
|
StateWriter
|
|
|
|
StateRefresher
|
|
|
|
StatePersister
|
|
|
|
}
|
|
|
|
|
2015-02-21 20:52:55 +01:00
|
|
|
// StateReader is the interface for things that can return a state. Retrieving
|
|
|
|
// the state here must not error. Loading the state fresh (an operation that
|
|
|
|
// can likely error) should be implemented by RefreshState. If a state hasn't
|
|
|
|
// been loaded yet, it is okay for State to return nil.
|
|
|
|
type StateReader interface {
|
|
|
|
State() *terraform.State
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateWriter is the interface that must be implemented by something that
|
|
|
|
// can write a state. Writing the state can be cached or in-memory, as
|
|
|
|
// full persistence should be implemented by StatePersister.
|
|
|
|
type StateWriter interface {
|
|
|
|
WriteState(*terraform.State) error
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateRefresher is the interface that is implemented by something that
|
|
|
|
// can load a state. This might be refreshing it from a remote location or
|
|
|
|
// it might simply be reloading it from disk.
|
|
|
|
type StateRefresher interface {
|
|
|
|
RefreshState() error
|
|
|
|
}
|
|
|
|
|
|
|
|
// StatePersister is implemented to truly persist a state. Whereas StateWriter
|
|
|
|
// is allowed to perhaps be caching in memory, PersistState must write the
|
|
|
|
// state to some durable storage.
|
|
|
|
type StatePersister interface {
|
|
|
|
PersistState() error
|
|
|
|
}
|
2017-01-10 00:00:05 +01:00
|
|
|
|
|
|
|
// Locker is implemented to lock state during command execution.
|
2017-02-14 23:43:42 +01:00
|
|
|
// The info parameter can be recorded with the lock, but the
|
|
|
|
// implementation should not depend in its value. The string returned by Lock
|
|
|
|
// is an ID corresponding to the lock acquired, and must be passed to Unlock to
|
|
|
|
// ensure that the correct lock is being released.
|
|
|
|
//
|
|
|
|
// Lock and Unlock may return an error value of type LockError which in turn
|
|
|
|
// can contain the LockInfo of a conflicting lock.
|
2017-01-10 00:00:05 +01:00
|
|
|
type Locker interface {
|
2017-02-14 23:43:42 +01:00
|
|
|
Lock(info *LockInfo) (string, error)
|
|
|
|
Unlock(id string) error
|
|
|
|
}
|
|
|
|
|
2017-02-15 16:25:04 +01:00
|
|
|
// 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, _ := user.Current()
|
|
|
|
host, _ := os.Hostname()
|
|
|
|
|
|
|
|
info := &LockInfo{
|
|
|
|
ID: id,
|
|
|
|
Who: fmt.Sprintf("%s@%s", username, host),
|
|
|
|
Version: terraform.Version,
|
|
|
|
Created: time.Now().UTC(),
|
|
|
|
}
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
|
|
|
// LockInfo stores lock metadata.
|
|
|
|
//
|
|
|
|
// Only Operation and Info are required to be set by the caller of Lock.
|
2017-02-14 23:43:42 +01:00
|
|
|
type LockInfo struct {
|
2017-02-15 16:25:04 +01:00
|
|
|
// Unique ID for the lock. NewLockInfo provides a random ID, but this may
|
|
|
|
// be overridden by the lock implementation. The final value if ID will be
|
|
|
|
// returned by the call to Lock.
|
|
|
|
ID string
|
|
|
|
|
|
|
|
// Terraform operation, provided by the caller.
|
|
|
|
Operation string
|
|
|
|
// Extra information to store with the lock, provided by the caller.
|
|
|
|
Info string
|
|
|
|
|
|
|
|
// user@hostname when available
|
|
|
|
Who string
|
|
|
|
// Terraform version
|
|
|
|
Version string
|
|
|
|
// Time that the lock was taken.
|
|
|
|
Created time.Time
|
|
|
|
|
|
|
|
// Path to the state file when applicable. Set by the Lock implementation.
|
|
|
|
Path string
|
2017-02-14 23:43:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Err returns the lock info formatted in an error
|
|
|
|
func (l *LockInfo) Err() error {
|
|
|
|
return fmt.Errorf("state locked. path:%q, created:%s, info:%q",
|
|
|
|
l.Path, l.Created, l.Info)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *LockInfo) String() string {
|
|
|
|
js, err := json.Marshal(l)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return string(js)
|
|
|
|
}
|
|
|
|
|
|
|
|
type LockError struct {
|
|
|
|
Info *LockInfo
|
|
|
|
Err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *LockError) Error() string {
|
|
|
|
var out []string
|
|
|
|
if e.Err != nil {
|
|
|
|
out = append(out, e.Err.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
if e.Info != nil {
|
|
|
|
out = append(out, e.Info.Err().Error())
|
|
|
|
}
|
|
|
|
return strings.Join(out, "\n")
|
2017-01-10 00:00:05 +01:00
|
|
|
}
|