github.com/olitvin/migrate/v4@v4.14.3-0.20210330111251-992b37ee04c8/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/ktrysmt/go-bitbucket"
    14  	"github.com/olitvin/migrate/v4/source"
    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  }