config.go

  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}