2015-02-21 20:52:55 +01:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
2017-01-10 00:15:48 +01:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2015-02-21 20:52:55 +01:00
|
|
|
"os"
|
2015-02-23 18:53:20 +01:00
|
|
|
"path/filepath"
|
2017-01-10 00:15:48 +01:00
|
|
|
"time"
|
2015-02-21 20:52:55 +01:00
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
)
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
// lock metadata structure for local locks
|
|
|
|
type lockInfo struct {
|
|
|
|
// Path to the state file
|
|
|
|
Path string
|
|
|
|
// The time the lock was taken
|
2017-01-12 22:55:42 +01:00
|
|
|
Created time.Time
|
2017-01-10 00:15:48 +01:00
|
|
|
// The time this lock expires
|
|
|
|
Expires time.Time
|
|
|
|
// The lock reason passed to State.Lock
|
|
|
|
Reason string
|
|
|
|
}
|
|
|
|
|
|
|
|
// return the lock info formatted in an error
|
|
|
|
func (l *lockInfo) Err() error {
|
|
|
|
return fmt.Errorf("state file %q locked. created:%s, expires:%s, reason:%s",
|
2017-01-12 22:55:42 +01:00
|
|
|
l.Path, l.Created, l.Expires, l.Reason)
|
2017-01-10 00:15:48 +01:00
|
|
|
}
|
|
|
|
|
2015-02-21 20:52:55 +01:00
|
|
|
// LocalState manages a state storage that is local to the filesystem.
|
|
|
|
type LocalState struct {
|
2015-02-22 01:11:47 +01:00
|
|
|
// Path is the path to read the state from. PathOut is the path to
|
|
|
|
// write the state to. If PathOut is not specified, Path will be used.
|
|
|
|
// If PathOut already exists, it will be overwritten.
|
|
|
|
Path string
|
|
|
|
PathOut string
|
2015-02-21 20:52:55 +01:00
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
// the file handles corresponding to Path and PathOut
|
|
|
|
stateFile *os.File
|
|
|
|
stateFileOut *os.File
|
|
|
|
|
2015-02-24 06:26:33 +01:00
|
|
|
state *terraform.State
|
|
|
|
readState *terraform.State
|
|
|
|
written bool
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
|
|
|
|
2015-02-22 03:00:08 +01:00
|
|
|
// SetState will force a specific state in-memory for this local state.
|
|
|
|
func (s *LocalState) SetState(state *terraform.State) {
|
|
|
|
s.state = state
|
2015-02-24 06:26:33 +01:00
|
|
|
s.readState = state
|
2015-02-22 03:00:08 +01:00
|
|
|
}
|
|
|
|
|
2015-02-21 20:52:55 +01:00
|
|
|
// StateReader impl.
|
|
|
|
func (s *LocalState) State() *terraform.State {
|
2015-02-24 06:36:35 +01:00
|
|
|
return s.state.DeepCopy()
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
// 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 {
|
|
|
|
os.Remove(s.lockInfoPath())
|
|
|
|
return s.unlock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open the state file, creating the directories and file as needed.
|
|
|
|
func (s *LocalState) createStateFiles() error {
|
|
|
|
f, err := createFileAndDirs(s.Path)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
s.stateFile = f
|
|
|
|
|
|
|
|
if s.PathOut == "" {
|
|
|
|
s.PathOut = s.Path
|
|
|
|
}
|
|
|
|
|
|
|
|
if s.PathOut == s.Path {
|
|
|
|
s.stateFileOut = s.stateFile
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err = createFileAndDirs(s.PathOut)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
s.stateFileOut = f
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func createFileAndDirs(path string) (*os.File, error) {
|
|
|
|
// Create all the directories
|
|
|
|
if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0666)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
|
2015-02-21 20:52:55 +01:00
|
|
|
// WriteState for LocalState always persists the state as well.
|
2017-01-10 00:15:48 +01:00
|
|
|
// TODO: this should use a more robust method of writing state, by first
|
|
|
|
// writing to a temp file on the same filesystem, and renaming the file over
|
|
|
|
// the original.
|
2015-02-21 20:52:55 +01:00
|
|
|
//
|
|
|
|
// StateWriter impl.
|
|
|
|
func (s *LocalState) WriteState(state *terraform.State) error {
|
2017-01-10 00:15:48 +01:00
|
|
|
if state == nil {
|
|
|
|
// if we have no state, don't write anything.
|
|
|
|
return nil
|
2015-02-22 01:11:47 +01:00
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
if s.stateFileOut == nil {
|
|
|
|
if err := s.createStateFiles(); err != nil {
|
2015-02-22 03:00:08 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
s.state = state
|
|
|
|
|
|
|
|
if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil {
|
2015-02-23 18:53:20 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
if err := s.stateFileOut.Truncate(0); err != nil {
|
2015-02-21 20:52:55 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-24 06:26:33 +01:00
|
|
|
s.state.IncrementSerialMaybe(s.readState)
|
|
|
|
s.readState = s.state
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
if err := terraform.WriteState(s.state, s.stateFileOut); err != nil {
|
2015-02-22 01:11:47 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
s.stateFileOut.Sync()
|
2015-02-22 01:11:47 +01:00
|
|
|
s.written = true
|
|
|
|
return nil
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// PersistState for LocalState is a no-op since WriteState always persists.
|
|
|
|
//
|
|
|
|
// StatePersister impl.
|
|
|
|
func (s *LocalState) PersistState() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateRefresher impl.
|
|
|
|
func (s *LocalState) RefreshState() error {
|
2017-01-10 00:15:48 +01:00
|
|
|
if s.stateFile == nil {
|
|
|
|
if err := s.createStateFiles(); err != nil {
|
2015-02-22 01:11:47 +01:00
|
|
|
return err
|
2015-02-22 01:06:27 +01:00
|
|
|
}
|
2017-01-10 00:15:48 +01:00
|
|
|
}
|
2015-02-22 01:06:27 +01:00
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
// make sure we're at the start of the file
|
|
|
|
if _, err := s.stateFile.Seek(0, os.SEEK_SET); err != nil {
|
|
|
|
return err
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
|
|
|
|
2017-01-10 00:15:48 +01:00
|
|
|
state, err := terraform.ReadState(s.stateFile)
|
|
|
|
// if there's no state we just assign the nil return value
|
|
|
|
if err != nil && err != terraform.ErrNoState {
|
|
|
|
return err
|
2015-02-21 20:52:55 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
s.state = state
|
2015-02-24 06:26:33 +01:00
|
|
|
s.readState = state
|
2015-02-21 20:52:55 +01:00
|
|
|
return nil
|
|
|
|
}
|
2017-01-10 00:15:48 +01:00
|
|
|
|
|
|
|
// return the path for the lockInfo metadata.
|
|
|
|
func (s *LocalState) lockInfoPath() string {
|
|
|
|
stateDir, stateName := filepath.Split(s.Path)
|
|
|
|
if stateName == "" {
|
|
|
|
panic("empty state file path")
|
|
|
|
}
|
|
|
|
|
|
|
|
if stateName[0] == '.' {
|
|
|
|
stateName = stateName[1:]
|
|
|
|
}
|
|
|
|
|
|
|
|
return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
|
|
|
|
}
|
|
|
|
|
|
|
|
// lockInfo returns the data in a lock info file
|
|
|
|
func (s *LocalState) lockInfo() (*lockInfo, error) {
|
|
|
|
path := s.lockInfoPath()
|
|
|
|
infoData, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
info := lockInfo{}
|
|
|
|
err = json.Unmarshal(infoData, &info)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.Path, err)
|
|
|
|
}
|
|
|
|
return &info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// write a new lock info file
|
|
|
|
func (s *LocalState) writeLockInfo(reason string) error {
|
|
|
|
path := s.lockInfoPath()
|
|
|
|
|
|
|
|
lockInfo := &lockInfo{
|
|
|
|
Path: s.Path,
|
2017-01-12 22:55:42 +01:00
|
|
|
Created: time.Now().UTC(),
|
|
|
|
Expires: time.Now().Add(time.Hour).UTC(),
|
2017-01-10 00:15:48 +01:00
|
|
|
Reason: reason,
|
|
|
|
}
|
|
|
|
|
|
|
|
infoData, err := json.Marshal(lockInfo)
|
|
|
|
if err != nil {
|
|
|
|
panic(fmt.Sprintf("could not marshal lock info: %#v", lockInfo))
|
|
|
|
}
|
|
|
|
|
|
|
|
err = ioutil.WriteFile(path, infoData, 0600)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("could not write lock info for %q: %s", s.Path, err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|