Skip to content

Commit 2e7358e

Browse files
authored
Merge pull request #560 from dhui/re-add-iofs-src-driver
Re-add the io/fs source driver
2 parents c4d82aa + eac6684 commit 2e7358e

18 files changed

+318
-25
lines changed

Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM golang:1.15-alpine3.12 AS builder
1+
FROM golang:1.16-alpine3.13 AS builder
22
ARG VERSION
33

44
RUN apk add --no-cache git gcc musl-dev make
@@ -15,7 +15,7 @@ COPY . ./
1515

1616
RUN make build-docker
1717

18-
FROM alpine:3.12
18+
FROM alpine:3.13
1919

2020
RUN apk add --no-cache ca-certificates
2121

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,4 @@ require (
6464
modernc.org/zappy v1.0.0 // indirect
6565
)
6666

67-
go 1.13
67+
go 1.16

source/file/file.go

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,22 @@
11
package file
22

33
import (
4-
"net/http"
54
nurl "net/url"
65
"os"
76
"path/filepath"
87

98
"github.com/golang-migrate/migrate/v4/source"
10-
"github.com/golang-migrate/migrate/v4/source/httpfs"
119
)
1210

1311
func init() {
1412
source.Register("file", &File{})
1513
}
1614

17-
type File struct {
18-
httpfs.PartialDriver
19-
url string
20-
path string
21-
}
22-
23-
func (f *File) Open(url string) (source.Driver, error) {
15+
func parseURL(url string) (string, error) {
2416
u, err := nurl.Parse(url)
2517
if err != nil {
26-
return nil, err
18+
return "", err
2719
}
28-
2920
// concat host and path to restore full path
3021
// host might be `.`
3122
p := u.Opaque
@@ -37,25 +28,17 @@ func (f *File) Open(url string) (source.Driver, error) {
3728
// default to current directory if no path
3829
wd, err := os.Getwd()
3930
if err != nil {
40-
return nil, err
31+
return "", err
4132
}
4233
p = wd
4334

4435
} else if p[0:1] == "." || p[0:1] != "/" {
4536
// make path absolute if relative
4637
abs, err := filepath.Abs(p)
4738
if err != nil {
48-
return nil, err
39+
return "", err
4940
}
5041
p = abs
5142
}
52-
53-
nf := &File{
54-
url: url,
55-
path: p,
56-
}
57-
if err := nf.Init(http.Dir(p), ""); err != nil {
58-
return nil, err
59-
}
60-
return nf, nil
43+
return p, nil
6144
}

source/file/file_go115.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// +build !go1.16
2+
3+
package file
4+
5+
import (
6+
"net/http"
7+
8+
"github.com/golang-migrate/migrate/v4/source"
9+
"github.com/golang-migrate/migrate/v4/source/httpfs"
10+
)
11+
12+
type File struct {
13+
httpfs.PartialDriver
14+
url string
15+
path string
16+
}
17+
18+
func (f *File) Open(url string) (source.Driver, error) {
19+
p, err := parseURL(url)
20+
if err != nil {
21+
return nil, err
22+
}
23+
24+
nf := &File{
25+
url: url,
26+
path: p,
27+
}
28+
if err := nf.Init(http.Dir(p), ""); err != nil {
29+
return nil, err
30+
}
31+
return nf, nil
32+
}

source/file/file_go116.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// +build go1.16
2+
3+
package file
4+
5+
import (
6+
"os"
7+
8+
"github.com/golang-migrate/migrate/v4/source"
9+
"github.com/golang-migrate/migrate/v4/source/iofs"
10+
)
11+
12+
type File struct {
13+
iofs.PartialDriver
14+
url string
15+
path string
16+
}
17+
18+
func (f *File) Open(url string) (source.Driver, error) {
19+
p, err := parseURL(url)
20+
if err != nil {
21+
return nil, err
22+
}
23+
nf := &File{
24+
url: url,
25+
path: p,
26+
}
27+
if err := nf.Init(os.DirFS(p), "."); err != nil {
28+
return nil, err
29+
}
30+
return nf, nil
31+
}

source/iofs/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# iofs
2+
3+
https://pkg.go.dev/github.com/golang-migrate/migrate/v4/source/iofs

source/iofs/doc.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
/*
2+
Package iofs provides the Go 1.16+ io/fs#FS driver.
3+
4+
It can accept various file systems (like embed.FS, archive/zip#Reader) implementing io/fs#FS.
5+
6+
This driver cannot be used with Go versions 1.15 and below.
7+
8+
Also, Opening with a URL scheme is not supported.
9+
*/
10+
package iofs

source/iofs/example_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// +build go1.16
2+
3+
package iofs_test
4+
5+
import (
6+
"embed"
7+
"log"
8+
9+
"github.com/golang-migrate/migrate/v4"
10+
_ "github.com/golang-migrate/migrate/v4/database/postgres"
11+
"github.com/golang-migrate/migrate/v4/source/iofs"
12+
)
13+
14+
//go:embed testdata/migrations/*.sql
15+
var fs embed.FS
16+
17+
func Example() {
18+
d, err := iofs.New(fs, "testdata/migrations")
19+
if err != nil {
20+
log.Fatal(err)
21+
}
22+
m, err := migrate.NewWithSourceInstance("iofs", d, "postgres://postgres@localhost/postgres?sslmode=disable")
23+
if err != nil {
24+
log.Fatal(err)
25+
}
26+
err = m.Up()
27+
if err != nil {
28+
// ...
29+
}
30+
// ...
31+
}

source/iofs/iofs.go

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// +build go1.16
2+
3+
package iofs
4+
5+
import (
6+
"errors"
7+
"fmt"
8+
"io"
9+
"io/fs"
10+
"path"
11+
"strconv"
12+
13+
"github.com/golang-migrate/migrate/v4/source"
14+
)
15+
16+
type driver struct {
17+
PartialDriver
18+
}
19+
20+
// New returns a new Driver from io/fs#FS and a relative path.
21+
func New(fsys fs.FS, path string) (source.Driver, error) {
22+
var i driver
23+
if err := i.Init(fsys, path); err != nil {
24+
return nil, fmt.Errorf("failed to init driver with path %s: %w", path, err)
25+
}
26+
return &i, nil
27+
}
28+
29+
// Open is part of source.Driver interface implementation.
30+
// Open cannot be called on the iofs passthrough driver.
31+
func (d *driver) Open(url string) (source.Driver, error) {
32+
return nil, errors.New("Open() cannot be called on the iofs passthrough driver")
33+
}
34+
35+
// PartialDriver is a helper service for creating new source drivers working with
36+
// io/fs.FS instances. It implements all source.Driver interface methods
37+
// except for Open(). New driver could embed this struct and add missing Open()
38+
// method.
39+
//
40+
// To prepare PartialDriver for use Init() function.
41+
type PartialDriver struct {
42+
migrations *source.Migrations
43+
fsys fs.FS
44+
path string
45+
}
46+
47+
// Init prepares not initialized IoFS instance to read migrations from a
48+
// io/fs#FS instance and a relative path.
49+
func (d *PartialDriver) Init(fsys fs.FS, path string) error {
50+
entries, err := fs.ReadDir(fsys, path)
51+
if err != nil {
52+
return err
53+
}
54+
55+
ms := source.NewMigrations()
56+
for _, e := range entries {
57+
if e.IsDir() {
58+
continue
59+
}
60+
m, err := source.DefaultParse(e.Name())
61+
if err != nil {
62+
continue
63+
}
64+
file, err := e.Info()
65+
if err != nil {
66+
return err
67+
}
68+
if !ms.Append(m) {
69+
return source.ErrDuplicateMigration{
70+
Migration: *m,
71+
FileInfo: file,
72+
}
73+
}
74+
}
75+
76+
d.fsys = fsys
77+
d.path = path
78+
d.migrations = ms
79+
return nil
80+
}
81+
82+
// Close is part of source.Driver interface implementation.
83+
// Closes the file system if possible.
84+
func (d *PartialDriver) Close() error {
85+
c, ok := d.fsys.(io.Closer)
86+
if !ok {
87+
return nil
88+
}
89+
return c.Close()
90+
}
91+
92+
// First is part of source.Driver interface implementation.
93+
func (d *PartialDriver) First() (version uint, err error) {
94+
if version, ok := d.migrations.First(); ok {
95+
return version, nil
96+
}
97+
return 0, &fs.PathError{
98+
Op: "first",
99+
Path: d.path,
100+
Err: fs.ErrNotExist,
101+
}
102+
}
103+
104+
// Prev is part of source.Driver interface implementation.
105+
func (d *PartialDriver) Prev(version uint) (prevVersion uint, err error) {
106+
if version, ok := d.migrations.Prev(version); ok {
107+
return version, nil
108+
}
109+
return 0, &fs.PathError{
110+
Op: "prev for version " + strconv.FormatUint(uint64(version), 10),
111+
Path: d.path,
112+
Err: fs.ErrNotExist,
113+
}
114+
}
115+
116+
// Next is part of source.Driver interface implementation.
117+
func (d *PartialDriver) Next(version uint) (nextVersion uint, err error) {
118+
if version, ok := d.migrations.Next(version); ok {
119+
return version, nil
120+
}
121+
return 0, &fs.PathError{
122+
Op: "next for version " + strconv.FormatUint(uint64(version), 10),
123+
Path: d.path,
124+
Err: fs.ErrNotExist,
125+
}
126+
}
127+
128+
// ReadUp is part of source.Driver interface implementation.
129+
func (d *PartialDriver) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
130+
if m, ok := d.migrations.Up(version); ok {
131+
body, err := d.open(path.Join(d.path, m.Raw))
132+
if err != nil {
133+
return nil, "", err
134+
}
135+
return body, m.Identifier, nil
136+
}
137+
return nil, "", &fs.PathError{
138+
Op: "read up for version " + strconv.FormatUint(uint64(version), 10),
139+
Path: d.path,
140+
Err: fs.ErrNotExist,
141+
}
142+
}
143+
144+
// ReadDown is part of source.Driver interface implementation.
145+
func (d *PartialDriver) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
146+
if m, ok := d.migrations.Down(version); ok {
147+
body, err := d.open(path.Join(d.path, m.Raw))
148+
if err != nil {
149+
return nil, "", err
150+
}
151+
return body, m.Identifier, nil
152+
}
153+
return nil, "", &fs.PathError{
154+
Op: "read down for version " + strconv.FormatUint(uint64(version), 10),
155+
Path: d.path,
156+
Err: fs.ErrNotExist,
157+
}
158+
}
159+
160+
func (d *PartialDriver) open(path string) (fs.File, error) {
161+
f, err := d.fsys.Open(path)
162+
if err == nil {
163+
return f, nil
164+
}
165+
// Some non-standard file systems may return errors that don't include the path, that
166+
// makes debugging harder.
167+
if !errors.As(err, new(*fs.PathError)) {
168+
err = &fs.PathError{
169+
Op: "open",
170+
Path: path,
171+
Err: err,
172+
}
173+
}
174+
return nil, err
175+
}

source/iofs/iofs_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// +build go1.16
2+
3+
package iofs_test
4+
5+
import (
6+
"testing"
7+
8+
"github.com/golang-migrate/migrate/v4/source/iofs"
9+
st "github.com/golang-migrate/migrate/v4/source/testing"
10+
)
11+
12+
func Test(t *testing.T) {
13+
// reuse the embed.FS set in example_test.go
14+
d, err := iofs.New(fs, "testdata/migrations")
15+
if err != nil {
16+
t.Fatal(err)
17+
}
18+
19+
st.Test(t, d)
20+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1 down
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1 up
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
3 up

0 commit comments

Comments
 (0)