migrate/source/github/github.go

228 lines
4.7 KiB
Go

package github
import (
"context"
"fmt"
"io"
"io/ioutil"
nurl "net/url"
"os"
"path"
"strings"
)
import (
"github.com/golang-migrate/migrate/v4/source"
"github.com/google/go-github/github"
)
func init() {
source.Register("github", &Github{})
}
var (
ErrNoUserInfo = fmt.Errorf("no username:token provided")
ErrNoAccessToken = fmt.Errorf("no access token")
ErrInvalidRepo = fmt.Errorf("invalid repo")
ErrInvalidGithubClient = fmt.Errorf("expected *github.Client")
ErrNoDir = fmt.Errorf("no directory")
)
type Github struct {
config *Config
client *github.Client
options *github.RepositoryContentGetOptions
migrations *source.Migrations
}
type Config struct {
Owner string
Repo string
Path string
Ref string
}
func (g *Github) Open(url string) (source.Driver, error) {
u, err := nurl.Parse(url)
if err != nil {
return nil, err
}
if u.User == nil {
return nil, ErrNoUserInfo
}
password, ok := u.User.Password()
if !ok {
return nil, ErrNoUserInfo
}
tr := &github.BasicAuthTransport{
Username: u.User.Username(),
Password: password,
}
gn := &Github{
client: github.NewClient(tr.Client()),
migrations: source.NewMigrations(),
options: &github.RepositoryContentGetOptions{Ref: u.Fragment},
}
gn.ensureFields()
// set owner, repo and path in repo
gn.config.Owner = u.Host
pe := strings.Split(strings.Trim(u.Path, "/"), "/")
if len(pe) < 1 {
return nil, ErrInvalidRepo
}
gn.config.Repo = pe[0]
if len(pe) > 1 {
gn.config.Path = strings.Join(pe[1:], "/")
}
if err := gn.readDirectory(); err != nil {
return nil, err
}
return gn, nil
}
func WithInstance(client *github.Client, config *Config) (source.Driver, error) {
gn := &Github{
client: client,
config: config,
migrations: source.NewMigrations(),
options: &github.RepositoryContentGetOptions{Ref: config.Ref},
}
if err := gn.readDirectory(); err != nil {
return nil, err
}
return gn, nil
}
func (g *Github) readDirectory() error {
g.ensureFields()
fileContent, dirContents, _, err := g.client.Repositories.GetContents(
context.Background(),
g.config.Owner,
g.config.Repo,
g.config.Path,
g.options,
)
if err != nil {
return err
}
if fileContent != nil {
return ErrNoDir
}
for _, fi := range dirContents {
m, err := source.DefaultParse(*fi.Name)
if err != nil {
continue // ignore files that we can't parse
}
if !g.migrations.Append(m) {
return fmt.Errorf("unable to parse file %v", *fi.Name)
}
}
return nil
}
func (g *Github) ensureFields() {
if g.config == nil {
g.config = &Config{}
}
}
func (g *Github) Close() error {
return nil
}
func (g *Github) First() (version uint, er error) {
g.ensureFields()
if v, ok := g.migrations.First(); !ok {
return 0, &os.PathError{Op: "first", Path: g.config.Path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Github) Prev(version uint) (prevVersion uint, err error) {
g.ensureFields()
if v, ok := g.migrations.Prev(version); !ok {
return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: g.config.Path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Github) Next(version uint) (nextVersion uint, err error) {
g.ensureFields()
if v, ok := g.migrations.Next(version); !ok {
return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: g.config.Path, Err: os.ErrNotExist}
} else {
return v, nil
}
}
func (g *Github) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
g.ensureFields()
if m, ok := g.migrations.Up(version); ok {
file, _, _, err := g.client.Repositories.GetContents(
context.Background(),
g.config.Owner,
g.config.Repo,
path.Join(g.config.Path, m.Raw),
g.options,
)
if err != nil {
return nil, "", err
}
if file != nil {
r, err := file.GetContent()
if err != nil {
return nil, "", err
}
return ioutil.NopCloser(strings.NewReader(r)), m.Identifier, nil
}
}
return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.config.Path, Err: os.ErrNotExist}
}
func (g *Github) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
g.ensureFields()
if m, ok := g.migrations.Down(version); ok {
file, _, _, err := g.client.Repositories.GetContents(
context.Background(),
g.config.Owner,
g.config.Repo,
path.Join(g.config.Path, m.Raw),
g.options,
)
if err != nil {
return nil, "", err
}
if file != nil {
r, err := file.GetContent()
if err != nil {
return nil, "", err
}
return ioutil.NopCloser(strings.NewReader(r)), m.Identifier, nil
}
}
return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.config.Path, Err: os.ErrNotExist}
}