package github import ( "bytes" "fmt" "io" "io/ioutil" nurl "net/url" "os" "path" "strings" "github.com/google/go-github/github" "github.com/mattes/migrate/source" ) 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 { client *github.Client url string pathOwner string pathRepo string path string migrations *source.Migrations } type Config struct { } 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()), url: url, migrations: source.NewMigrations(), } // set owner, repo and path in repo gn.pathOwner = u.Host pe := strings.Split(strings.Trim(u.Path, "/"), "/") if len(pe) < 1 { return nil, ErrInvalidRepo } gn.pathRepo = pe[0] if len(pe) > 1 { gn.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, migrations: source.NewMigrations(), } if err := gn.readDirectory(); err != nil { return nil, err } return gn, nil } func (g *Github) readDirectory() error { fileContent, dirContents, _, err := g.client.Repositories.GetContents(g.pathOwner, g.pathRepo, g.path, &github.RepositoryContentGetOptions{}) 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) Close() error { return nil } func (g *Github) First() (version uint, er error) { if v, ok := g.migrations.First(); !ok { return 0, &os.PathError{"first", g.path, os.ErrNotExist} } else { return v, nil } } func (g *Github) Prev(version uint) (prevVersion uint, err error) { if v, ok := g.migrations.Prev(version); !ok { return 0, &os.PathError{fmt.Sprintf("prev for version %v", version), g.path, os.ErrNotExist} } else { return v, nil } } func (g *Github) Next(version uint) (nextVersion uint, err error) { if v, ok := g.migrations.Next(version); !ok { return 0, &os.PathError{fmt.Sprintf("next for version %v", version), g.path, os.ErrNotExist} } else { return v, nil } } func (g *Github) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := g.migrations.Up(version); ok { file, _, _, err := g.client.Repositories.GetContents(g.pathOwner, g.pathRepo, path.Join(g.path, m.Raw), &github.RepositoryContentGetOptions{}) if err != nil { return nil, "", err } if file != nil { r, err := file.Decode() if err != nil { return nil, "", err } return ioutil.NopCloser(bytes.NewReader(r)), m.Identifier, nil } } return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), g.path, os.ErrNotExist} } func (g *Github) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { if m, ok := g.migrations.Down(version); ok { file, _, _, err := g.client.Repositories.GetContents(g.pathOwner, g.pathRepo, path.Join(g.path, m.Raw), &github.RepositoryContentGetOptions{}) if err != nil { return nil, "", err } if file != nil { r, err := file.Decode() if err != nil { return nil, "", err } return ioutil.NopCloser(bytes.NewReader(r)), m.Identifier, nil } } return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), g.path, os.ErrNotExist} }