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 }