github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/states/statemgr/migrate.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package statemgr
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/terramate-io/tf/states/statefile"
    10  )
    11  
    12  // Migrator is an optional interface implemented by state managers that
    13  // are capable of direct migration of state snapshots with their associated
    14  // metadata unchanged.
    15  //
    16  // This interface is used when available by function Migrate. See that
    17  // function for more information on how it is used.
    18  type Migrator interface {
    19  	PersistentMeta
    20  
    21  	// StateForMigration returns a full statefile representing the latest
    22  	// snapshot (as would be returned by Reader.State) and the associated
    23  	// snapshot metadata (as would be returned by
    24  	// PersistentMeta.StateSnapshotMeta).
    25  	//
    26  	// Just as with Reader.State, this must not fail.
    27  	StateForMigration() *statefile.File
    28  
    29  	// WriteStateForMigration accepts a full statefile including associated
    30  	// snapshot metadata, and atomically updates the stored file (as with
    31  	// Writer.WriteState) and the metadata.
    32  	//
    33  	// If "force" is not set, the manager must call CheckValidImport with
    34  	// the given file and the current file and complete the update only if
    35  	// that function returns nil. If force is set this may override such
    36  	// checks, but some backends do not support forcing and so will act
    37  	// as if force is always false.
    38  	WriteStateForMigration(f *statefile.File, force bool) error
    39  }
    40  
    41  // Migrate writes the latest transient state snapshot from src into dest,
    42  // preserving snapshot metadata (serial and lineage) where possible.
    43  //
    44  // If both managers implement the optional interface Migrator then it will
    45  // be used to copy the snapshot and its associated metadata. Otherwise,
    46  // the normal Reader and Writer interfaces will be used instead.
    47  //
    48  // If the destination manager refuses the new state or fails to write it then
    49  // its error is returned directly.
    50  //
    51  // For state managers that also implement Persistent, it is the caller's
    52  // responsibility to persist the newly-written state after a successful result,
    53  // just as with calls to Writer.WriteState.
    54  //
    55  // This function doesn't do any locking of its own, so if the state managers
    56  // also implement Locker the caller should hold a lock on both managers
    57  // for the duration of this call.
    58  func Migrate(dst, src Transient) error {
    59  	if dstM, ok := dst.(Migrator); ok {
    60  		if srcM, ok := src.(Migrator); ok {
    61  			// Full-fidelity migration, them.
    62  			s := srcM.StateForMigration()
    63  			return dstM.WriteStateForMigration(s, true)
    64  		}
    65  	}
    66  
    67  	// Managers to not support full-fidelity migration, so migration will not
    68  	// preserve serial/lineage.
    69  	s := src.State()
    70  	return dst.WriteState(s)
    71  }
    72  
    73  // Import loads the given state snapshot into the given manager, preserving
    74  // its metadata (serial and lineage) if the target manager supports metadata.
    75  //
    76  // A state manager must implement the optional interface Migrator to get
    77  // access to the full metadata.
    78  //
    79  // Unless "force" is true, Import will check first that the metadata given
    80  // in the file matches the current snapshot metadata for the manager, if the
    81  // manager supports metadata. Some managers do not support forcing, so a
    82  // write with an unsuitable lineage or serial may still be rejected even if
    83  // "force" is set. "force" has no effect for managers that do not support
    84  // snapshot metadata.
    85  //
    86  // For state managers that also implement Persistent, it is the caller's
    87  // responsibility to persist the newly-written state after a successful result,
    88  // just as with calls to Writer.WriteState.
    89  //
    90  // This function doesn't do any locking of its own, so if the state manager
    91  // also implements Locker the caller should hold a lock on it for the
    92  // duration of this call.
    93  func Import(f *statefile.File, mgr Transient, force bool) error {
    94  	if mgrM, ok := mgr.(Migrator); ok {
    95  		return mgrM.WriteStateForMigration(f, force)
    96  	}
    97  
    98  	// For managers that don't implement Migrator, this is just a normal write
    99  	// of the state contained in the given file.
   100  	return mgr.WriteState(f.State)
   101  }
   102  
   103  // Export retrieves the latest state snapshot from the given manager, including
   104  // its metadata (serial and lineage) where possible.
   105  //
   106  // A state manager must also implement either Migrator or PersistentMeta
   107  // for the metadata to be included. Otherwise, the relevant fields will have
   108  // zero value in the returned object.
   109  //
   110  // For state managers that also implement Persistent, it is the caller's
   111  // responsibility to refresh from persistent storage first if needed.
   112  //
   113  // This function doesn't do any locking of its own, so if the state manager
   114  // also implements Locker the caller should hold a lock on it for the
   115  // duration of this call.
   116  func Export(mgr Reader) *statefile.File {
   117  	switch mgrT := mgr.(type) {
   118  	case Migrator:
   119  		return mgrT.StateForMigration()
   120  	case PersistentMeta:
   121  		s := mgr.State()
   122  		meta := mgrT.StateSnapshotMeta()
   123  		return statefile.New(s, meta.Lineage, meta.Serial)
   124  	default:
   125  		s := mgr.State()
   126  		return statefile.New(s, "", 0)
   127  	}
   128  }
   129  
   130  // SnapshotMetaRel describes a relationship between two SnapshotMeta values,
   131  // returned from the SnapshotMeta.Compare method where the "first" value
   132  // is the receiver of that method and the "second" is the given argument.
   133  type SnapshotMetaRel rune
   134  
   135  //go:generate go run golang.org/x/tools/cmd/stringer -type=SnapshotMetaRel
   136  
   137  const (
   138  	// SnapshotOlder indicates that two snapshots have a common lineage and
   139  	// that the first has a lower serial value.
   140  	SnapshotOlder SnapshotMetaRel = '<'
   141  
   142  	// SnapshotNewer indicates that two snapshots have a common lineage and
   143  	// that the first has a higher serial value.
   144  	SnapshotNewer SnapshotMetaRel = '>'
   145  
   146  	// SnapshotEqual indicates that two snapshots have a common lineage and
   147  	// the same serial value.
   148  	SnapshotEqual SnapshotMetaRel = '='
   149  
   150  	// SnapshotUnrelated indicates that two snapshots have different lineage
   151  	// and thus cannot be meaningfully compared.
   152  	SnapshotUnrelated SnapshotMetaRel = '!'
   153  
   154  	// SnapshotLegacy indicates that one or both of the snapshots
   155  	// does not have a lineage at all, and thus no comparison is possible.
   156  	SnapshotLegacy SnapshotMetaRel = '?'
   157  )
   158  
   159  // Compare determines the relationship, if any, between the given existing
   160  // SnapshotMeta and the potential "new" SnapshotMeta that is the receiver.
   161  func (m SnapshotMeta) Compare(existing SnapshotMeta) SnapshotMetaRel {
   162  	switch {
   163  	case m.Lineage == "" || existing.Lineage == "":
   164  		return SnapshotLegacy
   165  	case m.Lineage != existing.Lineage:
   166  		return SnapshotUnrelated
   167  	case m.Serial > existing.Serial:
   168  		return SnapshotNewer
   169  	case m.Serial < existing.Serial:
   170  		return SnapshotOlder
   171  	default:
   172  		// both serials are equal, by elimination
   173  		return SnapshotEqual
   174  	}
   175  }
   176  
   177  // CheckValidImport returns nil if the "new" snapshot can be imported as a
   178  // successor of the "existing" snapshot without forcing.
   179  //
   180  // If not, an error is returned describing why.
   181  func CheckValidImport(newFile, existingFile *statefile.File) error {
   182  	if existingFile == nil || existingFile.State.Empty() {
   183  		// It's always okay to overwrite an empty state, regardless of
   184  		// its lineage/serial.
   185  		return nil
   186  	}
   187  	new := SnapshotMeta{
   188  		Lineage: newFile.Lineage,
   189  		Serial:  newFile.Serial,
   190  	}
   191  	existing := SnapshotMeta{
   192  		Lineage: existingFile.Lineage,
   193  		Serial:  existingFile.Serial,
   194  	}
   195  	rel := new.Compare(existing)
   196  	switch rel {
   197  	case SnapshotNewer:
   198  		return nil // a newer snapshot is fine
   199  	case SnapshotLegacy:
   200  		return nil // anything goes for a legacy state
   201  	case SnapshotUnrelated:
   202  		return fmt.Errorf("cannot import state with lineage %q over unrelated state with lineage %q", new.Lineage, existing.Lineage)
   203  	case SnapshotEqual:
   204  		if statefile.StatesMarshalEqual(newFile.State, existingFile.State) {
   205  			// If lineage, serial, and state all match then this is fine.
   206  			return nil
   207  		}
   208  		return fmt.Errorf("cannot overwrite existing state with serial %d with a different state that has the same serial", new.Serial)
   209  	case SnapshotOlder:
   210  		return fmt.Errorf("cannot import state with serial %d over newer state with serial %d", new.Serial, existing.Serial)
   211  	default:
   212  		// Should never happen, but we'll check to make sure for safety
   213  		return fmt.Errorf("unsupported state snapshot relationship %s", rel)
   214  	}
   215  }