github.com/dendy1/migrate/v4@v4.15.2/source/gitlab/gitlab.go (about)

     1  package gitlab
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"io"
     7  	"net/http"
     8  	nurl "net/url"
     9  	"os"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/dendy1/migrate/v4/source"
    14  	"github.com/xanzy/go-gitlab"
    15  )
    16  
    17  func init() {
    18  	source.Register("gitlab", &Gitlab{})
    19  }
    20  
    21  const DefaultMaxItemsPerPage = 100
    22  
    23  var (
    24  	ErrNoUserInfo       = fmt.Errorf("no username:token provided")
    25  	ErrNoAccessToken    = fmt.Errorf("no access token")
    26  	ErrInvalidHost      = fmt.Errorf("invalid host")
    27  	ErrInvalidProjectID = fmt.Errorf("invalid project id")
    28  	ErrInvalidResponse  = fmt.Errorf("invalid response")
    29  )
    30  
    31  type Gitlab struct {
    32  	client *gitlab.Client
    33  	url    string
    34  
    35  	projectID   string
    36  	path        string
    37  	listOptions *gitlab.ListTreeOptions
    38  	getOptions  *gitlab.GetFileOptions
    39  	migrations  *source.Migrations
    40  }
    41  
    42  type Config struct {
    43  }
    44  
    45  func (g *Gitlab) Open(url string) (source.Driver, error) {
    46  	u, err := nurl.Parse(url)
    47  	if err != nil {
    48  		return nil, err
    49  	}
    50  
    51  	if u.User == nil {
    52  		return nil, ErrNoUserInfo
    53  	}
    54  
    55  	password, ok := u.User.Password()
    56  	if !ok {
    57  		return nil, ErrNoAccessToken
    58  	}
    59  
    60  	gn := &Gitlab{
    61  		client:     gitlab.NewClient(nil, password),
    62  		url:        url,
    63  		migrations: source.NewMigrations(),
    64  	}
    65  
    66  	if u.Host != "" {
    67  		uri := nurl.URL{
    68  			Scheme: "https",
    69  			Host:   u.Host,
    70  		}
    71  
    72  		err = gn.client.SetBaseURL(uri.String())
    73  		if err != nil {
    74  			return nil, ErrInvalidHost
    75  		}
    76  	}
    77  
    78  	pe := strings.Split(strings.Trim(u.Path, "/"), "/")
    79  	if len(pe) < 1 {
    80  		return nil, ErrInvalidProjectID
    81  	}
    82  	gn.projectID = pe[0]
    83  	if len(pe) > 1 {
    84  		gn.path = strings.Join(pe[1:], "/")
    85  	}
    86  
    87  	gn.listOptions = &gitlab.ListTreeOptions{
    88  		Path: &gn.path,
    89  		Ref:  &u.Fragment,
    90  		ListOptions: gitlab.ListOptions{
    91  			PerPage: DefaultMaxItemsPerPage,
    92  		},
    93  	}
    94  
    95  	gn.getOptions = &gitlab.GetFileOptions{
    96  		Ref: &u.Fragment,
    97  	}
    98  
    99  	if err := gn.readDirectory(); err != nil {
   100  		return nil, err
   101  	}
   102  
   103  	return gn, nil
   104  }
   105  
   106  func WithInstance(client *gitlab.Client, config *Config) (source.Driver, error) {
   107  	gn := &Gitlab{
   108  		client:     client,
   109  		migrations: source.NewMigrations(),
   110  	}
   111  	if err := gn.readDirectory(); err != nil {
   112  		return nil, err
   113  	}
   114  	return gn, nil
   115  }
   116  
   117  func (g *Gitlab) readDirectory() error {
   118  	var nodes []*gitlab.TreeNode
   119  	for {
   120  		n, response, err := g.client.Repositories.ListTree(g.projectID, g.listOptions)
   121  		if err != nil {
   122  			return err
   123  		}
   124  
   125  		if response.StatusCode != http.StatusOK {
   126  			return ErrInvalidResponse
   127  		}
   128  
   129  		nodes = append(nodes, n...)
   130  		if response.CurrentPage >= response.TotalPages {
   131  			break
   132  		}
   133  		g.listOptions.ListOptions.Page = response.NextPage
   134  	}
   135  
   136  	for i := range nodes {
   137  		m, err := g.nodeToMigration(nodes[i])
   138  		if err != nil {
   139  			continue
   140  		}
   141  
   142  		if !g.migrations.Append(m) {
   143  			return fmt.Errorf("unable to parse file %v", nodes[i].Name)
   144  		}
   145  	}
   146  
   147  	return nil
   148  }
   149  
   150  func (g *Gitlab) nodeToMigration(node *gitlab.TreeNode) (*source.Migration, error) {
   151  	m := source.Regex.FindStringSubmatch(node.Name)
   152  	if len(m) == 5 {
   153  		versionUint64, err := strconv.ParseUint(m[1], 10, 64)
   154  		if err != nil {
   155  			return nil, err
   156  		}
   157  		return &source.Migration{
   158  			Version:    uint(versionUint64),
   159  			Identifier: m[2],
   160  			Direction:  source.Direction(m[3]),
   161  			Raw:        g.path + "/" + node.Name,
   162  		}, nil
   163  	}
   164  	return nil, source.ErrParse
   165  }
   166  
   167  func (g *Gitlab) Close() error {
   168  	return nil
   169  }
   170  
   171  func (g *Gitlab) First() (version uint, er error) {
   172  	if v, ok := g.migrations.First(); !ok {
   173  		return 0, &os.PathError{Op: "first", Path: g.path, Err: os.ErrNotExist}
   174  	} else {
   175  		return v, nil
   176  	}
   177  }
   178  
   179  func (g *Gitlab) Prev(version uint) (prevVersion uint, err error) {
   180  	if v, ok := g.migrations.Prev(version); !ok {
   181  		return 0, &os.PathError{Op: fmt.Sprintf("prev for version %v", version), Path: g.path, Err: os.ErrNotExist}
   182  	} else {
   183  		return v, nil
   184  	}
   185  }
   186  
   187  func (g *Gitlab) Next(version uint) (nextVersion uint, err error) {
   188  	if v, ok := g.migrations.Next(version); !ok {
   189  		return 0, &os.PathError{Op: fmt.Sprintf("next for version %v", version), Path: g.path, Err: os.ErrNotExist}
   190  	} else {
   191  		return v, nil
   192  	}
   193  }
   194  
   195  func (g *Gitlab) ReadUp(version uint) (r io.ReadCloser, identifier string, err error) {
   196  	if m, ok := g.migrations.Up(version); ok {
   197  		f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
   198  		if err != nil {
   199  			return nil, "", err
   200  		}
   201  
   202  		if response.StatusCode != http.StatusOK {
   203  			return nil, "", ErrInvalidResponse
   204  		}
   205  
   206  		content, err := base64.StdEncoding.DecodeString(f.Content)
   207  		if err != nil {
   208  			return nil, "", err
   209  		}
   210  
   211  		return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
   212  	}
   213  
   214  	return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
   215  }
   216  
   217  func (g *Gitlab) ReadDown(version uint) (r io.ReadCloser, identifier string, err error) {
   218  	if m, ok := g.migrations.Down(version); ok {
   219  		f, response, err := g.client.RepositoryFiles.GetFile(g.projectID, m.Raw, g.getOptions)
   220  		if err != nil {
   221  			return nil, "", err
   222  		}
   223  
   224  		if response.StatusCode != http.StatusOK {
   225  			return nil, "", ErrInvalidResponse
   226  		}
   227  
   228  		content, err := base64.StdEncoding.DecodeString(f.Content)
   229  		if err != nil {
   230  			return nil, "", err
   231  		}
   232  
   233  		return io.NopCloser(strings.NewReader(string(content))), m.Identifier, nil
   234  	}
   235  
   236  	return nil, "", &os.PathError{Op: fmt.Sprintf("read version %v", version), Path: g.path, Err: os.ErrNotExist}
   237  }