1 package popx
2
3 import (
4 "embed"
5 "io/fs"
6 "sort"
7 "strings"
8
9 "github.com/gobuffalo/pop/v5"
10 "github.com/pkg/errors"
11
12 "github.com/ory/x/logrusx"
13 )
14
15 type (
16
17 MigrationBox struct {
18 *Migrator
19
20 Dir embed.FS
21 l *logrusx.Logger
22 migrationContent MigrationContent
23 }
24 MigrationContent func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error)
25 )
26
27 func WithTemplateValues(v map[string]interface{}) func(*MigrationBox) *MigrationBox {
28 return func(m *MigrationBox) *MigrationBox {
29 m.migrationContent = ParameterizedMigrationContent(v)
30 return m
31 }
32 }
33
34 func WithMigrationContentMiddleware(middleware func(content string, err error) (string, error)) func(*MigrationBox) *MigrationBox {
35 return func(m *MigrationBox) *MigrationBox {
36 prev := m.migrationContent
37 m.migrationContent = func(mf Migration, c *pop.Connection, r []byte, usingTemplate bool) (string, error) {
38 return middleware(prev(mf, c, r, usingTemplate))
39 }
40 return m
41 }
42 }
43
44
45
46
47
48 func NewMigrationBox(dir embed.FS, m *Migrator, opts ...func(*MigrationBox) *MigrationBox) (*MigrationBox, error) {
49 mb := &MigrationBox{
50 Migrator: m,
51 Dir: dir,
52 l: m.l,
53 migrationContent: ParameterizedMigrationContent(nil),
54 }
55
56 for _, o := range opts {
57 mb = o(mb)
58 }
59
60 runner := func(b []byte) func(Migration, *pop.Connection, *pop.Tx) error {
61 return func(mf Migration, c *pop.Connection, tx *pop.Tx) error {
62 content, err := mb.migrationContent(mf, c, b, true)
63 if err != nil {
64 return errors.Wrapf(err, "error processing %s", mf.Path)
65 }
66 if content == "" {
67 m.l.WithField("migration", mf.Path).Warn("Ignoring migration because content is empty.")
68 return nil
69 }
70 if _, err = tx.Exec(content); err != nil {
71 return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content)
72 }
73 return nil
74 }
75 }
76
77 err := mb.findMigrations(runner)
78 if err != nil {
79 return mb, err
80 }
81
82 return mb, nil
83 }
84
85 func (fm *MigrationBox) findMigrations(runner func([]byte) func(mf Migration, c *pop.Connection, tx *pop.Tx) error) error {
86 return fs.WalkDir(fm.Dir, ".", func(p string, info fs.DirEntry, err error) error {
87 if err != nil {
88 return errors.WithStack(err)
89 }
90
91 if info.IsDir() {
92 return nil
93 }
94
95 match, err := pop.ParseMigrationFilename(info.Name())
96 if err != nil {
97 if strings.HasPrefix(err.Error(), "unsupported dialect") {
98 fm.l.Debugf("Ignoring migration file %s because dialect is not supported: %s", info.Name(), err.Error())
99 return nil
100 }
101 return errors.WithStack(err)
102 }
103
104 if match == nil {
105 fm.l.Debugf("Ignoring migration file %s because it does not match the file pattern.", info.Name())
106 return nil
107 }
108
109 content, err := fm.Dir.ReadFile(p)
110 if err != nil {
111 return errors.WithStack(err)
112 }
113
114 mf := Migration{
115 Path: p,
116 Version: match.Version,
117 Name: match.Name,
118 DBType: match.DBType,
119 Direction: match.Direction,
120 Type: match.Type,
121 Runner: runner(content),
122 }
123 fm.Migrations[mf.Direction] = append(fm.Migrations[mf.Direction], mf)
124 mod := sortIdent(fm.Migrations[mf.Direction])
125 if mf.Direction == "down" {
126 mod = sort.Reverse(mod)
127 }
128 sort.Sort(mod)
129 return nil
130 })
131 }
132
View as plain text