forked from golang-migrate/migrate
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add pkger source driver support (golang-migrate#377)
* Add pkger source driver support As go-bindata has been abandoned [1] there are open requests, golang-migrate#116, for alternative sources with similar functionality. The Buffalo project [2] created packr and recently pkger [3] was announced [4] with the intention to supersede packr. This change adds support for using pkger as a source. The implementation relies on httpfs.PartialDriver for pretty much all functionality. [1] jteeuwen/go-bindata#5 [2] https://gobuffalo.io/ [3] https://github.com/markbates/pkger [4] https://blog.gobuffalo.io/introducing-pkger-static-file-embedding-in-go-1ce76dc79c65 * pkger: rename Instance to Pkger * pkger: make WithInstance accept *Pkger * pkger: refactor and add access to global pkging.Pkger instance * pkger: fix typo and cleanup debug logging
- Loading branch information
1 parent
423c2d3
commit f5a22be
Showing
4 changed files
with
287 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
package pkger | ||
|
||
import ( | ||
"fmt" | ||
"net/http" | ||
stdurl "net/url" | ||
|
||
"github.com/golang-migrate/migrate/v4/source" | ||
"github.com/golang-migrate/migrate/v4/source/httpfs" | ||
"github.com/markbates/pkger" | ||
"github.com/markbates/pkger/pkging" | ||
) | ||
|
||
func init() { | ||
source.Register("pkger", &Pkger{}) | ||
} | ||
|
||
// Pkger is a source.Driver that reads migrations from instances of | ||
// pkging.Pkger. | ||
type Pkger struct { | ||
httpfs.PartialDriver | ||
} | ||
|
||
// Open implements source.Driver. The path component of url will be used as the | ||
// relative location of migrations. The returned driver will use the package | ||
// scoped pkger.Open to access migrations. The relative root and any | ||
// migrations must be added to the global pkger.Pkger instance by calling | ||
// pkger.Apply. Refer to Pkger documentation for more information. | ||
func (p *Pkger) Open(url string) (source.Driver, error) { | ||
u, err := stdurl.Parse(url) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// wrap pkger to implement http.FileSystem. | ||
fs := fsFunc(func(name string) (http.File, error) { | ||
f, err := pkger.Open(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return f.(http.File), nil | ||
}) | ||
|
||
if err := p.Init(fs, u.Path); err != nil { | ||
return nil, fmt.Errorf("failed to init driver with relative path %q: %w", u.Path, err) | ||
} | ||
|
||
return p, nil | ||
} | ||
|
||
// WithInstance returns a source.Driver that is backed by an instance of | ||
// pkging.Pkger. The relative location of migrations is indicated by path. The | ||
// path must exist on the pkging.Pkger instance for the driver to initialize | ||
// successfully. | ||
func WithInstance(instance pkging.Pkger, path string) (source.Driver, error) { | ||
if instance == nil { | ||
return nil, fmt.Errorf("expected instance of pkging.Pkger") | ||
} | ||
|
||
// wrap pkger to implement http.FileSystem. | ||
fs := fsFunc(func(name string) (http.File, error) { | ||
f, err := instance.Open(name) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return f.(http.File), nil | ||
}) | ||
|
||
var p Pkger | ||
|
||
if err := p.Init(fs, path); err != nil { | ||
return nil, fmt.Errorf("failed to init driver with relative path %q: %w", path, err) | ||
} | ||
|
||
return &p, nil | ||
} | ||
|
||
type fsFunc func(name string) (http.File, error) | ||
|
||
// Open implements http.FileSystem. | ||
func (f fsFunc) Open(name string) (http.File, error) { | ||
return f(name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
package pkger | ||
|
||
import ( | ||
"errors" | ||
"os" | ||
"testing" | ||
|
||
"github.com/gobuffalo/here" | ||
st "github.com/golang-migrate/migrate/v4/source/testing" | ||
"github.com/markbates/pkger" | ||
"github.com/markbates/pkger/pkging" | ||
"github.com/markbates/pkger/pkging/mem" | ||
) | ||
|
||
func Test(t *testing.T) { | ||
t.Run("WithInstance", func(t *testing.T) { | ||
i := testInstance(t) | ||
|
||
createPkgerFile(t, i, "/1_foobar.up.sql") | ||
createPkgerFile(t, i, "/1_foobar.down.sql") | ||
createPkgerFile(t, i, "/3_foobar.up.sql") | ||
createPkgerFile(t, i, "/4_foobar.up.sql") | ||
createPkgerFile(t, i, "/4_foobar.down.sql") | ||
createPkgerFile(t, i, "/5_foobar.down.sql") | ||
createPkgerFile(t, i, "/7_foobar.up.sql") | ||
createPkgerFile(t, i, "/7_foobar.down.sql") | ||
|
||
d, err := WithInstance(i, "/") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
st.Test(t, d) | ||
}) | ||
|
||
t.Run("Open", func(t *testing.T) { | ||
i := testInstance(t) | ||
|
||
createPkgerFile(t, i, "/1_foobar.up.sql") | ||
createPkgerFile(t, i, "/1_foobar.down.sql") | ||
createPkgerFile(t, i, "/3_foobar.up.sql") | ||
createPkgerFile(t, i, "/4_foobar.up.sql") | ||
createPkgerFile(t, i, "/4_foobar.down.sql") | ||
createPkgerFile(t, i, "/5_foobar.down.sql") | ||
createPkgerFile(t, i, "/7_foobar.up.sql") | ||
createPkgerFile(t, i, "/7_foobar.down.sql") | ||
|
||
registerPackageLevelInstance(t, i) | ||
|
||
d, err := (&Pkger{}).Open("pkger:///") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
st.Test(t, d) | ||
}) | ||
|
||
} | ||
|
||
func TestWithInstance(t *testing.T) { | ||
t.Run("Subdir", func(t *testing.T) { | ||
i := testInstance(t) | ||
|
||
// Make sure the relative root exists so that httpfs.PartialDriver can | ||
// initialize. | ||
createPkgerSubdir(t, i, "/subdir") | ||
|
||
_, err := WithInstance(i, "/subdir") | ||
if err != nil { | ||
t.Fatal("") | ||
} | ||
}) | ||
|
||
t.Run("NilInstance", func(t *testing.T) { | ||
_, err := WithInstance(nil, "") | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
t.Run("FailInit", func(t *testing.T) { | ||
i := testInstance(t) | ||
|
||
_, err := WithInstance(i, "/fail") | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
t.Run("FailWithoutMigrations", func(t *testing.T) { | ||
i := testInstance(t) | ||
|
||
createPkgerSubdir(t, i, "/") | ||
|
||
d, err := WithInstance(i, "/") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
if _, err := d.First(); !errors.Is(err, os.ErrNotExist) { | ||
t.Fatal(err) | ||
} | ||
|
||
}) | ||
} | ||
|
||
func TestOpen(t *testing.T) { | ||
|
||
t.Run("InvalidURL", func(t *testing.T) { | ||
_, err := (&Pkger{}).Open(":///") | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
t.Run("Root", func(t *testing.T) { | ||
_, err := (&Pkger{}).Open("pkger:///") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
t.Run("FailInit", func(t *testing.T) { | ||
_, err := (&Pkger{}).Open("pkger:///subdir") | ||
if err == nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
|
||
i := testInstance(t) | ||
createPkgerSubdir(t, i, "/subdir") | ||
|
||
// Note that this registers the instance globally so anything run after | ||
// this will have access to everything container in the registered | ||
// instance. | ||
registerPackageLevelInstance(t, i) | ||
|
||
t.Run("Subdir", func(t *testing.T) { | ||
_, err := (&Pkger{}).Open("pkger:///subdir") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
}) | ||
} | ||
|
||
func TestClose(t *testing.T) { | ||
d, err := (&Pkger{}).Open("pkger:///") | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
if err := d.Close(); err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func registerPackageLevelInstance(t *testing.T, pkg pkging.Pkger) { | ||
if err := pkger.Apply(pkg, nil); err != nil { | ||
t.Fatalf("failed to register pkger instance: %v\n", err) | ||
} | ||
} | ||
|
||
func testInstance(t *testing.T) pkging.Pkger { | ||
pkg, err := inMemoryPkger() | ||
if err != nil { | ||
t.Fatalf("failed to create an pkging.Pkger instance: %v\n", err) | ||
} | ||
|
||
return pkg | ||
} | ||
|
||
func createPkgerSubdir(t *testing.T, pkg pkging.Pkger, subdir string) { | ||
if err := pkg.MkdirAll(subdir, os.ModePerm); err != nil { | ||
t.Fatalf("failed to create pkger subdir %q: %v\n", subdir, err) | ||
} | ||
} | ||
|
||
func createPkgerFile(t *testing.T, pkg pkging.Pkger, name string) { | ||
_, err := pkg.Create(name) | ||
if err != nil { | ||
t.Fatalf("failed to create pkger file %q: %v\n", name, err) | ||
} | ||
} | ||
|
||
func inMemoryPkger() (*mem.Pkger, error) { | ||
info, err := here.New().Current() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
pkg, err := mem.New(info) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return pkg, nil | ||
} |