github.com/mendersoftware/go-lib-micro@v0.0.0-20240304135804-e8e39c59b148/mongo/migrate/migrator_simple.go (about) 1 // Copyright 2023 Northern.tech AS 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package migrate 15 16 import ( 17 "context" 18 "fmt" 19 "sort" 20 "strings" 21 22 "github.com/pkg/errors" 23 "go.mongodb.org/mongo-driver/mongo" 24 25 "github.com/mendersoftware/go-lib-micro/log" 26 ) 27 28 var ( 29 ErrNeedsMigration = "db needs migration" 30 ) 31 32 func IsErrNeedsMigration(e error) bool { 33 return strings.HasPrefix(e.Error(), ErrNeedsMigration) 34 } 35 36 // SimpleMigratior applies migrations by comparing `Version` of migrations 37 // passed to Apply() and already applied migrations. Only migrations that are of 38 // version higher than the last applied migration will be run. For example: 39 // 40 // already applied migrations: 1.0.0, 1.0.1, 1.0.2 41 // migrations in Apply(): 1.0.1, 1.0.3, 1.1.0 42 // migrations that will be applied: 1.0.3, 1.1.0 43 type SimpleMigrator struct { 44 Client *mongo.Client 45 Db string 46 Automigrate bool 47 } 48 49 // Apply will apply migrations, provided that Automigrate is on. After each successful migration a new migration 50 // record will be added to DB with the version of migration that was just 51 // applied. If a migration fails, Apply() returns an error and does not add a 52 // migration record (so last migration that is recorded is N-1). 53 // 54 // Apply() will log some messages when running. Logger will be extracted from 55 // context using go-lib-micro/log.LoggerContextKey as key. 56 // If Automigrate is off, the migrator will just check if the DB is up-to-date, and return with ErrNeedsMigration otherwise. 57 // Check for it with IsErrNeedsMigration. 58 func (m *SimpleMigrator) Apply(ctx context.Context, target Version, migrations []Migration) error { 59 l := log.FromContext(ctx).F(log.Ctx{"db": m.Db}) 60 61 sort.Slice(migrations, func(i int, j int) bool { 62 return VersionIsLess(migrations[i].Version(), migrations[j].Version()) 63 }) 64 65 applied, err := GetMigrationInfo(ctx, m.Client, m.Db) 66 if err != nil { 67 return errors.Wrap(err, "failed to list applied migrations") 68 } 69 70 // starts at 0.0.0 71 last := Version{} 72 73 if len(applied) != 0 { 74 // sort applied migrations wrt. version 75 sort.Slice(applied, func(i int, j int) bool { 76 return VersionIsLess(applied[i].Version, applied[j].Version) 77 }) 78 // last version from already applied migrations 79 last = applied[len(applied)-1].Version 80 } 81 82 // if Automigrate is disabled - just check 83 // if the last applied migration is lower than the target one 84 if !m.Automigrate { 85 if VersionIsLess(last, target) { 86 return fmt.Errorf(ErrNeedsMigration+": %s has version %s, needs version %s", m.Db, last.String(), target.String()) 87 } else { 88 return nil 89 } 90 } 91 92 // try to apply migrations 93 for _, migration := range migrations { 94 mv := migration.Version() 95 if VersionIsLess(target, mv) { 96 l.Warnf("migration to version %s skipped, target version %s is lower", 97 mv, target) 98 } else if VersionIsLess(last, mv) { 99 // log, migration applied 100 l.Infof("applying migration from version %s to %s", 101 last, mv) 102 103 // apply migration 104 if err := migration.Up(last); err != nil { 105 l.Errorf("migration from %s to %s failed: %s", 106 last, mv, err) 107 108 // migration from last to migration.Version() failed: err 109 return errors.Wrapf(err, 110 "failed to apply migration from %s to %s", 111 last, mv) 112 } 113 114 if err := UpdateMigrationInfo(ctx, mv, m.Client, m.Db); err != nil { 115 116 return errors.Wrapf(err, 117 "failed to record migration from %s to %s", 118 last, mv) 119 120 } 121 last = mv 122 } else { 123 // log migration already applied 124 l.Infof("migration to version %s skipped", mv) 125 } 126 } 127 128 // ideally, when all migrations have completed, DB should be in `target` version 129 if VersionIsLess(last, target) { 130 l.Warnf("last migration to version %s did not produce target version %s", 131 last, target) 132 // record DB version anyways 133 if err := UpdateMigrationInfo(ctx, target, m.Client, m.Db); err != nil { 134 return errors.Wrapf(err, 135 "failed to record migration from %s to %s", 136 last, target) 137 } 138 } else { 139 l.Infof("DB migrated to version %s", target) 140 } 141 142 return nil 143 }