| package statemgr |
| |
| import ( |
| "fmt" |
| |
| "github.com/hashicorp/terraform/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) |
| } |
| } |