github.com/dannyzhou2015/migrate/v4@v4.15.2/source/bitbucket/bitbucket.go (about) 1 package bitbucket 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 nurl "net/url" 8 "os" 9 "path" 10 "path/filepath" 11 "strings" 12 13 "github.com/dannyzhou2015/migrate/v4/source" 14 "github.com/ktrysmt/go-bitbucket" 15 ) 16 17 func init() { 18 source.Register("bitbucket", &Bitbucket{}) 19 } 20 21 var ( 22 ErrNoUserInfo = fmt.Errorf("no username:password provided") 23 ErrNoAccessToken = fmt.Errorf("no password/app password") 24 ErrInvalidRepo = fmt.Errorf("invalid repo") 25 ErrInvalidBitbucketClient = fmt.Errorf("expected *bitbucket.Client") 26 ErrNoDir = fmt.Errorf("no directory") 27 ) 28 29 type Bitbucket struct { 30 config *Config 31 client *bitbucket.Client 32 migrations *source.Migrations 33 } 34 35 type Config struct { 36 Owner string 37 Repo string 38 Path string 39 Ref string 40 } 41 42 func (b *Bitbucket) Open(url string) (source.Driver, error) { 43 u, err := nurl.Parse(url) 44 if err != nil { 45 return nil, err 46 } 47 48 if u.User == nil { 49 return nil, ErrNoUserInfo 50 } 51 52 password, ok := u.User.Password() 53 if !ok { 54 return nil, ErrNoAccessToken 55 } 56 57 cl := bitbucket.NewBasicAuth(u.User.Username(), password) 58 59 cfg := &Config{} 60 // set owner, repo and path in repo 61 cfg.Owner = u.Host 62 pe := strings.Split(strings.Trim(u.Path, "/"), "/") 63 if len(pe) < 1 { 64 return nil, ErrInvalidRepo 65 } 66 cfg.Repo = pe[0] 67 if len(pe) > 1 { 68 cfg.Path = strings.Join(pe[1:], "/") 69 } 70 cfg.Ref = u.Fragment 71 72 bi, err := WithInstance(cl, cfg) 73 if err != nil { 74 return nil, err 75 } 76 77 return bi, nil 78 } 79 80 func WithInstance(client *bitbucket.Client, config *Config) (source.Driver, error) { 81 bi := &Bitbucket{ 82 client: client, 83 config: config, 84 migrations: source.NewMigrations(), 85 } 86 87 if err := bi.readDirectory(); err != nil { 88 return nil, err 89 } 90 91 return bi, nil 92 } 93 94 func (b *Bitbucket) readDirectory() error { 95 b.ensureFields() 96 97 fOpt := &bitbucket.RepositoryFilesOptions{ 98 Owner: b.config.Owner, 99 RepoSlug: b.config.Repo, 100 Ref: b.config.Ref, 101 Path: b.config.Path, 102 } 103 104 dirContents, err := b.client.Repositories.Repository.ListFiles(fOpt) 105 106 if err != nil { 107 return err 108 } 109 110 for _, fi := range dirContents { 111 112 m, err := source.DefaultParse(filepath.Base(fi.Path)) 113 if err != nil { 114 continue // ignore files that we can't parse 115 } 116 if !b.migrations.Append(m) { 117 return fmt.Errorf("unable to parse file %v", fi.Path) 118 } 119 } 120 121 return nil 122 } 123 124 func (b *Bitbucket) ensureFields() { 125 if b.config == nil { 126 b.config = &Config{} 127 } 128 } 129 130 func (b *Bitbucket) Close() error { 131 return nil 132 } 133 134 func (b *Bitbucket) First() (version uint, er error) { 135 b.ensureFields() 136 137 if v, ok := b.migrations.First(); !ok { 138 return 0, &os.PathError{Op: "first", Path: b.config.Path, Err: os.ErrNotExist} 139 } else { 140 return v, nil 141 } 142 } 143 144 func (b *Bitbucket) Prev(version uint) (prevVersion uint, err error) { 145 b.ensureFields() 146 147 if v, ok := b.migrations.Prev(version); !ok { 148 return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: b.config.Path, Err: os.ErrNotExist} 149 } else { 150 return v, nil 151 } 152 } 153 154 func (b *Bitbucket) Next(version uint) (nextVersion uint, err error) { 155 b.ensureFields() 156 157 if v, ok := b.migrations.Next(version); !ok { 158 return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: b.config.Path, Err: os.ErrNotExist} 159 } else { 160 return v, nil 161 } 162 } 163 164 func (b *Bitbucket) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) { 165 b.ensureFields() 166 167 if m, ok := b.migrations.Up(version); ok { 168 fBlobOpt := &bitbucket.RepositoryBlobOptions{ 169 Owner: b.config.Owner, 170 RepoSlug: b.config.Repo, 171 Ref: b.config.Ref, 172 Path: path.Join(b.config.Path, m.Raw), 173 } 174 file, err := b.client.Repositories.Repository.GetFileBlob(fBlobOpt) 175 if err != nil { 176 return nil, "", err 177 } 178 if file != nil { 179 r := file.Content 180 return ioutil.NopCloser(strings.NewReader(string(r))), m.Identifier, nil 181 } 182 } 183 return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: b.config.Path, Err: os.ErrNotExist} 184 } 185 186 func (b *Bitbucket) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) { 187 b.ensureFields() 188 189 if m, ok := b.migrations.Down(version); ok { 190 fBlobOpt := &bitbucket.RepositoryBlobOptions{ 191 Owner: b.config.Owner, 192 RepoSlug: b.config.Repo, 193 Ref: b.config.Ref, 194 Path: path.Join(b.config.Path, m.Raw), 195 } 196 file, err := b.client.Repositories.Repository.GetFileBlob(fBlobOpt) 197 198 if err != nil { 199 return nil, "", err 200 } 201 if file != nil { 202 r := file.Content 203 204 return ioutil.NopCloser(strings.NewReader(string(r))), m.Identifier, nil 205 } 206 } 207 return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: b.config.Path, Err: os.ErrNotExist} 208 }