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