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 }