github.com/ghosta3/migrate/v4@v4.15.10/source/github/github.go (about) 1 package github 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "net/http" 9 nurl "net/url" 10 "os" 11 "path" 12 "strings" 13 14 "github.com/golang-migrate/migrate/v4/source" 15 "github.com/google/go-github/v35/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 config *Config 32 client *github.Client 33 options *github.RepositoryContentGetOptions 34 migrations *source.Migrations 35 } 36 37 type Config struct { 38 Owner string 39 Repo string 40 Path string 41 Ref string 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 // client defaults to http.DefaultClient 51 var client *http.Client 52 if u.User != nil { 53 password, ok := u.User.Password() 54 if !ok { 55 return nil, ErrNoUserInfo 56 } 57 58 tr := &github.BasicAuthTransport{ 59 Username: u.User.Username(), 60 Password: password, 61 } 62 client = tr.Client() 63 } 64 65 gn := &Github{ 66 client: github.NewClient(client), 67 migrations: source.NewMigrations(), 68 options: &github.RepositoryContentGetOptions{Ref: u.Fragment}, 69 } 70 71 gn.ensureFields() 72 73 // set owner, repo and path in repo 74 gn.config.Owner = u.Host 75 pe := strings.Split(strings.Trim(u.Path, "/"), "/") 76 if len(pe) < 1 { 77 return nil, ErrInvalidRepo 78 } 79 gn.config.Repo = pe[0] 80 if len(pe) > 1 { 81 gn.config.Path = strings.Join(pe[1:], "/") 82 } 83 84 if err := gn.readDirectory(); err != nil { 85 return nil, err 86 } 87 88 return gn, nil 89 } 90 91 func WithInstance(client *github.Client, config *Config) (source.Driver, error) { 92 gn := &Github{ 93 client: client, 94 config: config, 95 migrations: source.NewMigrations(), 96 options: &github.RepositoryContentGetOptions{Ref: config.Ref}, 97 } 98 99 if err := gn.readDirectory(); err != nil { 100 return nil, err 101 } 102 103 return gn, nil 104 } 105 106 func (g *Github) readDirectory() error { 107 g.ensureFields() 108 109 fileContent, dirContents, _, err := g.client.Repositories.GetContents( 110 context.Background(), 111 g.config.Owner, 112 g.config.Repo, 113 g.config.Path, 114 g.options, 115 ) 116 117 if err != nil { 118 return err 119 } 120 if fileContent != nil { 121 return ErrNoDir 122 } 123 124 for _, fi := range dirContents { 125 m, err := source.DefaultParse(*fi.Name) 126 if err != nil { 127 continue // ignore files that we can't parse 128 } 129 if !g.migrations.Append(m) { 130 return fmt.Errorf("unable to parse file %v", *fi.Name) 131 } 132 } 133 134 return nil 135 } 136 137 func (g *Github) ensureFields() { 138 if g.config == nil { 139 g.config = &Config{} 140 } 141 } 142 143 func (g *Github) Close() error { 144 return nil 145 } 146 147 func (g *Github) First() (version uint, err error) { 148 g.ensureFields() 149 150 if v, ok := g.migrations.First(); !ok { 151 return 0, &os.PathError{Op: "first", Path: g.config.Path, Err: os.ErrNotExist} 152 } else { 153 return v, nil 154 } 155 } 156 157 func (g *Github) Prev(version uint) (prevVersion uint, err error) { 158 g.ensureFields() 159 160 if v, ok := g.migrations.Prev(version); !ok { 161 return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: g.config.Path, Err: os.ErrNotExist} 162 } else { 163 return v, nil 164 } 165 } 166 167 func (g *Github) Next(version uint) (nextVersion uint, err error) { 168 g.ensureFields() 169 170 if v, ok := g.migrations.Next(version); !ok { 171 return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: g.config.Path, Err: os.ErrNotExist} 172 } else { 173 return v, nil 174 } 175 } 176 177 func (g *Github) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { 178 g.ensureFields() 179 180 if m, ok := g.migrations.Up(version); ok { 181 file, _, _, err := g.client.Repositories.GetContents( 182 context.Background(), 183 g.config.Owner, 184 g.config.Repo, 185 path.Join(g.config.Path, m.Raw), 186 g.options, 187 ) 188 189 if err != nil { 190 return nil, "", err 191 } 192 if file != nil { 193 r, err := file.GetContent() 194 if err != nil { 195 return nil, "", err 196 } 197 return ioutil.NopCloser(strings.NewReader(r)), m.Identifier, nil 198 } 199 } 200 return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.config.Path, Err: os.ErrNotExist} 201 } 202 203 func (g *Github) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { 204 g.ensureFields() 205 206 if m, ok := g.migrations.Down(version); ok { 207 file, _, _, err := g.client.Repositories.GetContents( 208 context.Background(), 209 g.config.Owner, 210 g.config.Repo, 211 path.Join(g.config.Path, m.Raw), 212 g.options, 213 ) 214 215 if err != nil { 216 return nil, "", err 217 } 218 if file != nil { 219 r, err := file.GetContent() 220 if err != nil { 221 return nil, "", err 222 } 223 return ioutil.NopCloser(strings.NewReader(r)), m.Identifier, nil 224 } 225 } 226 return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.config.Path, Err: os.ErrNotExist} 227 }