github.com/paultyng/terraform@v0.6.11-0.20180227224804-66ff8f8bed40/state/local.go (about)

     1  package state
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"os"
    10  	"path/filepath"
    11  	"sync"
    12  	"time"
    13  
    14  	multierror "github.com/hashicorp/go-multierror"
    15  	"github.com/hashicorp/terraform/terraform"
    16  )
    17  
    18  // LocalState manages a state storage that is local to the filesystem.
    19  type LocalState struct {
    20  	mu sync.Mutex
    21  
    22  	// Path is the path to read the state from. PathOut is the path to
    23  	// write the state to. If PathOut is not specified, Path will be used.
    24  	// If PathOut already exists, it will be overwritten.
    25  	Path    string
    26  	PathOut string
    27  
    28  	// the file handle corresponding to PathOut
    29  	stateFileOut *os.File
    30  
    31  	// While the stateFileOut will correspond to the lock directly,
    32  	// store and check the lock ID to maintain a strict state.Locker
    33  	// implementation.
    34  	lockID string
    35  
    36  	// created is set to true if stateFileOut didn't exist before we created it.
    37  	// This is mostly so we can clean up emtpy files during tests, but doesn't
    38  	// hurt to remove file we never wrote to.
    39  	created bool
    40  
    41  	state     *terraform.State
    42  	readState *terraform.State
    43  	written   bool
    44  }
    45  
    46  // SetState will force a specific state in-memory for this local state.
    47  func (s *LocalState) SetState(state *terraform.State) {
    48  	s.mu.Lock()
    49  	defer s.mu.Unlock()
    50  
    51  	s.state = state.DeepCopy()
    52  	s.readState = state.DeepCopy()
    53  }
    54  
    55  // StateReader impl.
    56  func (s *LocalState) State() *terraform.State {
    57  	return s.state.DeepCopy()
    58  }
    59  
    60  // WriteState for LocalState always persists the state as well.
    61  // TODO: this should use a more robust method of writing state, by first
    62  // writing to a temp file on the same filesystem, and renaming the file over
    63  // the original.
    64  //
    65  // StateWriter impl.
    66  func (s *LocalState) WriteState(state *terraform.State) error {
    67  	s.mu.Lock()
    68  	defer s.mu.Unlock()
    69  
    70  	if s.stateFileOut == nil {
    71  		if err := s.createStateFiles(); err != nil {
    72  			return nil
    73  		}
    74  	}
    75  	defer s.stateFileOut.Sync()
    76  
    77  	s.state = state.DeepCopy() // don't want mutations before we actually get this written to disk
    78  
    79  	if s.readState != nil && s.state != nil {
    80  		// We don't trust callers to properly manage serials. Instead, we assume
    81  		// that a WriteState is always for the next version after what was
    82  		// most recently read.
    83  		s.state.Serial = s.readState.Serial
    84  	}
    85  
    86  	if _, err := s.stateFileOut.Seek(0, os.SEEK_SET); err != nil {
    87  		return err
    88  	}
    89  	if err := s.stateFileOut.Truncate(0); err != nil {
    90  		return err
    91  	}
    92  
    93  	if state == nil {
    94  		// if we have no state, don't write anything else.
    95  		return nil
    96  	}
    97  
    98  	if !s.state.MarshalEqual(s.readState) {
    99  		s.state.Serial++
   100  	}
   101  
   102  	if err := terraform.WriteState(s.state, s.stateFileOut); err != nil {
   103  		return err
   104  	}
   105  
   106  	s.written = true
   107  	return nil
   108  }
   109  
   110  // PersistState for LocalState is a no-op since WriteState always persists.
   111  //
   112  // StatePersister impl.
   113  func (s *LocalState) PersistState() error {
   114  	return nil
   115  }
   116  
   117  // StateRefresher impl.
   118  func (s *LocalState) RefreshState() error {
   119  	s.mu.Lock()
   120  	defer s.mu.Unlock()
   121  
   122  	var reader io.Reader
   123  	if !s.written {
   124  		// we haven't written a state file yet, so load from Path
   125  		f, err := os.Open(s.Path)
   126  		if err != nil {
   127  			// It is okay if the file doesn't exist, we treat that as a nil state
   128  			if !os.IsNotExist(err) {
   129  				return err
   130  			}
   131  
   132  			// we need a non-nil reader for ReadState and an empty buffer works
   133  			// to return EOF immediately
   134  			reader = bytes.NewBuffer(nil)
   135  
   136  		} else {
   137  			defer f.Close()
   138  			reader = f
   139  		}
   140  	} else {
   141  		// no state to refresh
   142  		if s.stateFileOut == nil {
   143  			return nil
   144  		}
   145  
   146  		// we have a state file, make sure we're at the start
   147  		s.stateFileOut.Seek(0, os.SEEK_SET)
   148  		reader = s.stateFileOut
   149  	}
   150  
   151  	state, err := terraform.ReadState(reader)
   152  	// if there's no state we just assign the nil return value
   153  	if err != nil && err != terraform.ErrNoState {
   154  		return err
   155  	}
   156  
   157  	s.state = state
   158  	s.readState = s.state.DeepCopy()
   159  	return nil
   160  }
   161  
   162  // Lock implements a local filesystem state.Locker.
   163  func (s *LocalState) Lock(info *LockInfo) (string, error) {
   164  	s.mu.Lock()
   165  	defer s.mu.Unlock()
   166  
   167  	if s.stateFileOut == nil {
   168  		if err := s.createStateFiles(); err != nil {
   169  			return "", err
   170  		}
   171  	}
   172  
   173  	if s.lockID != "" {
   174  		return "", fmt.Errorf("state %q already locked", s.stateFileOut.Name())
   175  	}
   176  
   177  	if err := s.lock(); err != nil {
   178  		info, infoErr := s.lockInfo()
   179  		if infoErr != nil {
   180  			err = multierror.Append(err, infoErr)
   181  		}
   182  
   183  		lockErr := &LockError{
   184  			Info: info,
   185  			Err:  err,
   186  		}
   187  
   188  		return "", lockErr
   189  	}
   190  
   191  	s.lockID = info.ID
   192  	return s.lockID, s.writeLockInfo(info)
   193  }
   194  
   195  func (s *LocalState) Unlock(id string) error {
   196  	s.mu.Lock()
   197  	defer s.mu.Unlock()
   198  
   199  	if s.lockID == "" {
   200  		return fmt.Errorf("LocalState not locked")
   201  	}
   202  
   203  	if id != s.lockID {
   204  		idErr := fmt.Errorf("invalid lock id: %q. current id: %q", id, s.lockID)
   205  		info, err := s.lockInfo()
   206  		if err != nil {
   207  			err = multierror.Append(idErr, err)
   208  		}
   209  
   210  		return &LockError{
   211  			Err:  idErr,
   212  			Info: info,
   213  		}
   214  	}
   215  
   216  	os.Remove(s.lockInfoPath())
   217  
   218  	fileName := s.stateFileOut.Name()
   219  
   220  	unlockErr := s.unlock()
   221  
   222  	s.stateFileOut.Close()
   223  	s.stateFileOut = nil
   224  	s.lockID = ""
   225  
   226  	// clean up the state file if we created it an never wrote to it
   227  	stat, err := os.Stat(fileName)
   228  	if err == nil && stat.Size() == 0 && s.created {
   229  		os.Remove(fileName)
   230  	}
   231  
   232  	return unlockErr
   233  }
   234  
   235  // Open the state file, creating the directories and file as needed.
   236  func (s *LocalState) createStateFiles() error {
   237  	if s.PathOut == "" {
   238  		s.PathOut = s.Path
   239  	}
   240  
   241  	// yes this could race, but we only use it to clean up empty files
   242  	if _, err := os.Stat(s.PathOut); os.IsNotExist(err) {
   243  		s.created = true
   244  	}
   245  
   246  	// Create all the directories
   247  	if err := os.MkdirAll(filepath.Dir(s.PathOut), 0755); err != nil {
   248  		return err
   249  	}
   250  
   251  	f, err := os.OpenFile(s.PathOut, os.O_RDWR|os.O_CREATE, 0666)
   252  	if err != nil {
   253  		return err
   254  	}
   255  
   256  	s.stateFileOut = f
   257  	return nil
   258  }
   259  
   260  // return the path for the lockInfo metadata.
   261  func (s *LocalState) lockInfoPath() string {
   262  	stateDir, stateName := filepath.Split(s.Path)
   263  	if stateName == "" {
   264  		panic("empty state file path")
   265  	}
   266  
   267  	if stateName[0] == '.' {
   268  		stateName = stateName[1:]
   269  	}
   270  
   271  	return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
   272  }
   273  
   274  // lockInfo returns the data in a lock info file
   275  func (s *LocalState) lockInfo() (*LockInfo, error) {
   276  	path := s.lockInfoPath()
   277  	infoData, err := ioutil.ReadFile(path)
   278  	if err != nil {
   279  		return nil, err
   280  	}
   281  
   282  	info := LockInfo{}
   283  	err = json.Unmarshal(infoData, &info)
   284  	if err != nil {
   285  		return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.Path, err)
   286  	}
   287  	return &info, nil
   288  }
   289  
   290  // write a new lock info file
   291  func (s *LocalState) writeLockInfo(info *LockInfo) error {
   292  	path := s.lockInfoPath()
   293  	info.Path = s.Path
   294  	info.Created = time.Now().UTC()
   295  
   296  	err := ioutil.WriteFile(path, info.Marshal(), 0600)
   297  	if err != nil {
   298  		return fmt.Errorf("could not write lock info for %q: %s", s.Path, err)
   299  	}
   300  	return nil
   301  }