From 7562e9ad5d9ad74d95f3fc6e9f9340b52386fe4e Mon Sep 17 00:00:00 2001 From: Sam Whited Date: Fri, 15 Jun 2018 16:33:47 -0500 Subject: [PATCH] source/vfs: add virtual file system source Fixes #56 --- Makefile | 2 +- cli/build_godoc-vfs.go | 7 ++ source/godoc_vfs/vfs.go | 137 +++++++++++++++++++++++++++ source/godoc_vfs/vfs_example_test.go | 30 ++++++ source/godoc_vfs/vfs_test.go | 38 ++++++++ 5 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 cli/build_godoc-vfs.go create mode 100644 source/godoc_vfs/vfs.go create mode 100644 source/godoc_vfs/vfs_example_test.go create mode 100644 source/godoc_vfs/vfs_test.go diff --git a/Makefile b/Makefile index 841a074..64687cf 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -SOURCE ?= file go_bindata github aws_s3 google_cloud_storage +SOURCE ?= file go_bindata github aws_s3 google_cloud_storage godoc_vfs DATABASE ?= postgres mysql redshift cassandra spanner cockroachdb clickhouse VERSION ?= $(shell git describe --tags 2>/dev/null | cut -c 2-) TEST_FLAGS ?= diff --git a/cli/build_godoc-vfs.go b/cli/build_godoc-vfs.go new file mode 100644 index 0000000..7bab0d7 --- /dev/null +++ b/cli/build_godoc-vfs.go @@ -0,0 +1,7 @@ +// +build godoc_vfs + +package main + +import ( + _ "github.com/golang-migrate/migrate/source/go_vfs" +) diff --git a/source/godoc_vfs/vfs.go b/source/godoc_vfs/vfs.go new file mode 100644 index 0000000..d84d968 --- /dev/null +++ b/source/godoc_vfs/vfs.go @@ -0,0 +1,137 @@ +// Package vfs contains a driver that reads migrations from a virtual file +// system. +// +// Implementations of the filesystem interface that read from zip files and +// maps, as well as the definition of the filesystem interface can be found in +// the golang.org/x/tools/godoc/vfs package. +package godoc_vfs + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" + "path" + + "github.com/golang-migrate/migrate/source" + "golang.org/x/tools/godoc/vfs" +) + +func init() { + source.Register("godoc-vfs", &VFS{}) +} + +// VFS is an implementation of driver that returns migrations from a virtual +// file system. +type VFS struct { + migrations *source.Migrations + fs vfs.FileSystem + path string +} + +// Open implements the source.Driver interface for VFS. +// +// Calling this function panics, instead use the WithInstance function. +// See the package level documentation for an example. +func (b *VFS) Open(url string) (source.Driver, error) { + panic("not implemented") +} + +// WithInstance creates a new driver from a virtual file system. +// If a tree named searchPath exists in the virtual filesystem, WithInstance +// searches for migration files there. +// It defaults to "/". +func WithInstance(fs vfs.FileSystem, searchPath string) (source.Driver, error) { + if searchPath == "" { + searchPath = "/" + } + + bn := &VFS{ + fs: fs, + path: searchPath, + migrations: source.NewMigrations(), + } + + files, err := fs.ReadDir(searchPath) + if err != nil { + return nil, err + } + + for _, fi := range files { + m, err := source.DefaultParse(fi.Name()) + if err != nil { + continue // ignore files that we can't parse + } + + if !bn.migrations.Append(m) { + return nil, fmt.Errorf("unable to parse file %v", fi) + } + } + + return bn, nil +} + +// Close implements the source.Driver interface for VFS. +// It is a no-op and should not be used. +func (b *VFS) Close() error { + return nil +} + +// First returns the first migration verion found in the file system. +// If no version is available os.ErrNotExist is returned. +func (b *VFS) First() (version uint, err error) { + v, ok := b.migrations.First() + if !ok { + return 0, &os.PathError{"first", "://" + b.path, os.ErrNotExist} + } + return v, nil +} + +// Prev returns the previous version available to the driver. +// If no previous version is available os.ErrNotExist is returned. +func (b *VFS) Prev(version uint) (prevVersion uint, err error) { + v, ok := b.migrations.Prev(version) + if !ok { + return 0, &os.PathError{fmt.Sprintf("prev for version %v", version), "://" + b.path, os.ErrNotExist} + } + return v, nil +} + +// Prev returns the next version available to the driver. +// If no previous version is available os.ErrNotExist is returned. +func (b *VFS) Next(version uint) (nextVersion uint, err error) { + v, ok := b.migrations.Next(version) + if !ok { + return 0, &os.PathError{fmt.Sprintf("next for version %v", version), "://" + b.path, os.ErrNotExist} + } + return v, nil +} + +// ReadUp returns the up migration body and an identifier that helps with +// finding this migration in the source. +// If there is no up migration available for this version it returns +// os.ErrNotExist. +func (b *VFS) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := b.migrations.Up(version); ok { + body, err := vfs.ReadFile(b.fs, path.Join(b.path, m.Raw)) + if err != nil { + return nil, "", err + } + return ioutil.NopCloser(bytes.NewReader(body)), m.Identifier, nil + } + return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), "://" + b.path, os.ErrNotExist} +} + +// ReadDown returns the down migration body and an identifier that helps with +// finding this migration in the source. +func (b *VFS) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { + if m, ok := b.migrations.Down(version); ok { + body, err := vfs.ReadFile(b.fs, path.Join(b.path, m.Raw)) + if err != nil { + return nil, "", err + } + return ioutil.NopCloser(bytes.NewReader(body)), m.Identifier, nil + } + return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), "://" + b.path, os.ErrNotExist} +} diff --git a/source/godoc_vfs/vfs_example_test.go b/source/godoc_vfs/vfs_example_test.go new file mode 100644 index 0000000..a4d63fa --- /dev/null +++ b/source/godoc_vfs/vfs_example_test.go @@ -0,0 +1,30 @@ +package godoc_vfs_test + +import ( + "github.com/golang-migrate/migrate" + "github.com/golang-migrate/migrate/source/godoc_vfs" + "golang.org/x/tools/godoc/vfs/mapfs" +) + +func Example_mapfs() { + fs := mapfs.New(map[string]string{ + "1_foobar.up.sql": "1 up", + "1_foobar.down.sql": "1 down", + "3_foobar.up.sql": "3 up", + "4_foobar.up.sql": "4 up", + "4_foobar.down.sql": "4 down", + "5_foobar.down.sql": "5 down", + "7_foobar.up.sql": "7 up", + "7_foobar.down.sql": "7 down", + }) + + d, err := godoc_vfs.WithInstance(fs, "") + if err != nil { + panic("bad migrations found!") + } + m, err := migrate.NewWithSourceInstance("godoc-vfs", d, "database://foobar") + if err != nil { + panic("error creating the migrations") + } + m.Up() +} diff --git a/source/godoc_vfs/vfs_test.go b/source/godoc_vfs/vfs_test.go new file mode 100644 index 0000000..08a86ec --- /dev/null +++ b/source/godoc_vfs/vfs_test.go @@ -0,0 +1,38 @@ +package godoc_vfs_test + +import ( + "testing" + + "github.com/golang-migrate/migrate/source/godoc_vfs" + st "github.com/golang-migrate/migrate/source/testing" + "golang.org/x/tools/godoc/vfs/mapfs" +) + +func TestVFS(t *testing.T) { + fs := mapfs.New(map[string]string{ + "1_foobar.up.sql": "1 up", + "1_foobar.down.sql": "1 down", + "3_foobar.up.sql": "3 up", + "4_foobar.up.sql": "4 up", + "4_foobar.down.sql": "4 down", + "5_foobar.down.sql": "5 down", + "7_foobar.up.sql": "7 up", + "7_foobar.down.sql": "7 down", + }) + + d, err := godoc_vfs.WithInstance(fs, "") + if err != nil { + t.Fatal(err) + } + st.Test(t, d) +} + +func TestOpen(t *testing.T) { + defer func() { + if r := recover(); r == nil { + t.Error("Expected Open to panic") + } + }() + b := &godoc_vfs.VFS{} + b.Open("") +}