...

Source file src/github.com/ory/x/pkgerx/migration_box.go

Documentation: github.com/ory/x/pkgerx

     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  	// MigrationBox is a wrapper around pkger.Dir and Migrator.
    21  	// This will allow you to run migrations from migrations packed
    22  	// inside of a compiled binary.
    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  // NewMigrationBox from a packr.Dir and a Connection.
    94  //
    95  //	migrations, err := NewMigrationBox(pkger.Dir("/migrations"))
    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