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