github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/popx/migration_box.go (about) 1 package popx 2 3 import ( 4 "io" 5 "io/fs" 6 "sort" 7 "strings" 8 9 "github.com/gobuffalo/pop/v6" 10 "github.com/pkg/errors" 11 12 "github.com/ory/x/logrusx" 13 ) 14 15 type ( 16 // MigrationBox is a embed migration box. 17 MigrationBox struct { 18 *Migrator 19 20 Dir fs.FS 21 l *logrusx.Logger 22 migrationContent MigrationContent 23 goMigrations Migrations 24 } 25 MigrationContent func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) 26 GoMigration func(c *pop.Tx) error 27 ) 28 29 func WithTemplateValues(v map[string]interface{}) func(*MigrationBox) *MigrationBox { 30 return func(m *MigrationBox) *MigrationBox { 31 m.migrationContent = ParameterizedMigrationContent(v) 32 return m 33 } 34 } 35 36 func WithMigrationContentMiddleware(middleware func(content string, err error) (string, error)) func(*MigrationBox) *MigrationBox { 37 return func(m *MigrationBox) *MigrationBox { 38 prev := m.migrationContent 39 m.migrationContent = func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) { 40 return middleware(prev(mf, c, r, usingTemplate)) 41 } 42 return m 43 } 44 } 45 46 // WithGoMigrations adds migrations that have a custom migration runner. 47 // TEST THEM THOROUGHLY! 48 // It will be very hard to fix a buggy migration. 49 func WithGoMigrations(migrations Migrations) func(*MigrationBox) *MigrationBox { 50 return func(m *MigrationBox) *MigrationBox { 51 m.goMigrations = migrations 52 return m 53 } 54 } 55 56 // NewMigrationBox creates a new migration box. 57 func NewMigrationBox(dir fs.FS, m *Migrator, opts ...func(*MigrationBox) *MigrationBox) (*MigrationBox, error) { 58 mb := &MigrationBox{ 59 Migrator: m, 60 Dir: dir, 61 l: m.l, 62 migrationContent: ParameterizedMigrationContent(nil), 63 } 64 65 for _, o := range opts { 66 mb = o(mb) 67 } 68 69 runner := func(b []byte) func(Migration, *pop.Connection, *pop.Tx) error { 70 return func(mf Migration, c *pop.Connection, tx *pop.Tx) error { 71 content, err := mb.migrationContent(mf, c, b, true) 72 if err != nil { 73 return errors.Wrapf(err, "error processing %s", mf.Path) 74 } 75 if content == "" { 76 m.l.WithField("migration", mf.Path).Trace("This is usually ok - ignoring migration because content is empty. This is ok!") 77 return nil 78 } 79 if _, err = tx.Exec(content); err != nil { 80 return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content) 81 } 82 return nil 83 } 84 } 85 86 err := mb.findMigrations(runner) 87 if err != nil { 88 return mb, err 89 } 90 91 for _, migration := range mb.goMigrations { 92 mb.Migrations[migration.Direction] = append(mb.Migrations[migration.Direction], migration) 93 } 94 95 return mb, nil 96 } 97 98 func (fm *MigrationBox) findMigrations(runner func([]byte) func(mf Migration, c *pop.Connection, tx *pop.Tx) error) error { 99 return fs.WalkDir(fm.Dir, ".", func(p string, info fs.DirEntry, err error) error { 100 if err != nil { 101 return errors.WithStack(err) 102 } 103 104 if info.IsDir() { 105 return nil 106 } 107 108 match, err := pop.ParseMigrationFilename(info.Name()) 109 if err != nil { 110 if strings.HasPrefix(err.Error(), "unsupported dialect") { 111 fm.l.Tracef("This is usually ok - ignoring migration file %s because dialect is not supported: %s", info.Name(), err.Error()) 112 return nil 113 } 114 return errors.WithStack(err) 115 } 116 117 if match == nil { 118 fm.l.Tracef("This is usually ok - ignoring migration file %s because it does not match the file pattern.", info.Name()) 119 return nil 120 } 121 122 f, err := fm.Dir.Open(p) 123 if err != nil { 124 return errors.WithStack(err) 125 } 126 content, err := io.ReadAll(f) 127 if err != nil { 128 return errors.WithStack(err) 129 } 130 131 mf := Migration{ 132 Path: p, 133 Version: match.Version, 134 Name: match.Name, 135 DBType: match.DBType, 136 Direction: match.Direction, 137 Type: match.Type, 138 Runner: runner(content), 139 } 140 fm.Migrations[mf.Direction] = append(fm.Migrations[mf.Direction], mf) 141 mod := sortIdent(fm.Migrations[mf.Direction]) 142 if mf.Direction == "down" { 143 mod = sort.Reverse(mod) 144 } 145 sort.Sort(mod) 146 return nil 147 }) 148 }