config.go

  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}