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 (*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}