1package gitlab
2
3import (
4 "bufio"
5 "fmt"
6 neturl "net/url"
7 "os"
8 "regexp"
9 "strconv"
10 "strings"
11 "time"
12
13 "github.com/pkg/errors"
14 "github.com/xanzy/go-gitlab"
15
16 "github.com/MichaelMure/git-bug/bridge/core"
17 "github.com/MichaelMure/git-bug/repository"
18)
19
20const (
21 target = "gitlab"
22 gitlabV4Url = "https://gitlab.com/api/v4"
23 keyProjectID = "project-id"
24 keyTarget = "target"
25 keyToken = "token"
26
27 defaultTimeout = 60 * time.Second
28)
29
30var (
31 ErrBadProjectURL = errors.New("bad project url")
32)
33
34func (*Gitlab) Configure(repo repository.RepoCommon, params core.BridgeParams) (core.Configuration, error) {
35 if params.Project != "" {
36 fmt.Println("warning: --project is ineffective for a gitlab bridge")
37 }
38 if params.Owner != "" {
39 fmt.Println("warning: --owner is ineffective for a gitlab bridge")
40 }
41
42 conf := make(core.Configuration)
43 var err error
44 var url string
45 var token string
46
47 // get project url
48 if params.URL != "" {
49 url = params.URL
50
51 } else {
52 // remote suggestions
53 remotes, err := repo.GetRemotes()
54 if err != nil {
55 return nil, err
56 }
57
58 // terminal prompt
59 url, err = promptURL(remotes)
60 if err != nil {
61 return nil, err
62 }
63 }
64
65 // get user token
66 if params.Token != "" {
67 token = params.Token
68 } else {
69 token, err = promptToken()
70 if err != nil {
71 return nil, err
72 }
73 }
74
75 var ok bool
76 // validate project url and get it ID
77 ok, id, err := validateProjectURL(url, token)
78 if err != nil {
79 return nil, err
80 }
81 if !ok {
82 return nil, fmt.Errorf("invalid project id or wrong token scope")
83 }
84
85 conf[keyProjectID] = strconv.Itoa(id)
86 conf[keyToken] = token
87 conf[keyTarget] = target
88
89 return conf, nil
90}
91
92func (*Gitlab) ValidateConfig(conf core.Configuration) error {
93 if v, ok := conf[keyTarget]; !ok {
94 return fmt.Errorf("missing %s key", keyTarget)
95 } else if v != target {
96 return fmt.Errorf("unexpected target name: %v", v)
97 }
98
99 if _, ok := conf[keyToken]; !ok {
100 return fmt.Errorf("missing %s key", keyToken)
101 }
102
103 if _, ok := conf[keyProjectID]; !ok {
104 return fmt.Errorf("missing %s key", keyProjectID)
105 }
106
107 return nil
108}
109
110func promptToken() (string, error) {
111 fmt.Println("You can generate a new token by visiting https://gitlab.com/settings/tokens.")
112 fmt.Println("Choose 'Generate new token' and set the necessary access scope for your repository.")
113 fmt.Println()
114 fmt.Println("'api' scope access : access scope: to be able to make api calls")
115 fmt.Println()
116
117 re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`)
118 if err != nil {
119 panic("regexp compile:" + err.Error())
120 }
121
122 for {
123 fmt.Print("Enter token: ")
124
125 line, err := bufio.NewReader(os.Stdin).ReadString('\n')
126 if err != nil {
127 return "", err
128 }
129
130 token := strings.TrimRight(line, "\n")
131 if re.MatchString(token) {
132 return token, nil
133 }
134
135 fmt.Println("token is invalid")
136 }
137}
138
139func promptURL(remotes map[string]string) (string, error) {
140 validRemotes := getValidGitlabRemoteURLs(remotes)
141 if len(validRemotes) > 0 {
142 for {
143 fmt.Println("\nDetected projects:")
144
145 // print valid remote gitlab urls
146 for i, remote := range validRemotes {
147 fmt.Printf("[%d]: %v\n", i+1, remote)
148 }
149
150 fmt.Printf("\n[0]: Another project\n\n")
151 fmt.Printf("Select option: ")
152
153 line, err := bufio.NewReader(os.Stdin).ReadString('\n')
154 if err != nil {
155 return "", err
156 }
157
158 line = strings.TrimRight(line, "\n")
159
160 index, err := strconv.Atoi(line)
161 if err != nil || (index < 0 && index >= len(validRemotes)) {
162 fmt.Println("invalid input")
163 continue
164 }
165
166 // if user want to enter another project url break this loop
167 if index == 0 {
168 break
169 }
170
171 return validRemotes[index-1], nil
172 }
173 }
174
175 // manually enter gitlab url
176 for {
177 fmt.Print("Gitlab project URL: ")
178
179 line, err := bufio.NewReader(os.Stdin).ReadString('\n')
180 if err != nil {
181 return "", err
182 }
183
184 url := strings.TrimRight(line, "\n")
185 if line == "" {
186 fmt.Println("URL is empty")
187 continue
188 }
189
190 return url, nil
191 }
192}
193
194func getProjectPath(url string) (string, error) {
195
196 cleanUrl := strings.TrimSuffix(url, ".git")
197 objectUrl, err := neturl.Parse(cleanUrl)
198 if err != nil {
199 return "", nil
200 }
201
202 return objectUrl.Path[1:], nil
203}
204
205func getValidGitlabRemoteURLs(remotes map[string]string) []string {
206 urls := make([]string, 0, len(remotes))
207 for _, u := range remotes {
208 path, err := getProjectPath(u)
209 if err != nil {
210 continue
211 }
212
213 urls = append(urls, fmt.Sprintf("%s%s", "gitlab.com", path))
214 }
215
216 return urls
217}
218
219func validateProjectURL(url, token string) (bool, int, error) {
220 client := buildClient(token)
221
222 projectPath, err := getProjectPath(url)
223 if err != nil {
224 return false, 0, err
225 }
226
227 project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{})
228 if err != nil {
229 return false, 0, err
230 }
231
232 return true, project.ID, nil
233}