github.com/dendy1/migrate/v4@v4.15.2/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/dendy1/migrate/v4/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  }