|
|
|
|
@ -13,30 +13,6 @@ import (
|
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// lock metadata structure for local locks
|
|
|
|
|
type LockInfo struct {
|
|
|
|
|
// Path to the state file
|
|
|
|
|
Path string
|
|
|
|
|
// The time the lock was taken
|
|
|
|
|
Created time.Time
|
|
|
|
|
// Extra info passed to State.Lock
|
|
|
|
|
Info string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LocalState manages a state storage that is local to the filesystem.
|
|
|
|
|
type LocalState struct {
|
|
|
|
|
// Path is the path to read the state from. PathOut is the path to
|
|
|
|
|
@ -47,7 +23,8 @@ type LocalState struct {
|
|
|
|
|
|
|
|
|
|
// the file handle corresponding to PathOut
|
|
|
|
|
stateFileOut *os.File
|
|
|
|
|
// created is set to tru if stateFileOut didn't exist before we created it.
|
|
|
|
|
|
|
|
|
|
// created is set to true if stateFileOut didn't exist before we created it.
|
|
|
|
|
// This is mostly so we can clean up emtpy files during tests, but doesn't
|
|
|
|
|
// hurt to remove file we never wrote to.
|
|
|
|
|
created bool
|
|
|
|
|
@ -57,82 +34,36 @@ type LocalState struct {
|
|
|
|
|
written bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// SetState will force a specific state in-memory for this local state.
|
|
|
|
|
func (s *LocalState) SetState(state *terraform.State) {
|
|
|
|
|
s.state = state
|
|
|
|
|
s.readState = state
|
|
|
|
|
// LockInfo stores metadata for locks taken.
|
|
|
|
|
type LockInfo struct {
|
|
|
|
|
Path string // Path to the state file
|
|
|
|
|
Created time.Time // The time the lock was taken
|
|
|
|
|
Info string // Extra info passed to State.Lock
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StateReader impl.
|
|
|
|
|
func (s *LocalState) State() *terraform.State {
|
|
|
|
|
return s.state.DeepCopy()
|
|
|
|
|
// 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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lock implements a local filesystem state.Locker.
|
|
|
|
|
func (s *LocalState) Lock(reason string) error {
|
|
|
|
|
if s.stateFileOut == nil {
|
|
|
|
|
if err := s.createStateFiles(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := s.lock(); err != nil {
|
|
|
|
|
if info, err := s.lockInfo(); err == nil {
|
|
|
|
|
return info.Err()
|
|
|
|
|
}
|
|
|
|
|
return fmt.Errorf("state file %q locked: %s", s.Path, err)
|
|
|
|
|
func (l *LockInfo) String() string {
|
|
|
|
|
js, err := json.Marshal(l)
|
|
|
|
|
if err != nil {
|
|
|
|
|
panic(err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.writeLockInfo(reason)
|
|
|
|
|
return string(js)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LocalState) Unlock() error {
|
|
|
|
|
// we can't be locked if we don't have a file
|
|
|
|
|
if s.stateFileOut == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
os.Remove(s.lockInfoPath())
|
|
|
|
|
|
|
|
|
|
fileName := s.stateFileOut.Name()
|
|
|
|
|
|
|
|
|
|
unlockErr := s.unlock()
|
|
|
|
|
|
|
|
|
|
s.stateFileOut.Close()
|
|
|
|
|
s.stateFileOut = nil
|
|
|
|
|
|
|
|
|
|
// clean up the state file if we created it an never wrote to it
|
|
|
|
|
stat, err := os.Stat(fileName)
|
|
|
|
|
if err == nil && stat.Size() == 0 && s.created {
|
|
|
|
|
os.Remove(fileName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unlockErr
|
|
|
|
|
// SetState will force a specific state in-memory for this local state.
|
|
|
|
|
func (s *LocalState) SetState(state *terraform.State) {
|
|
|
|
|
s.state = state
|
|
|
|
|
s.readState = state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open the state file, creating the directories and file as needed.
|
|
|
|
|
func (s *LocalState) createStateFiles() error {
|
|
|
|
|
if s.PathOut == "" {
|
|
|
|
|
s.PathOut = s.Path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// yes this could race, but we only use it to clean up empty files
|
|
|
|
|
if _, err := os.Stat(s.PathOut); os.IsNotExist(err) {
|
|
|
|
|
s.created = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create all the directories
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(s.PathOut), 0755); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f, err := os.OpenFile(s.PathOut, os.O_RDWR|os.O_CREATE, 0666)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.stateFileOut = f
|
|
|
|
|
return nil
|
|
|
|
|
// StateReader impl.
|
|
|
|
|
func (s *LocalState) State() *terraform.State {
|
|
|
|
|
return s.state.DeepCopy()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// WriteState for LocalState always persists the state as well.
|
|
|
|
|
@ -223,6 +154,74 @@ func (s *LocalState) RefreshState() error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lock implements a local filesystem state.Locker.
|
|
|
|
|
func (s *LocalState) Lock(reason string) error {
|
|
|
|
|
if s.stateFileOut == nil {
|
|
|
|
|
if err := s.createStateFiles(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := s.lock(); err != nil {
|
|
|
|
|
if info, err := s.lockInfo(); err == nil {
|
|
|
|
|
return info.Err()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fmt.Errorf("state file %q locked: %s", s.Path, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return s.writeLockInfo(reason)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *LocalState) Unlock() error {
|
|
|
|
|
// we can't be locked if we don't have a file
|
|
|
|
|
if s.stateFileOut == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
os.Remove(s.lockInfoPath())
|
|
|
|
|
|
|
|
|
|
fileName := s.stateFileOut.Name()
|
|
|
|
|
|
|
|
|
|
unlockErr := s.unlock()
|
|
|
|
|
|
|
|
|
|
s.stateFileOut.Close()
|
|
|
|
|
s.stateFileOut = nil
|
|
|
|
|
|
|
|
|
|
// clean up the state file if we created it an never wrote to it
|
|
|
|
|
stat, err := os.Stat(fileName)
|
|
|
|
|
if err == nil && stat.Size() == 0 && s.created {
|
|
|
|
|
os.Remove(fileName)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return unlockErr
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Open the state file, creating the directories and file as needed.
|
|
|
|
|
func (s *LocalState) createStateFiles() error {
|
|
|
|
|
if s.PathOut == "" {
|
|
|
|
|
s.PathOut = s.Path
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// yes this could race, but we only use it to clean up empty files
|
|
|
|
|
if _, err := os.Stat(s.PathOut); os.IsNotExist(err) {
|
|
|
|
|
s.created = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create all the directories
|
|
|
|
|
if err := os.MkdirAll(filepath.Dir(s.PathOut), 0755); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
f, err := os.OpenFile(s.PathOut, os.O_RDWR|os.O_CREATE, 0666)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
s.stateFileOut = f
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// return the path for the lockInfo metadata.
|
|
|
|
|
func (s *LocalState) lockInfoPath() string {
|
|
|
|
|
stateDir, stateName := filepath.Split(s.Path)
|
|
|
|
|
|