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