github.com/matcornic/migrate@v3.3.2-0.20180717234201-feea45c20506+incompatible/source/github/github.go (about) 1 package github 2 3 import ( 4 "bytes" 5 "context" 6 "fmt" 7 "io" 8 "io/ioutil" 9 nurl "net/url" 10 "os" 11 "path" 12 "strings" 13 14 "github.com/golang-migrate/migrate/source" 15 "github.com/google/go-github/github" 16 ) 17 18 func init() { 19 source.Register("github", &Github{}) 20 } 21 22 var ( 23 ErrNoUserInfo = fmt.Errorf("no username:token provided") 24 ErrNoAccessToken = fmt.Errorf("no access token") 25 ErrInvalidRepo = fmt.Errorf("invalid repo") 26 ErrInvalidGithubClient = fmt.Errorf("expected *github.Client") 27 ErrNoDir = fmt.Errorf("no directory") 28 ) 29 30 type Github struct { 31 client *github.Client 32 url string 33 34 pathOwner string 35 pathRepo string 36 path string 37 options *github.RepositoryContentGetOptions 38 migrations *source.Migrations 39 } 40 41 type Config struct { 42 } 43 44 func (g *Github) Open(url string) (source.Driver, error) { 45 u, err := nurl.Parse(url) 46 if err != nil { 47 return nil, err 48 } 49 50 if u.User == nil { 51 return nil, ErrNoUserInfo 52 } 53 54 password, ok := u.User.Password() 55 if !ok { 56 return nil, ErrNoUserInfo 57 } 58 59 tr := &github.BasicAuthTransport{ 60 Username: u.User.Username(), 61 Password: password, 62 } 63 64 gn := &Github{ 65 client: github.NewClient(tr.Client()), 66 url: url, 67 migrations: source.NewMigrations(), 68 options: &github.RepositoryContentGetOptions{Ref: u.Fragment}, 69 } 70 71 // set owner, repo and path in repo 72 gn.pathOwner = u.Host 73 pe := strings.Split(strings.Trim(u.Path, "/"), "/") 74 if len(pe) < 1 { 75 return nil, ErrInvalidRepo 76 } 77 gn.pathRepo = pe[0] 78 if len(pe) > 1 { 79 gn.path = strings.Join(pe[1:], "/") 80 } 81 82 if err := gn.readDirectory(); err != nil { 83 return nil, err 84 } 85 86 return gn, nil 87 } 88 89 func WithInstance(client *github.Client, config *Config) (source.Driver, error) { 90 gn := &Github{ 91 client: client, 92 migrations: source.NewMigrations(), 93 } 94 if err := gn.readDirectory(); err != nil { 95 return nil, err 96 } 97 return gn, nil 98 } 99 100 func (g *Github) readDirectory() error { 101 fileContent, dirContents, _, err := g.client.Repositories.GetContents(context.Background(), g.pathOwner, g.pathRepo, g.path, g.options) 102 if err != nil { 103 return err 104 } 105 if fileContent != nil { 106 return ErrNoDir 107 } 108 109 for _, fi := range dirContents { 110 m, err := source.DefaultParse(*fi.Name) 111 if err != nil { 112 continue // ignore files that we can't parse 113 } 114 if !g.migrations.Append(m) { 115 return fmt.Errorf("unable to parse file %v", *fi.Name) 116 } 117 } 118 119 return nil 120 } 121 122 func (g *Github) Close() error { 123 return nil 124 } 125 126 func (g *Github) First() (version uint, er error) { 127 if v, ok := g.migrations.First(); !ok { 128 return 0, &os.PathError{"first", g.path, os.ErrNotExist} 129 } else { 130 return v, nil 131 } 132 } 133 134 func (g *Github) Prev(version uint) (prevVersion uint, err error) { 135 if v, ok := g.migrations.Prev(version); !ok { 136 return 0, &os.PathError{fmt.Sprintf("prev for version %v", version), g.path, os.ErrNotExist} 137 } else { 138 return v, nil 139 } 140 } 141 142 func (g *Github) Next(version uint) (nextVersion uint, err error) { 143 if v, ok := g.migrations.Next(version); !ok { 144 return 0, &os.PathError{fmt.Sprintf("next for version %v", version), g.path, os.ErrNotExist} 145 } else { 146 return v, nil 147 } 148 } 149 150 func (g *Github) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { 151 if m, ok := g.migrations.Up(version); ok { 152 file, _, _, err := g.client.Repositories.GetContents(context.Background(), g.pathOwner, g.pathRepo, path.Join(g.path, m.Raw), g.options) 153 if err != nil { 154 return nil, "", err 155 } 156 if file != nil { 157 r, err := file.GetContent() 158 if err != nil { 159 return nil, "", err 160 } 161 return ioutil.NopCloser(bytes.NewReader([]byte(r))), m.Identifier, nil 162 } 163 } 164 return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), g.path, os.ErrNotExist} 165 } 166 167 func (g *Github) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { 168 if m, ok := g.migrations.Down(version); ok { 169 file, _, _, err := g.client.Repositories.GetContents(context.Background(), g.pathOwner, g.pathRepo, path.Join(g.path, m.Raw), g.options) 170 if err != nil { 171 return nil, "", err 172 } 173 if file != nil { 174 r, err := file.GetContent() 175 if err != nil { 176 return nil, "", err 177 } 178 return ioutil.NopCloser(bytes.NewReader([]byte(r))), m.Identifier, nil 179 } 180 } 181 return nil, "", &os.PathError{fmt.Sprintf("read version %v", version), g.path, os.ErrNotExist} 182 }