config.go

  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}