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