mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-02 12:17:39 -06:00
f40800b3a4
This is part of a general effort to move all of Terraform's non-library package surface under internal in order to reinforce that these are for internal use within Terraform only. If you were previously importing packages under this prefix into an external codebase, you could pin to an earlier release tag as an interim solution until you've make a plan to achieve the same functionality some other way.
213 lines
7.9 KiB
Go
213 lines
7.9 KiB
Go
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)
|
|
}
|
|
}
|