You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/db/migrations/driver.go

128 lines
3.2 KiB

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) {
return newFakeFile(m.dialect, name)
}
// 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 newFakeFile(dialect string, name string) (*fakeFile, error) {
var ff *fakeFile
switch 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 = dialect
return ff, nil
}
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 i, 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)
if count > 0 && count == i {
break
}
}
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 }