github.com/fr-nvriep/migrate/v4@v4.3.2/source/gitlab/gitlab.go (about)

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