// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 package statemgr import ( "fmt" "github.com/opentofu/opentofu/internal/states/statefile" ) // Migrator is an optional interface implemented by state managers that // are capable of direct migration of state snapshots with their associated // metadata unchanged. // // This interface is used when available by function Migrate. See that // function for more information on how it is used. type Migrator interface { PersistentMeta // StateForMigration returns a full statefile representing the latest // snapshot (as would be returned by Reader.State) and the associated // snapshot metadata (as would be returned by // PersistentMeta.StateSnapshotMeta). // // Just as with Reader.State, this must not fail. StateForMigration() *statefile.File // WriteStateForMigration accepts a full statefile including associated // snapshot metadata, and atomically updates the stored file (as with // Writer.WriteState) and the metadata. // // If "force" is not set, the manager must call CheckValidImport with // the given file and the current file and complete the update only if // that function returns nil. If force is set this may override such // checks, but some backends do not support forcing and so will act // as if force is always false. WriteStateForMigration(f *statefile.File, force bool) error } // Migrate writes the latest transient state snapshot from src into dest, // preserving snapshot metadata (serial and lineage) where possible. // // If both managers implement the optional interface Migrator then it will // be used to copy the snapshot and its associated metadata. Otherwise, // the normal Reader and Writer interfaces will be used instead. // // If the destination manager refuses the new state or fails to write it then // its error is returned directly. // // For state managers that also implement Persistent, it is the caller's // responsibility to persist the newly-written state after a successful result, // just as with calls to Writer.WriteState. // // This function doesn't do any locking of its own, so if the state managers // also implement Locker the caller should hold a lock on both managers // for the duration of this call. func Migrate(dst, src Transient) error { if dstM, ok := dst.(Migrator); ok { if srcM, ok := src.(Migrator); ok { // Full-fidelity migration, them. s := srcM.StateForMigration() return dstM.WriteStateForMigration(s, true) } } // Managers to not support full-fidelity migration, so migration will not // preserve serial/lineage. s := src.State() return dst.WriteState(s) } // Import loads the given state snapshot into the given manager, preserving // its metadata (serial and lineage) if the target manager supports metadata. // // A state manager must implement the optional interface Migrator to get // access to the full metadata. // // Unless "force" is true, Import will check first that the metadata given // in the file matches the current snapshot metadata for the manager, if the // manager supports metadata. Some managers do not support forcing, so a // write with an unsuitable lineage or serial may still be rejected even if // "force" is set. "force" has no effect for managers that do not support // snapshot metadata. // // For state managers that also implement Persistent, it is the caller's // responsibility to persist the newly-written state after a successful result, // just as with calls to Writer.WriteState. // // This function doesn't do any locking of its own, so if the state manager // also implements Locker the caller should hold a lock on it for the // duration of this call. func Import(f *statefile.File, mgr Transient, force bool) error { if mgrM, ok := mgr.(Migrator); ok { return mgrM.WriteStateForMigration(f, force) } // For managers that don't implement Migrator, this is just a normal write // of the state contained in the given file. return mgr.WriteState(f.State) } // Export retrieves the latest state snapshot from the given manager, including // its metadata (serial and lineage) where possible. // // A state manager must also implement either Migrator or PersistentMeta // for the metadata to be included. Otherwise, the relevant fields will have // zero value in the returned object. // // For state managers that also implement Persistent, it is the caller's // responsibility to refresh from persistent storage first if needed. // // This function doesn't do any locking of its own, so if the state manager // also implements Locker the caller should hold a lock on it for the // duration of this call. func Export(mgr Reader) *statefile.File { switch mgrT := mgr.(type) { case Migrator: return mgrT.StateForMigration() case PersistentMeta: s := mgr.State() meta := mgrT.StateSnapshotMeta() return statefile.New(s, meta.Lineage, meta.Serial) default: s := mgr.State() return statefile.New(s, "", 0) } } // SnapshotMetaRel describes a relationship between two SnapshotMeta values, // returned from the SnapshotMeta.Compare method where the "first" value // is the receiver of that method and the "second" is the given argument. type SnapshotMetaRel rune //go:generate go run golang.org/x/tools/cmd/stringer -type=SnapshotMetaRel const ( // SnapshotOlder indicates that two snapshots have a common lineage and // that the first has a lower serial value. SnapshotOlder SnapshotMetaRel = '<' // SnapshotNewer indicates that two snapshots have a common lineage and // that the first has a higher serial value. SnapshotNewer SnapshotMetaRel = '>' // SnapshotEqual indicates that two snapshots have a common lineage and // the same serial value. SnapshotEqual SnapshotMetaRel = '=' // SnapshotUnrelated indicates that two snapshots have different lineage // and thus cannot be meaningfully compared. SnapshotUnrelated SnapshotMetaRel = '!' // SnapshotLegacy indicates that one or both of the snapshots // does not have a lineage at all, and thus no comparison is possible. SnapshotLegacy SnapshotMetaRel = '?' ) // Compare determines the relationship, if any, between the given existing // SnapshotMeta and the potential "new" SnapshotMeta that is the receiver. func (m SnapshotMeta) Compare(existing SnapshotMeta) SnapshotMetaRel { switch { case m.Lineage == "" || existing.Lineage == "": return SnapshotLegacy case m.Lineage != existing.Lineage: return SnapshotUnrelated case m.Serial > existing.Serial: return SnapshotNewer case m.Serial < existing.Serial: return SnapshotOlder default: // both serials are equal, by elimination return SnapshotEqual } } // CheckValidImport returns nil if the "new" snapshot can be imported as a // successor of the "existing" snapshot without forcing. // // If not, an error is returned describing why. func CheckValidImport(newFile, existingFile *statefile.File) error { if existingFile == nil || existingFile.State.Empty() { // It's always okay to overwrite an empty state, regardless of // its lineage/serial. return nil } new := SnapshotMeta{ Lineage: newFile.Lineage, Serial: newFile.Serial, } existing := SnapshotMeta{ Lineage: existingFile.Lineage, Serial: existingFile.Serial, } rel := new.Compare(existing) switch rel { case SnapshotNewer: return nil // a newer snapshot is fine case SnapshotLegacy: return nil // anything goes for a legacy state case SnapshotUnrelated: return fmt.Errorf("cannot import state with lineage %q over unrelated state with lineage %q", new.Lineage, existing.Lineage) case SnapshotEqual: if statefile.StatesMarshalEqual(newFile.State, existingFile.State) { // If lineage, serial, and state all match then this is fine. return nil } return fmt.Errorf("cannot overwrite existing state with serial %d with a different state that has the same serial", new.Serial) case SnapshotOlder: return fmt.Errorf("cannot import state with serial %d over newer state with serial %d", new.Serial, existing.Serial) default: // Should never happen, but we'll check to make sure for safety return fmt.Errorf("unsupported state snapshot relationship %s", rel) } }