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