1 package pkgerx
2
3 import (
4 "bytes"
5 "io"
6 "io/ioutil"
7 "os"
8 "strings"
9 "text/template"
10
11 "github.com/gobuffalo/fizz"
12 "github.com/gobuffalo/pop/v5"
13 "github.com/markbates/pkger"
14 "github.com/pkg/errors"
15
16 "github.com/ory/x/logrusx"
17 )
18
19 type (
20
21
22
23 MigrationBox struct {
24 pop.Migrator
25
26 Dir pkger.Dir
27 l *logrusx.Logger
28 migrationContent MigrationContent
29 }
30 MigrationContent func(mf pop.Migration, c *pop.Connection, r io.Reader, usingTemplate bool) (string, error)
31 )
32
33 func templatingMigrationContent(params map[string]interface{}) func(pop.Migration, *pop.Connection, io.Reader, bool) (string, error) {
34 return func(mf pop.Migration, c *pop.Connection, r io.Reader, usingTemplate bool) (string, error) {
35 b, err := ioutil.ReadAll(r)
36 if err != nil {
37 return "", nil
38 }
39
40 content := ""
41 if usingTemplate {
42 t := template.New("migration")
43 t.Funcs(SQLTemplateFuncs)
44 t, err := t.Parse(string(b))
45 if err != nil {
46 return "", err
47 }
48
49 var bb bytes.Buffer
50 err = t.Execute(&bb, struct {
51 DialectDetails *pop.ConnectionDetails
52 Parameters map[string]interface{}
53 }{
54 DialectDetails: c.Dialect.Details(),
55 Parameters: params,
56 })
57 if err != nil {
58 return "", errors.Wrapf(err, "could not execute migration template %s", mf.Path)
59 }
60 content = bb.String()
61 } else {
62 content = string(b)
63 }
64
65 if mf.Type == "fizz" {
66 content, err = fizz.AString(content, c.Dialect.FizzTranslator())
67 if err != nil {
68 return "", errors.Wrapf(err, "could not fizz the migration %s", mf.Path)
69 }
70 }
71
72 return content, nil
73 }
74 }
75
76 func WithTemplateValues(v map[string]interface{}) func(*MigrationBox) *MigrationBox {
77 return func(m *MigrationBox) *MigrationBox {
78 m.migrationContent = templatingMigrationContent(v)
79 return m
80 }
81 }
82
83 func WithMigrationContentMiddleware(middleware func(content string, err error) (string, error)) func(*MigrationBox) *MigrationBox {
84 return func(m *MigrationBox) *MigrationBox {
85 prev := m.migrationContent
86 m.migrationContent = func(mf pop.Migration, c *pop.Connection, r io.Reader, usingTemplate bool) (string, error) {
87 return middleware(prev(mf, c, r, usingTemplate))
88 }
89 return m
90 }
91 }
92
93
94
95
96
97 func NewMigrationBox(dir pkger.Dir, c *pop.Connection, l *logrusx.Logger, opts ...func(*MigrationBox) *MigrationBox) (*MigrationBox, error) {
98 mb := &MigrationBox{
99 Migrator: pop.NewMigrator(c),
100 Dir: dir,
101 l: l,
102 migrationContent: pop.MigrationContent,
103 }
104
105 for _, o := range opts {
106 mb = o(mb)
107 }
108
109 runner := func(f io.Reader) func(mf pop.Migration, tx *pop.Connection) error {
110 return func(mf pop.Migration, tx *pop.Connection) error {
111 content, err := mb.migrationContent(mf, tx, f, true)
112 if err != nil {
113 return errors.Wrapf(err, "error processing %s", mf.Path)
114 }
115 if content == "" {
116 return nil
117 }
118 err = tx.RawQuery(content).Exec()
119 if err != nil {
120 return errors.Wrapf(err, "error executing %s, sql: %s", mf.Path, content)
121 }
122 return nil
123 }
124 }
125
126 err := mb.findMigrations(runner)
127 if err != nil {
128 return mb, err
129 }
130
131 return mb, nil
132 }
133
134 func (fm *MigrationBox) findMigrations(runner func(f io.Reader) func(mf pop.Migration, tx *pop.Connection) error) error {
135 return pkger.Walk(string(fm.Dir), func(p string, info os.FileInfo, err error) error {
136 if err != nil {
137 return errors.WithStack(err)
138 }
139
140 match, err := pop.ParseMigrationFilename(info.Name())
141 if err != nil {
142 if strings.HasPrefix(err.Error(), "unsupported dialect") {
143 fm.l.Debugf("Ignoring migration file %s because dialect is not supported: %s", info.Name(), err.Error())
144 return nil
145 }
146 return errors.WithStack(err)
147 }
148
149 if match == nil {
150 fm.l.Debugf("Ignoring migration file %s because it does not match the file pattern.", info.Name())
151 return nil
152 }
153
154 file, err := pkger.Open(p)
155 if err != nil {
156 return errors.WithStack(err)
157 }
158 defer file.Close()
159
160 content, err := ioutil.ReadAll(file)
161 if err != nil {
162 return errors.WithStack(err)
163 }
164
165 mf := pop.Migration{
166 Path: p,
167 Version: match.Version,
168 Name: match.Name,
169 DBType: match.DBType,
170 Direction: match.Direction,
171 Type: match.Type,
172 Runner: runner(bytes.NewReader(content)),
173 }
174 fm.Migrations[mf.Direction] = append(fm.Migrations[mf.Direction], mf)
175 return nil
176 })
177 }
178
View as plain text