diff options
Diffstat (limited to 'bridge/gitlab/config.go')
-rw-r--r-- | bridge/gitlab/config.go | 178 |
1 files changed, 122 insertions, 56 deletions
diff --git a/bridge/gitlab/config.go b/bridge/gitlab/config.go index 99c27836..fb593819 100644 --- a/bridge/gitlab/config.go +++ b/bridge/gitlab/config.go @@ -5,6 +5,7 @@ import ( "fmt" "net/url" "os" + "path" "regexp" "sort" "strconv" @@ -18,7 +19,7 @@ import ( "github.com/MichaelMure/git-bug/bridge/core" "github.com/MichaelMure/git-bug/bridge/core/auth" "github.com/MichaelMure/git-bug/cache" - "github.com/MichaelMure/git-bug/entity" + "github.com/MichaelMure/git-bug/input" "github.com/MichaelMure/git-bug/repository" "github.com/MichaelMure/git-bug/util/colors" ) @@ -34,16 +35,22 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor if params.Owner != "" { fmt.Println("warning: --owner is ineffective for a gitlab bridge") } + if params.Login != "" { + fmt.Println("warning: --login is ineffective for a gitlab bridge") + } conf := make(core.Configuration) var err error + var baseUrl string - if (params.CredPrefix != "" || params.TokenRaw != "") && params.URL == "" { - return nil, fmt.Errorf("you must provide a project URL to configure this bridge with a token") - } - - if params.URL == "" { - params.URL = defaultBaseURL + switch { + case params.BaseURL != "": + baseUrl = params.BaseURL + default: + baseUrl, err = promptBaseUrlOptions() + if err != nil { + return nil, errors.Wrap(err, "base url prompt") + } } var url string @@ -54,7 +61,7 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor url = params.URL default: // terminal prompt - url, err = promptURL(repo) + url, err = promptURL(repo, baseUrl) if err != nil { return nil, errors.Wrap(err, "url prompt") } @@ -64,11 +71,6 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor return nil, fmt.Errorf("base URL (%s) doesn't match the project URL (%s)", params.BaseURL, url) } - user, err := repo.GetUserIdentity() - if err != nil { - return nil, err - } - var cred auth.Credential switch { @@ -77,13 +79,16 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor if err != nil { return nil, err } - if cred.UserId() != user.Id() { - return nil, fmt.Errorf("selected credential don't match the user") - } case params.TokenRaw != "": - cred = auth.NewToken(user.Id(), params.TokenRaw, target) + token := auth.NewToken(params.TokenRaw, target) + login, err := getLoginFromToken(baseUrl, token) + if err != nil { + return nil, err + } + token.SetMetadata(auth.MetaKeyLogin, login) + cred = token default: - cred, err = promptTokenOptions(repo, user.Id()) + cred, err = promptTokenOptions(repo, baseUrl) if err != nil { return nil, err } @@ -95,14 +100,14 @@ func (g *Gitlab) Configure(repo *cache.RepoCache, params core.BridgeParams) (cor } // validate project url and get its ID - id, err := validateProjectURL(params.BaseURL, url, token) + id, err := validateProjectURL(baseUrl, url, token) if err != nil { return nil, errors.Wrap(err, "project validation") } conf[core.ConfigKeyTarget] = target conf[keyProjectID] = strconv.Itoa(id) - conf[keyGitlabBaseUrl] = params.BaseURL + conf[keyGitlabBaseUrl] = baseUrl err = g.ValidateConfig(conf) if err != nil { @@ -126,7 +131,9 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error { } else if v != target { return fmt.Errorf("unexpected target name: %v", v) } - + if _, ok := conf[keyGitlabBaseUrl]; !ok { + return fmt.Errorf("missing %s key", keyGitlabBaseUrl) + } if _, ok := conf[keyProjectID]; !ok { return fmt.Errorf("missing %s key", keyProjectID) } @@ -134,20 +141,51 @@ func (g *Gitlab) ValidateConfig(conf core.Configuration) error { return nil } -func promptTokenOptions(repo repository.RepoConfig, userId entity.Id) (auth.Credential, error) { +func promptBaseUrlOptions() (string, error) { + index, err := input.PromptChoice("Gitlab base url", []string{ + "https://gitlab.com", + "enter your own base url", + }) + + if err != nil { + return "", err + } + + if index == 0 { + return defaultBaseURL, nil + } else { + return promptBaseUrl() + } +} + +func promptBaseUrl() (string, error) { + validator := func(name string, value string) (string, error) { + u, err := url.Parse(value) + if err != nil { + return err.Error(), nil + } + if u.Scheme == "" { + return "missing scheme", nil + } + if u.Host == "" { + return "missing host", nil + } + return "", nil + } + + return input.Prompt("Base url", "url", input.Required, validator) +} + +func promptTokenOptions(repo repository.RepoConfig, baseUrl string) (auth.Credential, error) { for { - creds, err := auth.List(repo, auth.WithUserId(userId), auth.WithTarget(target), auth.WithKind(auth.KindToken)) + creds, err := auth.List(repo, auth.WithTarget(target), auth.WithKind(auth.KindToken)) if err != nil { return nil, err } // if we don't have existing token, fast-track to the token prompt if len(creds) == 0 { - value, err := promptToken() - if err != nil { - return nil, err - } - return auth.NewToken(userId, value, target), nil + return promptToken(baseUrl) } fmt.Println() @@ -185,54 +223,57 @@ func promptTokenOptions(repo repository.RepoConfig, userId entity.Id) (auth.Cred switch index { case 1: - value, err := promptToken() - if err != nil { - return nil, err - } - return auth.NewToken(userId, value, target), nil + return promptToken(baseUrl) default: return creds[index-2], nil } } } -func promptToken() (string, error) { - fmt.Println("You can generate a new token by visiting https://gitlab.com/profile/personal_access_tokens.") +func promptToken(baseUrl string) (*auth.Token, error) { + fmt.Printf("You can generate a new token by visiting %s.\n", path.Join(baseUrl, "profile/personal_access_tokens")) fmt.Println("Choose 'Create personal access token' and set the necessary access scope for your repository.") fmt.Println() fmt.Println("'api' access scope: to be able to make api calls") fmt.Println() - re, err := regexp.Compile(`^[a-zA-Z0-9\-]{20}`) + re, err := regexp.Compile(`^[a-zA-Z0-9\-\_]{20}$`) if err != nil { panic("regexp compile:" + err.Error()) } - for { - fmt.Print("Enter token: ") + var login string - line, err := bufio.NewReader(os.Stdin).ReadString('\n') - if err != nil { - return "", err + validator := func(name string, value string) (complaint string, err error) { + if !re.MatchString(value) { + return "token has incorrect format", nil } - - token := strings.TrimSpace(line) - if re.MatchString(token) { - return token, nil + login, err = getLoginFromToken(baseUrl, auth.NewToken(value, target)) + if err != nil { + return fmt.Sprintf("token is invalid: %v", err), nil } + return "", nil + } - fmt.Println("token format is invalid") + rawToken, err := input.Prompt("Enter token", "token", input.Required, validator) + if err != nil { + return nil, err } + + token := auth.NewToken(rawToken, target) + token.SetMetadata(auth.MetaKeyLogin, login) + + return token, nil } -func promptURL(repo repository.RepoCommon) (string, error) { +func promptURL(repo repository.RepoCommon, baseUrl string) (string, error) { // remote suggestions remotes, err := repo.GetRemotes() if err != nil { return "", errors.Wrap(err, "getting remotes") } - validRemotes := getValidGitlabRemoteURLs(remotes) + validRemotes := getValidGitlabRemoteURLs(baseUrl, remotes) if len(validRemotes) > 0 { for { fmt.Println("\nDetected projects:") @@ -286,7 +327,7 @@ func promptURL(repo repository.RepoCommon) (string, error) { } } -func getProjectPath(projectUrl string) (string, error) { +func getProjectPath(baseUrl, projectUrl string) (string, error) { cleanUrl := strings.TrimSuffix(projectUrl, ".git") cleanUrl = strings.Replace(cleanUrl, "git@", "https://", 1) objectUrl, err := url.Parse(cleanUrl) @@ -294,38 +335,63 @@ func getProjectPath(projectUrl string) (string, error) { return "", ErrBadProjectURL } + objectBaseUrl, err := url.Parse(baseUrl) + if err != nil { + return "", ErrBadProjectURL + } + + if objectUrl.Hostname() != objectBaseUrl.Hostname() { + return "", fmt.Errorf("base url and project url hostnames doesn't match") + } return objectUrl.Path[1:], nil } -func getValidGitlabRemoteURLs(remotes map[string]string) []string { +func getValidGitlabRemoteURLs(baseUrl string, remotes map[string]string) []string { urls := make([]string, 0, len(remotes)) for _, u := range remotes { - path, err := getProjectPath(u) + path, err := getProjectPath(baseUrl, u) if err != nil { continue } - urls = append(urls, fmt.Sprintf("%s%s", "gitlab.com", path)) + urls = append(urls, fmt.Sprintf("%s/%s", baseUrl, path)) } return urls } -func validateProjectURL(baseURL, url string, token *auth.Token) (int, error) { - projectPath, err := getProjectPath(url) +func validateProjectURL(baseUrl, url string, token *auth.Token) (int, error) { + projectPath, err := getProjectPath(baseUrl, url) if err != nil { return 0, err } - client, err := buildClient(baseURL, token) + client, err := buildClient(baseUrl, token) if err != nil { return 0, err } project, _, err := client.Projects.GetProject(projectPath, &gitlab.GetProjectOptions{}) if err != nil { - return 0, err + return 0, errors.Wrap(err, "wrong token scope ou non-existent project") } return project.ID, nil } + +func getLoginFromToken(baseUrl string, token *auth.Token) (string, error) { + client, err := buildClient(baseUrl, token) + if err != nil { + return "", err + } + + user, _, err := client.Users.CurrentUser() + if err != nil { + return "", err + } + if user.Username == "" { + return "", fmt.Errorf("gitlab say username is empty") + } + + return user.Username, nil +} |