122 lines
3.1 KiB
Go
122 lines
3.1 KiB
Go
package migrations
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/golang-migrate/migrate/v4/source"
|
|
"github.com/golang-migrate/migrate/v4/source/httpfs"
|
|
)
|
|
|
|
// migrationDriver satisfies the remaining need of the Driver interface, since
|
|
// the package uses PartialDriver under the hood
|
|
type migrationDriver struct {
|
|
dialect string
|
|
}
|
|
|
|
// Open returns the given "file"
|
|
func (m *migrationDriver) Open(name string) (http.File, error) {
|
|
var ff *fakeFile
|
|
switch m.dialect {
|
|
case "postgres":
|
|
ff = postgresMigrations[name]
|
|
}
|
|
if ff == nil {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
ff.name = strings.TrimPrefix(name, "migrations/")
|
|
ff.reader = bytes.NewReader(ff.bytes)
|
|
ff.dialect = m.dialect
|
|
return ff, nil
|
|
}
|
|
|
|
// NewMigrationSource creates a source.Driver using httpfs with the given dialect
|
|
func NewMigrationSource(dialect string) (source.Driver, error) {
|
|
switch dialect {
|
|
case "postgres":
|
|
default:
|
|
return nil, fmt.Errorf("unknown migrations dialect %s", dialect)
|
|
}
|
|
return httpfs.New(&migrationDriver{dialect}, "migrations")
|
|
}
|
|
|
|
// fakeFile is used to satisfy the http.File interface
|
|
type fakeFile struct {
|
|
name string
|
|
bytes []byte
|
|
reader *bytes.Reader
|
|
dialect string
|
|
}
|
|
|
|
func (f *fakeFile) Read(p []byte) (n int, err error) {
|
|
return f.reader.Read(p)
|
|
}
|
|
|
|
func (f *fakeFile) Seek(offset int64, whence int) (int64, error) {
|
|
return f.reader.Seek(offset, whence)
|
|
}
|
|
|
|
func (f *fakeFile) Close() error { return nil }
|
|
|
|
// Readdir returns os.FileInfo values, in sorted order, and eliding the
|
|
// migrations "dir"
|
|
func (f *fakeFile) Readdir(count int) ([]os.FileInfo, error) {
|
|
// Get the right map
|
|
var migrationsMap map[string]*fakeFile
|
|
switch f.dialect {
|
|
case "postgres":
|
|
migrationsMap = postgresMigrations
|
|
default:
|
|
return nil, fmt.Errorf("unknown database dialect %s", f.dialect)
|
|
}
|
|
|
|
// Sort the keys. May not be necessary but feels nice.
|
|
keys := make([]string, 0, len(migrationsMap))
|
|
for k := range migrationsMap {
|
|
keys = append(keys, k)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
// Create the slice of fileinfo objects to return
|
|
ret := make([]os.FileInfo, 0, len(migrationsMap))
|
|
for _, v := range keys {
|
|
// We need "migrations" in the map for the initial Open call but we
|
|
// should not return it as part of the "directory"'s "files".
|
|
if v == "migrations" {
|
|
continue
|
|
}
|
|
stat, err := migrationsMap[v].Stat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, stat)
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
// Stat returns a new fakeFileInfo object with the necessary bits
|
|
func (f *fakeFile) Stat() (os.FileInfo, error) {
|
|
return &fakeFileInfo{
|
|
name: f.name,
|
|
size: int64(len(f.bytes)),
|
|
}, nil
|
|
}
|
|
|
|
// fakeFileInfo satisfies os.FileInfo but represents our fake "files"
|
|
type fakeFileInfo struct {
|
|
name string
|
|
size int64
|
|
}
|
|
|
|
func (f *fakeFileInfo) Name() string { return f.name }
|
|
func (f *fakeFileInfo) Size() int64 { return f.size }
|
|
func (f *fakeFileInfo) Mode() os.FileMode { return os.ModePerm }
|
|
func (f *fakeFileInfo) ModTime() time.Time { return time.Now() }
|
|
func (f *fakeFileInfo) IsDir() bool { return false }
|
|
func (f *fakeFileInfo) Sys() interface{} { return nil }
|