github.com/opentofu/opentofu@v1.7.1/internal/states/statemgr/migrate.go (about)

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