github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/states/statemgr/filesystem.go (about)

     1  package statemgr
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io"
     8  	"io/ioutil"
     9  	"log"
    10  	"os"
    11  	"path/filepath"
    12  	"sync"
    13  	"time"
    14  
    15  	multierror "github.com/hashicorp/go-multierror"
    16  
    17  	"github.com/hashicorp/terraform/internal/states"
    18  	"github.com/hashicorp/terraform/internal/states/statefile"
    19  	"github.com/hashicorp/terraform/internal/terraform"
    20  )
    21  
    22  // Filesystem is a full state manager that uses a file in the local filesystem
    23  // for persistent storage.
    24  //
    25  // The transient storage for Filesystem is always in-memory.
    26  type Filesystem struct {
    27  	mu sync.Mutex
    28  
    29  	// path is the location where a file will be created or replaced for
    30  	// each persistent snapshot.
    31  	path string
    32  
    33  	// readPath is read by RefreshState instead of "path" until the first
    34  	// call to PersistState, after which it is ignored.
    35  	//
    36  	// The file at readPath must never be written to by this manager.
    37  	readPath string
    38  
    39  	// backupPath is an optional extra path which, if non-empty, will be
    40  	// created or overwritten with the first state snapshot we read if there
    41  	// is a subsequent call to write a different state.
    42  	backupPath string
    43  
    44  	// the file handle corresponding to PathOut
    45  	stateFileOut *os.File
    46  
    47  	// While the stateFileOut will correspond to the lock directly,
    48  	// store and check the lock ID to maintain a strict statemgr.Locker
    49  	// implementation.
    50  	lockID string
    51  
    52  	// created is set to true if stateFileOut didn't exist before we created it.
    53  	// This is mostly so we can clean up empty files during tests, but doesn't
    54  	// hurt to remove file we never wrote to.
    55  	created bool
    56  
    57  	file          *statefile.File
    58  	readFile      *statefile.File
    59  	backupFile    *statefile.File
    60  	writtenBackup bool
    61  }
    62  
    63  var (
    64  	_ Full           = (*Filesystem)(nil)
    65  	_ PersistentMeta = (*Filesystem)(nil)
    66  	_ Migrator       = (*Filesystem)(nil)
    67  )
    68  
    69  // NewFilesystem creates a filesystem-based state manager that reads and writes
    70  // state snapshots at the given filesystem path.
    71  //
    72  // This is equivalent to calling NewFileSystemBetweenPaths with statePath as
    73  // both of the path arguments.
    74  func NewFilesystem(statePath string) *Filesystem {
    75  	return &Filesystem{
    76  		path:     statePath,
    77  		readPath: statePath,
    78  	}
    79  }
    80  
    81  // NewFilesystemBetweenPaths creates a filesystem-based state manager that
    82  // reads an initial snapshot from readPath and then writes all new snapshots to
    83  // writePath.
    84  func NewFilesystemBetweenPaths(readPath, writePath string) *Filesystem {
    85  	return &Filesystem{
    86  		path:     writePath,
    87  		readPath: readPath,
    88  	}
    89  }
    90  
    91  // SetBackupPath configures the receiever so that it will create a local
    92  // backup file of the next state snapshot it reads (in State) if a different
    93  // snapshot is subsequently written (in WriteState). Only one backup is
    94  // written for the lifetime of the object, unless reset as described below.
    95  //
    96  // For correct operation, this must be called before any other state methods
    97  // are called. If called multiple times, each call resets the backup
    98  // function so that the next read will become the backup snapshot and a
    99  // following write will save a backup of it.
   100  func (s *Filesystem) SetBackupPath(path string) {
   101  	s.backupPath = path
   102  	s.backupFile = nil
   103  	s.writtenBackup = false
   104  }
   105  
   106  // BackupPath returns the manager's backup path if backup files are enabled,
   107  // or an empty string otherwise.
   108  func (s *Filesystem) BackupPath() string {
   109  	return s.backupPath
   110  }
   111  
   112  // State is an implementation of Reader.
   113  func (s *Filesystem) State() *states.State {
   114  	defer s.mutex()()
   115  	if s.file == nil {
   116  		return nil
   117  	}
   118  	return s.file.DeepCopy().State
   119  }
   120  
   121  // WriteState is an incorrect implementation of Writer that actually also
   122  // persists.
   123  func (s *Filesystem) WriteState(state *states.State) error {
   124  	// TODO: this should use a more robust method of writing state, by first
   125  	// writing to a temp file on the same filesystem, and renaming the file over
   126  	// the original.
   127  
   128  	defer s.mutex()()
   129  
   130  	if s.readFile == nil {
   131  		err := s.refreshState()
   132  		if err != nil {
   133  			return err
   134  		}
   135  	}
   136  
   137  	return s.writeState(state, nil)
   138  }
   139  
   140  func (s *Filesystem) writeState(state *states.State, meta *SnapshotMeta) error {
   141  	if s.stateFileOut == nil {
   142  		if err := s.createStateFiles(); err != nil {
   143  			return nil
   144  		}
   145  	}
   146  	defer s.stateFileOut.Sync()
   147  
   148  	// We'll try to write our backup first, so we can be sure we've created
   149  	// it successfully before clobbering the original file it came from.
   150  	if !s.writtenBackup && s.backupFile != nil && s.backupPath != "" {
   151  		if !statefile.StatesMarshalEqual(state, s.backupFile.State) {
   152  			log.Printf("[TRACE] statemgr.Filesystem: creating backup snapshot at %s", s.backupPath)
   153  			bfh, err := os.Create(s.backupPath)
   154  			if err != nil {
   155  				return fmt.Errorf("failed to create local state backup file: %s", err)
   156  			}
   157  			defer bfh.Close()
   158  
   159  			err = statefile.Write(s.backupFile, bfh)
   160  			if err != nil {
   161  				return fmt.Errorf("failed to write to local state backup file: %s", err)
   162  			}
   163  
   164  			s.writtenBackup = true
   165  		} else {
   166  			log.Print("[TRACE] statemgr.Filesystem: not making a backup, because the new snapshot is identical to the old")
   167  		}
   168  	} else {
   169  		// This branch is all just logging, to help understand why we didn't make a backup.
   170  		switch {
   171  		case s.backupPath == "":
   172  			log.Print("[TRACE] statemgr.Filesystem: state file backups are disabled")
   173  		case s.writtenBackup:
   174  			log.Printf("[TRACE] statemgr.Filesystem: have already backed up original %s to %s on a previous write", s.path, s.backupPath)
   175  		case s.backupFile == nil:
   176  			log.Printf("[TRACE] statemgr.Filesystem: no original state snapshot to back up")
   177  		default:
   178  			log.Printf("[TRACE] statemgr.Filesystem: not creating a backup for an unknown reason")
   179  		}
   180  	}
   181  
   182  	s.file = s.file.DeepCopy()
   183  	if s.file == nil {
   184  		s.file = NewStateFile()
   185  	}
   186  	s.file.State = state.DeepCopy()
   187  
   188  	if _, err := s.stateFileOut.Seek(0, io.SeekStart); err != nil {
   189  		return err
   190  	}
   191  	if err := s.stateFileOut.Truncate(0); err != nil {
   192  		return err
   193  	}
   194  
   195  	if state == nil {
   196  		// if we have no state, don't write anything else.
   197  		log.Print("[TRACE] statemgr.Filesystem: state is nil, so leaving the file empty")
   198  		return nil
   199  	}
   200  
   201  	if meta == nil {
   202  		if s.readFile == nil || !statefile.StatesMarshalEqual(s.file.State, s.readFile.State) {
   203  			s.file.Serial++
   204  			log.Printf("[TRACE] statemgr.Filesystem: state has changed since last snapshot, so incrementing serial to %d", s.file.Serial)
   205  		} else {
   206  			log.Print("[TRACE] statemgr.Filesystem: no state changes since last snapshot")
   207  		}
   208  	} else {
   209  		// Force new metadata
   210  		s.file.Lineage = meta.Lineage
   211  		s.file.Serial = meta.Serial
   212  		log.Printf("[TRACE] statemgr.Filesystem: forcing lineage %q serial %d for migration/import", s.file.Lineage, s.file.Serial)
   213  	}
   214  
   215  	log.Printf("[TRACE] statemgr.Filesystem: writing snapshot at %s", s.path)
   216  	if err := statefile.Write(s.file, s.stateFileOut); err != nil {
   217  		return err
   218  	}
   219  
   220  	// Any future reads must come from the file we've now updated
   221  	s.readPath = s.path
   222  	return nil
   223  }
   224  
   225  // PersistState is an implementation of Persister that does nothing because
   226  // this type's Writer implementation does its own persistence.
   227  func (s *Filesystem) PersistState(schemas *terraform.Schemas) error {
   228  	return nil
   229  }
   230  
   231  // RefreshState is an implementation of Refresher.
   232  func (s *Filesystem) RefreshState() error {
   233  	defer s.mutex()()
   234  	return s.refreshState()
   235  }
   236  
   237  func (s *Filesystem) GetRootOutputValues() (map[string]*states.OutputValue, error) {
   238  	err := s.RefreshState()
   239  	if err != nil {
   240  		return nil, err
   241  	}
   242  
   243  	state := s.State()
   244  	if state == nil {
   245  		state = states.NewState()
   246  	}
   247  
   248  	return state.RootModule().OutputValues, nil
   249  }
   250  
   251  func (s *Filesystem) refreshState() error {
   252  	var reader io.Reader
   253  
   254  	// The s.readPath file is only OK to read if we have not written any state out
   255  	// (in which case the same state needs to be read in), and no state output file
   256  	// has been opened (possibly via a lock) or the input path is different
   257  	// than the output path.
   258  	// This is important for Windows, as if the input file is the same as the
   259  	// output file, and the output file has been locked already, we can't open
   260  	// the file again.
   261  	if s.stateFileOut == nil || s.readPath != s.path {
   262  		// we haven't written a state file yet, so load from readPath
   263  		log.Printf("[TRACE] statemgr.Filesystem: reading initial snapshot from %s", s.readPath)
   264  		f, err := os.Open(s.readPath)
   265  		if err != nil {
   266  			// It is okay if the file doesn't exist; we'll treat that as a nil state.
   267  			if !os.IsNotExist(err) {
   268  				return err
   269  			}
   270  
   271  			// we need a non-nil reader for ReadState and an empty buffer works
   272  			// to return EOF immediately
   273  			reader = bytes.NewBuffer(nil)
   274  
   275  		} else {
   276  			defer f.Close()
   277  			reader = f
   278  		}
   279  	} else {
   280  		log.Printf("[TRACE] statemgr.Filesystem: reading latest snapshot from %s", s.path)
   281  		// no state to refresh
   282  		if s.stateFileOut == nil {
   283  			return nil
   284  		}
   285  
   286  		// we have a state file, make sure we're at the start
   287  		s.stateFileOut.Seek(0, io.SeekStart)
   288  		reader = s.stateFileOut
   289  	}
   290  
   291  	f, err := statefile.Read(reader)
   292  	// if there's no state then a nil file is fine
   293  	if err != nil {
   294  		if err != statefile.ErrNoState {
   295  			return err
   296  		}
   297  		log.Printf("[TRACE] statemgr.Filesystem: snapshot file has nil snapshot, but that's okay")
   298  	}
   299  
   300  	s.file = f
   301  	s.readFile = s.file.DeepCopy()
   302  	if s.file != nil {
   303  		log.Printf("[TRACE] statemgr.Filesystem: read snapshot with lineage %q serial %d", s.file.Lineage, s.file.Serial)
   304  	} else {
   305  		log.Print("[TRACE] statemgr.Filesystem: read nil snapshot")
   306  	}
   307  	return nil
   308  }
   309  
   310  // Lock implements Locker using filesystem discretionary locks.
   311  func (s *Filesystem) Lock(info *LockInfo) (string, error) {
   312  	defer s.mutex()()
   313  
   314  	if s.stateFileOut == nil {
   315  		if err := s.createStateFiles(); err != nil {
   316  			return "", err
   317  		}
   318  	}
   319  
   320  	if s.lockID != "" {
   321  		return "", fmt.Errorf("state %q already locked", s.stateFileOut.Name())
   322  	}
   323  
   324  	if err := s.lock(); err != nil {
   325  		info, infoErr := s.lockInfo()
   326  		if infoErr != nil {
   327  			err = multierror.Append(err, infoErr)
   328  		}
   329  
   330  		lockErr := &LockError{
   331  			Info: info,
   332  			Err:  err,
   333  		}
   334  
   335  		return "", lockErr
   336  	}
   337  
   338  	s.lockID = info.ID
   339  	return s.lockID, s.writeLockInfo(info)
   340  }
   341  
   342  // Unlock is the companion to Lock, completing the implemention of Locker.
   343  func (s *Filesystem) Unlock(id string) error {
   344  	defer s.mutex()()
   345  
   346  	if s.lockID == "" {
   347  		return fmt.Errorf("LocalState not locked")
   348  	}
   349  
   350  	if id != s.lockID {
   351  		idErr := fmt.Errorf("invalid lock id: %q. current id: %q", id, s.lockID)
   352  		info, err := s.lockInfo()
   353  		if err != nil {
   354  			idErr = multierror.Append(idErr, err)
   355  		}
   356  
   357  		return &LockError{
   358  			Err:  idErr,
   359  			Info: info,
   360  		}
   361  	}
   362  
   363  	lockInfoPath := s.lockInfoPath()
   364  	log.Printf("[TRACE] statemgr.Filesystem: removing lock metadata file %s", lockInfoPath)
   365  	os.Remove(lockInfoPath)
   366  
   367  	fileName := s.stateFileOut.Name()
   368  
   369  	unlockErr := s.unlock()
   370  
   371  	s.stateFileOut.Close()
   372  	s.stateFileOut = nil
   373  	s.lockID = ""
   374  
   375  	// clean up the state file if we created it an never wrote to it
   376  	stat, err := os.Stat(fileName)
   377  	if err == nil && stat.Size() == 0 && s.created {
   378  		os.Remove(fileName)
   379  	}
   380  
   381  	return unlockErr
   382  }
   383  
   384  // StateSnapshotMeta returns the metadata from the most recently persisted
   385  // or refreshed persistent state snapshot.
   386  //
   387  // This is an implementation of PersistentMeta.
   388  func (s *Filesystem) StateSnapshotMeta() SnapshotMeta {
   389  	if s.file == nil {
   390  		return SnapshotMeta{} // placeholder
   391  	}
   392  
   393  	return SnapshotMeta{
   394  		Lineage: s.file.Lineage,
   395  		Serial:  s.file.Serial,
   396  
   397  		TerraformVersion: s.file.TerraformVersion,
   398  	}
   399  }
   400  
   401  // StateForMigration is part of our implementation of Migrator.
   402  func (s *Filesystem) StateForMigration() *statefile.File {
   403  	return s.file.DeepCopy()
   404  }
   405  
   406  // WriteStateForMigration is part of our implementation of Migrator.
   407  func (s *Filesystem) WriteStateForMigration(f *statefile.File, force bool) error {
   408  	defer s.mutex()()
   409  
   410  	if s.readFile == nil {
   411  		err := s.refreshState()
   412  		if err != nil {
   413  			return err
   414  		}
   415  	}
   416  
   417  	if !force {
   418  		err := CheckValidImport(f, s.readFile)
   419  		if err != nil {
   420  			return err
   421  		}
   422  	}
   423  
   424  	if s.readFile != nil {
   425  		log.Printf(
   426  			"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d over snapshot with lineage %q serial %d at %s",
   427  			f.Lineage, f.Serial,
   428  			s.readFile.Lineage, s.readFile.Serial,
   429  			s.path,
   430  		)
   431  	} else {
   432  		log.Printf(
   433  			"[TRACE] statemgr.Filesystem: Importing snapshot with lineage %q serial %d as the initial state snapshot at %s",
   434  			f.Lineage, f.Serial,
   435  			s.path,
   436  		)
   437  	}
   438  
   439  	err := s.writeState(f.State, &SnapshotMeta{Lineage: f.Lineage, Serial: f.Serial})
   440  	if err != nil {
   441  		return err
   442  	}
   443  
   444  	return nil
   445  }
   446  
   447  // Open the state file, creating the directories and file as needed.
   448  func (s *Filesystem) createStateFiles() error {
   449  	log.Printf("[TRACE] statemgr.Filesystem: preparing to manage state snapshots at %s", s.path)
   450  
   451  	// This could race, but we only use it to clean up empty files
   452  	if _, err := os.Stat(s.path); os.IsNotExist(err) {
   453  		s.created = true
   454  	}
   455  
   456  	// Create all the directories
   457  	if err := os.MkdirAll(filepath.Dir(s.path), 0755); err != nil {
   458  		return err
   459  	}
   460  
   461  	f, err := os.OpenFile(s.path, os.O_RDWR|os.O_CREATE, 0666)
   462  	if err != nil {
   463  		return err
   464  	}
   465  
   466  	s.stateFileOut = f
   467  
   468  	// If the file already existed with content then that'll be the content
   469  	// of our backup file if we write a change later.
   470  	s.backupFile, err = statefile.Read(s.stateFileOut)
   471  	if err != nil {
   472  		if err != statefile.ErrNoState {
   473  			return err
   474  		}
   475  		log.Printf("[TRACE] statemgr.Filesystem: no previously-stored snapshot exists")
   476  	} else {
   477  		log.Printf("[TRACE] statemgr.Filesystem: existing snapshot has lineage %q serial %d", s.backupFile.Lineage, s.backupFile.Serial)
   478  	}
   479  
   480  	// Refresh now, to load in the snapshot if the file already existed
   481  	return nil
   482  }
   483  
   484  // return the path for the lockInfo metadata.
   485  func (s *Filesystem) lockInfoPath() string {
   486  	stateDir, stateName := filepath.Split(s.path)
   487  	if stateName == "" {
   488  		panic("empty state file path")
   489  	}
   490  
   491  	if stateName[0] == '.' {
   492  		stateName = stateName[1:]
   493  	}
   494  
   495  	return filepath.Join(stateDir, fmt.Sprintf(".%s.lock.info", stateName))
   496  }
   497  
   498  // lockInfo returns the data in a lock info file
   499  func (s *Filesystem) lockInfo() (*LockInfo, error) {
   500  	path := s.lockInfoPath()
   501  	infoData, err := ioutil.ReadFile(path)
   502  	if err != nil {
   503  		return nil, err
   504  	}
   505  
   506  	info := LockInfo{}
   507  	err = json.Unmarshal(infoData, &info)
   508  	if err != nil {
   509  		return nil, fmt.Errorf("state file %q locked, but could not unmarshal lock info: %s", s.readPath, err)
   510  	}
   511  	return &info, nil
   512  }
   513  
   514  // write a new lock info file
   515  func (s *Filesystem) writeLockInfo(info *LockInfo) error {
   516  	path := s.lockInfoPath()
   517  	info.Path = s.readPath
   518  	info.Created = time.Now().UTC()
   519  
   520  	log.Printf("[TRACE] statemgr.Filesystem: writing lock metadata to %s", path)
   521  	err := ioutil.WriteFile(path, info.Marshal(), 0600)
   522  	if err != nil {
   523  		return fmt.Errorf("could not write lock info for %q: %s", s.readPath, err)
   524  	}
   525  	return nil
   526  }
   527  
   528  func (s *Filesystem) mutex() func() {
   529  	s.mu.Lock()
   530  	return s.mu.Unlock
   531  }