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